| … | … | @@ -1,4 +1,5 @@ |
| 1 | 1 | #include "dc_compute.h" |
| 2 | #include <cstdlib> | |
| 2 | 3 | #include "dc_gpu_thread.h" // for GpuFillRequest |
| 3 | 4 | #include "dc_gpu_params.h" // shared GpuParams struct |
| 4 | 5 | #include "dc_generator_mesh_sdf.h" // for BVH flattening |
| … | … | @@ -391,7 +392,10 @@ void DCCompute::set_custom_data(const float* data, int n) { |
| 391 | 392 | // one samples. Off until something reads it; one line to re-enable, and the |
| 392 | 393 | // per-tile texture_copy blits it exists for are still proven in |
| 393 | 394 | // gate_texture_atlas.gd. |
| 394 | static const bool kUploadAtlas = false; | |
| 395 | // A/B SWITCH, because two global changes landed together and guessing which | |
| 396 | // one regressed a map that does not even use the tile grid has already cost | |
| 397 | // several runs. DC_ATLAS=1 restores the pre-change behaviour. | |
| 398 | static const bool kUploadAtlas = (getenv("DC_ATLAS") != nullptr); | |
| 395 | 399 | if (kUploadAtlas) _upload_atlas(data, n); |
| 396 | 400 | uniform_set_dirty_ = true; |
| 397 | 401 | } |
| … | … | @@ -20,6 +20,7 @@ |
| 20 | 20 | // GPU thread and wait (or fall back to CPU if the GPU thread isn't available). |
| 21 | 21 | |
| 22 | 22 | #include "dc_compute.h" |
| 23 | #include <cstdlib> | |
| 23 | 24 | #include "dc_gpu_mesh_pipeline.h" |
| 24 | 25 | #include "dc_density.h" |
| 25 | 26 | #include "dc_density_grid.h" |
| … | … | @@ -287,9 +288,21 @@ private: |
| 287 | 288 | // existing capacity keeps the same data() address and the upload is |
| 288 | 289 | // silently skipped -- new terrain on disk, old terrain on screen, no |
| 289 | 290 | // error anywhere. A tile arriving from a peer mid-session is that case. |
| 291 | // A/B SWITCH: DC_NOVER=1 ignores the content version and restores the | |
| 292 | // pointer-only test this had before 2026-09-03. It also COUNTS the | |
| 293 | // uploads, because "set_custom_data ran every batch" and "it ran twice" | |
| 294 | // look identical from a frame graph and only one of them is a | |
| 295 | // regression. | |
| 296 | static const bool kNoVersion = (getenv("DC_NOVER") != nullptr); | |
| 297 | static uint64_t s_uploads = 0; | |
| 290 | 298 | const float* hb = batch[0].custom_data; |
| 291 | const uint32_t hv = batch[0].custom_data_version; | |
| 299 | const uint32_t hv = kNoVersion ? current_custom_data_version_ | |
| 300 | : batch[0].custom_data_version; | |
| 292 | 301 | if (hb != current_custom_data_ || hv != current_custom_data_version_) { |
| 302 | if ((++s_uploads % 16) == 1 || s_uploads < 4) { | |
| 303 | fprintf(stderr, "DCGpuThread: custom-data upload #%llu (%d floats)\n", | |
| 304 | (unsigned long long)s_uploads, batch[0].custom_data_n); | |
| 305 | } | |
| 293 | 306 | compute_.set_custom_data(hb, batch[0].custom_data_n); |
| 294 | 307 | current_custom_data_ = hb; |
| 295 | 308 | current_custom_data_version_ = hv; |
| … | … | @@ -1,4 +1,6 @@ |
| 1 | 1 | #include "dc_worker_cpu.h" |
| 2 | #include <atomic> | |
| 3 | #include <cstdio> | |
| 2 | 4 | #include "dc_octree.h" |
| 3 | 5 | #include "dc_pool.h" |
| 4 | 6 | #include "dc_simplify.h" |
| … | … | @@ -308,6 +310,44 @@ void DCWorkerCPU::_process_cell(const CellRequest &request, int64_t picked_up_us |
| 308 | 310 | |
| 309 | 311 | int tris_raw = static_cast<int>(result.mesh_data.indices.size()) / 3; |
| 310 | 312 | |
| 313 | // ⛔ WHICH MESHER PRODUCED THIS CHUNK? Counted HERE, after the mesh exists, | |
| 314 | // because that is the only point where the three cases are distinguishable. | |
| 315 | // | |
| 316 | // A first cut of this counter sat before the mesh block and reported | |
| 317 | // "gpu_mesh 15%", which CANNOT be read: gpu_meshed is set from | |
| 318 | // `!gpu_mesh.indices.empty()`, so a chunk the GPU meshed correctly to an | |
| 319 | // EMPTY result (no surface in it, and there are many) counts identically to | |
| 320 | // one the CPU had to mesh. Those have opposite meanings. | |
| 321 | // | |
| 322 | // cpu_mesh_solid is the number that matters: chunks with real geometry that | |
| 323 | // the CPU dual-contoured. Every one of those sits in the same world as | |
| 324 | // GPU-meshed neighbours, and the two are SEPARATE implementations — | |
| 325 | // qef_solve.glsl vs libdc/src/dc_contour.cpp. test_erosion_parity.cpp exists | |
| 326 | // because the GPU and CPU DENSITY paths silently drifted 4 km apart; there is | |
| 327 | // no equivalent gate for the two MESHERS, so a disagreement between them | |
| 328 | // shows up only as chunks that do not line up. | |
| 329 | { | |
| 330 | static std::atomic<uint64_t> s_total{0}, s_dens{0}, s_gmesh{0}, s_cmesh{0}, s_empty{0}; | |
| 331 | const uint64_t n = ++s_total; | |
| 332 | if (gpu_filled) s_dens++; | |
| 333 | if (tris_raw == 0) s_empty++; | |
| 334 | else if (gpu_meshed) s_gmesh++; | |
| 335 | else s_cmesh++; | |
| 336 | if ((n % 64) == 0) { | |
| 337 | const uint64_t solid = s_gmesh.load() + s_cmesh.load(); | |
| 338 | fprintf(stderr, "[GPUPATH] chunks=%llu gpu_density=%llu (%.0f%%) " | |
| 339 | "| SOLID %llu: gpu_mesh=%llu (%.0f%%) cpu_mesh=%llu (%.0f%%)" | |
| 340 | " | empty=%llu cpu_density_fallback=%llu\n", | |
| 341 | (unsigned long long)n, | |
| 342 | (unsigned long long)s_dens.load(), 100.0 * s_dens.load() / n, | |
| 343 | (unsigned long long)solid, | |
| 344 | (unsigned long long)s_gmesh.load(), solid ? 100.0 * s_gmesh.load() / solid : 0.0, | |
| 345 | (unsigned long long)s_cmesh.load(), solid ? 100.0 * s_cmesh.load() / solid : 0.0, | |
| 346 | (unsigned long long)s_empty.load(), | |
| 347 | (unsigned long long)(n - s_dens.load())); | |
| 348 | } | |
| 349 | } | |
| 350 | ||
| 311 | 351 | // Free grid early |
| 312 | 352 | grid.data.clear(); grid.data.shrink_to_fit(); |
| 313 | 353 | grid.normal_x.clear(); grid.normal_x.shrink_to_fit(); |