From 8fd3ccc4724157d33aeac61a667e9279cac1763f Mon Sep 17 00:00:00 2001 From: sync0516 Date: Thu, 27 Aug 2026 16:43:09 -0700 Subject: [PATCH 1/3] Optimize CPU mushroom biome verification Refactor cubiomes.c to improve memory management and structure usage. Replace malloc with calloc for Cubiomes creation, implement FloodEntry struct for queue management, and update flood fill logic to use the new structure. --- src/cubiomes.c | 349 ++++++++++++++++++++++++++++++------------------- 1 file changed, 213 insertions(+), 136 deletions(-) diff --git a/src/cubiomes.c b/src/cubiomes.c index e01c46f..74b50a4 100644 --- a/src/cubiomes.c +++ b/src/cubiomes.c @@ -6,12 +6,55 @@ #include #include +typedef struct FloodEntry { + int i; + int j; + int d; +} FloodEntry; + struct Cubiomes { Generator g; + uint8_t *visited; + size_t visited_capacity; + uint32_t *candidates; + size_t candidates_capacity; + FloodEntry *queue; + size_t queue_capacity; }; +// getSpline is implemented in the bundled cubiomes biomenoise module, but is +// not part of its public header. +float getSpline(const Spline *sp, const float *vals); + +static void ensure_capacity(void **buffer, size_t *capacity, size_t needed, + size_t element_size, const char *name) { + if (*capacity >= needed) + return; + + size_t new_capacity = *capacity ? *capacity : 4096; + while (new_capacity < needed) { + if (new_capacity > SIZE_MAX / 2) { + new_capacity = needed; + break; + } + new_capacity *= 2; + } + if (new_capacity > SIZE_MAX / element_size) { + fprintf(stderr, "%s is too large\n", name); + abort(); + } + + void *resized = realloc(*buffer, new_capacity * element_size); + if (resized == NULL) { + fprintf(stderr, "could not allocate %s\n", name); + abort(); + } + *buffer = resized; + *capacity = new_capacity; +} + Cubiomes *cubiomes_create(int large_biomes) { - Cubiomes *cubiomes = malloc(sizeof(Cubiomes)); + Cubiomes *cubiomes = calloc(1, sizeof(Cubiomes)); if (cubiomes == NULL) { fprintf(stderr, "cubiomes_create failed\n"); abort(); @@ -21,6 +64,9 @@ Cubiomes *cubiomes_create(int large_biomes) { } void cubiomes_free(Cubiomes *cubiomes) { + free(cubiomes->visited); + free(cubiomes->candidates); + free(cubiomes->queue); free(cubiomes); } @@ -28,13 +74,81 @@ void cubiomes_apply_seed(Cubiomes *cubiomes, uint64_t seed) { applySeed(&cubiomes->g, DIM_OVERWORLD, seed); } +static int has_mushroom_continentalness(Generator *g, int scale, int x, int z) { + int qx = x; + int qz = z; + double px; + double pz; + + if (scale > 4) { + int qscale = scale / 4; + int mid = qscale / 2; + qx = x * qscale + mid; + qz = z * qscale + mid; + px = qx; + pz = qz; + } else { + px = qx + sampleDoublePerlin(&g->bn.climate[NP_SHIFT], qx, 0, qz) * 4.0; + pz = qz + sampleDoublePerlin(&g->bn.climate[NP_SHIFT], qz, qx, 0) * 4.0; + } + + float c = sampleDoublePerlin(&g->bn.climate[NP_CONTINENTALNESS], px, 0, pz); + return (int64_t)(10000.0F * c) <= -10500; +} + static int eval(Generator *g, int scale, int x, int y, int z, void *data) { - double px = x, pz = z; - px += sampleDoublePerlin(&g->bn.climate[NP_SHIFT], x, 0, z) * 4.0; - pz += sampleDoublePerlin(&g->bn.climate[NP_SHIFT], x, z, 0) * 4.0; + (void)y; + (void)data; + return has_mushroom_continentalness(g, scale, x, z); +} + +static int is_mushroom_fields(Generator *g, int scale, int x, int y, int z) { + uint64_t dat = 0; + uint64_t *p_dat = NULL; + double px; + double pz; + + if (scale > 4) { + int qscale = scale / 4; + int mid = qscale / 2; + x = x * qscale + mid; + z = z * qscale + mid; + px = x; + pz = z; + p_dat = &dat; + } else { + px = x + sampleDoublePerlin(&g->bn.climate[NP_SHIFT], x, 0, z) * 4.0; + pz = z + sampleDoublePerlin(&g->bn.climate[NP_SHIFT], z, x, 0) * 4.0; + } + + float c = sampleDoublePerlin(&g->bn.climate[NP_CONTINENTALNESS], px, 0, pz); + int64_t c_quantized = (int64_t)(10000.0F * c); + // Mushroom fields have no parameter point above this continentalness. + // Rejecting here avoids sampling the other climate fields for most cells. + if (c_quantized > -10500) + return 0; - double c = sampleDoublePerlin(&g->bn.climate[NP_CONTINENTALNESS], px, 0, pz); - return c < -1.05f; + // This mirrors the remainder of sampleBiomeNoise(), without resampling + // shifts and continentalness or allocating getBiomeAt()'s one-cell cache. + float e = sampleDoublePerlin(&g->bn.climate[NP_EROSION], px, 0, pz); + float w = sampleDoublePerlin(&g->bn.climate[NP_WEIRDNESS], px, 0, pz); + float np_param[] = { + c, e, -3.0F * (fabsf(fabsf(w) - 0.6666667F) - 0.33333334F), w, + }; + double off = getSpline(g->bn.sp, np_param) + 0.015F; + float d = 1.0 - (y * 4) / 128.0 - 83.0 / 160.0 + off; + float t = sampleDoublePerlin(&g->bn.climate[NP_TEMPERATURE], px, 0, pz); + float h = sampleDoublePerlin(&g->bn.climate[NP_HUMIDITY], px, 0, pz); + + int64_t np[] = { + (int64_t)(10000.0F * t), + (int64_t)(10000.0F * h), + c_quantized, + (int64_t)(10000.0F * e), + (int64_t)(10000.0F * d), + (int64_t)(10000.0F * w), + }; + return climateToBiome(g->mc, (const uint64_t *)np, p_dat) == mushroom_fields; } static Range make_range(int32_t x, int32_t z, int32_t range, int32_t scale) { @@ -51,45 +165,49 @@ static Range make_range(int32_t x, int32_t z, int32_t range, int32_t scale) { struct locate_info_t { + Cubiomes *cubiomes; Generator *g; - int *ids; Range r; - int match, tol; + int tol; volatile char *stop; }; +static int is_visited(const Cubiomes *cubiomes, size_t index) +{ + return (cubiomes->visited[index >> 3] >> (index & 7)) & 1; +} + +static void mark_visited(Cubiomes *cubiomes, size_t index) +{ + cubiomes->visited[index >> 3] |= (uint8_t)(1u << (index & 7)); +} + static int floodFillGen(struct locate_info_t *info, int i, int j, Pos *p) { - typedef struct { int i, j, d; } entry_t; - entry_t *queue = (entry_t*) malloc(info->r.sx*info->r.sz * sizeof(*queue)); - int qn = 1; - queue->i = i; - queue->j = j; - queue->d = 0; + Cubiomes *cubiomes = info->cubiomes; + ensure_capacity((void **)&cubiomes->queue, &cubiomes->queue_capacity, + 1, sizeof(*cubiomes->queue), "flood-fill queue"); + size_t qn = 1; + cubiomes->queue[0] = (FloodEntry){i, j, 0}; int64_t sumx = 0; int64_t sumz = 0; int n = 0; - while (--qn >= 0) + while (qn != 0) { + FloodEntry entry = cubiomes->queue[--qn]; if (info->stop && *info->stop) - { - free(queue); return 0; - } - int d = queue[qn].d; - i = queue[qn].i; - j = queue[qn].j; - int k = j * info->r.sx + i; - int id = info->ids[k]; - if (id == INT_MAX) + int d = entry.d; + i = entry.i; + j = entry.j; + size_t index = (size_t)j * info->r.sx + i; + if (is_visited(cubiomes, index)) continue; - info->ids[k] = INT_MAX; + mark_visited(cubiomes, index); int x = info->r.x + i; int z = info->r.z + j; - if (info->g->mc >= MC_1_18) - id = getBiomeAt(info->g, info->r.scale, x, info->r.y, z); - if (id == info->match) + if (is_mushroom_fields(info->g, info->r.scale, x, info->r.y, z)) { sumx += x; sumz += z; @@ -101,18 +219,19 @@ int floodFillGen(struct locate_info_t *info, int i, int j, Pos *p) if (++d >= info->tol) continue; } - entry_t next[] = { {i,j-1,d}, {i,j+1,d}, {i-1,j,d}, {i+1,j,d} }; - for (k = 0; k < 4; k++) + FloodEntry next[] = { {i,j-1,d}, {i,j+1,d}, {i-1,j,d}, {i+1,j,d} }; + ensure_capacity((void **)&cubiomes->queue, &cubiomes->queue_capacity, + qn + 4, sizeof(*cubiomes->queue), "flood-fill queue"); + for (int k = 0; k < 4; k++) { i = next[k].i; j = next[k].j; if (i < 0 || i >= info->r.sx || j < 0 || j >= info->r.sz) continue; - if (info->ids[j * info->r.sx + i] == INT_MAX) + if (is_visited(cubiomes, (size_t)j * info->r.sx + i)) continue; - queue[qn++] = next[k]; + cubiomes->queue[qn++] = next[k]; } } - free(queue); if (n) { p->x = (int) round((sumx / (double)n + 0.5) * info->r.scale); @@ -122,140 +241,97 @@ int floodFillGen(struct locate_info_t *info, int i, int j, Pos *p) } static -int getBiomeCentersOpt(Pos *pos, int *siz, int nmax, Generator *g, Range r, +int getBiomeCentersOpt(Pos *pos, int *siz, int nmax, Cubiomes *cubiomes, Range r, int match, int minsiz, int tol, int step, volatile char *stop) { + Generator *g = &cubiomes->g; if (minsiz <= 0) minsiz = 1; int i, j, k, n = 0; - int *ids = (int*) malloc(r.sx*r.sz * sizeof(int)); - memset(ids, -1, r.sx*r.sz * sizeof(int)); + size_t cell_count = (size_t)r.sx * r.sz; + if (cell_count > UINT32_MAX) { + fprintf(stderr, "biome-center range is too large\n"); + abort(); + } + size_t visited_size = (cell_count + 7) / 8; + ensure_capacity((void **)&cubiomes->visited, &cubiomes->visited_capacity, + visited_size, sizeof(*cubiomes->visited), "visited bitmap"); + memset(cubiomes->visited, 0, visited_size); if (tol <= 0) tol = 1; if (step <= 0) step = 1; struct locate_info_t info; + info.cubiomes = cubiomes; info.g = g; - info.ids = ids; info.r = r; info.stop = stop; - info.match = match; info.tol = tol; - if (g->mc >= MC_1_18) - { - const int *lim = getBiomeParaLimits(g->mc, match); - - int para[] = { - NP_TEMPERATURE, - NP_HUMIDITY, - NP_EROSION, - NP_CONTINENTALNESS, - NP_WEIRDNESS, - }; - int npara = sizeof(para) / sizeof(para[0]); - if (step == 1) - step = 1 + floor(sqrt(minsiz) * 0.5); - - for (j = 0; j < r.sz; j += step) - { - for (i = 0; i < r.sx; i += step) - { - if (stop && *stop) - break; - for (k = 0; k < npara; k++) - { - const int *plim = lim + 2*para[k]; - if (plim[0] == INT_MIN && plim[1] == INT_MAX) - continue; - DoublePerlinNoise *dpn = &g->bn.climate[para[k]]; - double px = (r.x+i) * r.scale / 4.0; - double pz = (r.z+j) * r.scale / 4.0; - int p = 10000 * sampleDoublePerlin(dpn, px, 0, pz); - if (p < plim[0] || p > plim[1]) - { - ids[j*r.sx + i] = -2; - break; - } - } - } - } - match = -1; // id entries that are still -1 are our candidates - } - else // 1.17- - { - int ts = 32 / r.scale; - if (r.sx + r.sz < 32) - ts = 8; - - int tx = (int) floor(r.x / (double)ts); - int tz = (int) floor(r.z / (double)ts); - int tw = (int) ceil((r.x+r.sx) / (double)ts) - tx; - int th = (int) ceil((r.z+r.sz) / (double)ts) - tz; - int ti, tj; - - BiomeFilter bf; - setupBiomeFilter(&bf, g->mc, 0, &match, 1, 0, 0, 0, 0); - //applySeed(g, 0, g->seed); + const int *lim = getBiomeParaLimits(g->mc, match); + size_t candidates_len = 0; - Range tr = { r.scale, 0, 0, ts, ts, 0, 1 }; - int *cache = allocCache(g, r); + int para[] = { + NP_TEMPERATURE, + NP_HUMIDITY, + NP_EROSION, + NP_CONTINENTALNESS, + NP_WEIRDNESS, + }; + int npara = sizeof(para) / sizeof(para[0]); + if (step == 1) + step = 1 + floor(sqrt(minsiz) * 0.5); - for (tj = 0; tj < th; tj++) + for (j = 0; j < r.sz; j += step) + { + for (i = 0; i < r.sx; i += step) { - for (ti = 0; ti < tw; ti++) + if (stop && *stop) + break; + int candidate = 1; + for (k = 0; k < npara; k++) { - if (stop && *stop) - break; - tr.x = (tx+ti) * ts; - tr.z = (tz+tj) * ts; - if (checkForBiomes(g, cache, tr, DIM_OVERWORLD, g->seed, - &bf, stop) != 1) - { + const int *plim = lim + 2*para[k]; + if (plim[0] == INT_MIN && plim[1] == INT_MAX) continue; - } - for (j = 0; j < ts; j++) + DoublePerlinNoise *dpn = &g->bn.climate[para[k]]; + double px = (r.x+i) * r.scale / 4.0; + double pz = (r.z+j) * r.scale / 4.0; + int p = 10000 * sampleDoublePerlin(dpn, px, 0, pz); + if (p < plim[0] || p > plim[1]) { - int jj = tr.z + j - r.z; - if (jj < 0 || jj >= r.sz) - continue; - for (i = 0; i < ts; i++) - { - int ii = tr.x + i - r.x; - if (ii < 0 || ii >= r.sx) - continue; - ids[jj*r.sx + ii] = cache[j*tr.sx + i]; - } + candidate = 0; + break; } } + if (candidate) { + ensure_capacity((void **)&cubiomes->candidates, + &cubiomes->candidates_capacity, + candidates_len + 1, sizeof(*cubiomes->candidates), + "biome-center candidates"); + cubiomes->candidates[candidates_len++] = (uint32_t)((size_t)j * r.sx + i); + } } - free(cache); } - // applySeed(g, DIM_OVERWORLD, g->seed); - for (j = 0; j < r.sz; j += step) + for (size_t candidate_index = 0; candidate_index < candidates_len; candidate_index++) { - for (i = 0; i < r.sx; i += step) + size_t index = cubiomes->candidates[candidate_index]; + if (is_visited(cubiomes, index)) + continue; + i = (int)(index % r.sx); + j = (int)(index / r.sx); + Pos center; + int area = floodFillGen(&info, i, j, ¢er); + if (area >= minsiz) { - if (stop && *stop) + pos[n] = center; + if (siz) siz[n] = area; + if (++n >= nmax) break; - if (ids[j*r.sx + i] != match) - continue; - Pos center; - int area = floodFillGen(&info, i, j, ¢er); - if (area >= minsiz) - { - pos[n] = center; - if (siz) siz[n] = area; - if (++n >= nmax) - goto L_end; - } } } -L_end: - free(ids); - return n; } @@ -271,7 +347,7 @@ int cubiomes_test_biome_centers(Cubiomes *cubiomes, int32_t x, int32_t z, int32_ int siz; Range r = make_range(x, z, range, scale); int minsiz = min_area / (scale * scale); - int n = getBiomeCentersOpt(&pos, &siz, 1, &cubiomes->g, r, mushroom_fields, minsiz, tol, 0, NULL); + int n = getBiomeCentersOpt(&pos, &siz, 1, cubiomes, r, mushroom_fields, minsiz, tol, 0, NULL); if (n == 1) { if (out) { *out = (PosArea){ @@ -284,3 +360,4 @@ int cubiomes_test_biome_centers(Cubiomes *cubiomes, int32_t x, int32_t z, int32_ } return 0; } + From c1a66cc06f770af24dfb9a21b32d1406c31182d3 Mon Sep 17 00:00:00 2001 From: sync0516 Date: Thu, 27 Aug 2026 16:43:56 -0700 Subject: [PATCH 2/3] Add CPU_ARCH variable for manual architecture override --- makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/makefile b/makefile index 15afc77..32393b9 100644 --- a/makefile +++ b/makefile @@ -14,6 +14,7 @@ else endif PRINT_INTERVAL ?= 256 +CPU_ARCH ?= # Auto-detect GPU architecture: # - RTX 40xx/50xx series: sm_89 is faster than native sm_120 # - Everything else: use native @@ -31,6 +32,9 @@ endif $(info Using ARCH = $(ARCH)) override CFLAGS += -O3 +ifneq ($(strip $(CPU_ARCH)),) + override CFLAGS += -march=$(CPU_ARCH) +endif override CXXFLAGS += -O3 -std=c++20 -I asio/asio/include -DOMISSION_LARGE_BIOMES=$(LARGE_BIOMES) -DOMISSION_UNBOUND=$(UNBOUND) -DPRINT_INTERVAL=$(PRINT_INTERVAL) override NVCC_FLAGS += $(CXXFLAGS) --expt-relaxed-constexpr --default-stream per-thread -arch=$(ARCH) -use_fast_math @@ -105,3 +109,4 @@ server.o: src/server.cpp src/server.h src/common.h main: $(MAIN_DEP) $(MAIN_CXX) $(MAIN_SRC) -o $@-$(BIN_SUFFIX) $(MAIN_CXXFLAGS) endif + From 11154d2815c73313965cb0a1fb2db3f71079a2fa Mon Sep 17 00:00:00 2001 From: sync0516 Date: Thu, 27 Aug 2026 16:44:39 -0700 Subject: [PATCH 3/3] Update README with CPU_ARCH tuning information Added section for optional CPU architecture tuning in README. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 7d5c5cd..d4e27ef 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,12 @@ CUDA app that uses various clever tricks to produce mushroom islands of adequate - Change GPU target for PTX compilation - `ARCH=native` is default for GPUs 30 series or older - `ARCH=sm_89` is default for GPUs 40 series or newer + - CPU_ARCH + - Optional CPU architecture tuning for the GCC/Clang build path. The default + remains portable; use `CPU_ARCH=native` when the binary will run only on + the machine that builds it. + - Example usage + `make -B CPU_ARCH=native` - PRINT_INTERVAL - Change how often the program prints benchmarking info - Default is 256, meaning every 256 iterations of the full GPU pipeline, it will print the table with stats. @@ -60,3 +66,4 @@ CUDA app that uses various clever tricks to produce mushroom islands of adequate - Example usage `make -B LARGE_BIOMES=1` for large biomes `make -B LARGE_BIOMES=0` for small biomes (default) +