OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain commits / 41486bd

geiss: keep the noise volumes as HALF, not float32
The parity gate caught this: dc-terrain and the raymarcher disagreed by up to 0.196 density units on the same field at the same points. Not a different field -- a different PRECISION. The generator was promoting the .vol files' halves to float32 and creating R32G32B32A32 textures, so the hardware trilinear filtering ran at a different precision from the raymarcher's R16G16B16A16, and the 25-unit prewarp amplified the difference. Half is also the faithful choice: the files are half and the demo's D3D textures would have been too. So the promoting side was both the wrong one and the deviating one. Kept as raw uint16 end to end, which also deletes the hand-rolled half->float conversion and its bug surface. gpu_noise_volumes() returns const void* const* now, documented as RGBA half. Max difference 0.196 -> 0.024, i.e. 0.038 voxels of surface movement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
41486bdbf0088d072a6f90b0bf5be7e849cafbc2
by plankatron <fleeky@gmail.com> · 2026-09-07T22:51:14.000Z

Changed files (7) +26 -49

libdc/include/dc_generator.h +6 -2
……@@ -97,9 +97,13 @@ public:
9797 // gpu_custom_data above.
9898 //
9999 // Returns an array of EIGHT pointers -- plain 0-3, then packed 4-7 -- each
100 // to dim^3 RGBA float32. nullptr means the body samples no volumes, and
100 // to dim^3 RGBA HALF floats (the .vol files' native format, and what the
101 // texture is created as). Deliberately not float32: promoting them changes
102 // the precision of the hardware trilinear filtering, and a 25-unit domain
103 // warp amplifies that into a visible density difference from anything else
104 // sampling the same volumes. nullptr means the body samples no volumes, and
101105 // then neither shader declares the bindings.
102 virtual const float* const* gpu_noise_volumes() const { return nullptr; }
106 virtual const void* const* gpu_noise_volumes() const { return nullptr; }
103107 // Side length, unlike gpu_custom_data_count which is an element count.
104108 virtual int gpu_noise_dim() const { return 0; }
105109 virtual uint32_t gpu_noise_version() const { return 0; }
libdc/include/dc_generator_geiss.h +8 -35
……@@ -15,7 +15,6 @@
1515#include "dc_generator.h"
1616#include "geiss_sdf_glsl.h"
1717
18#include <cmath>
1918#include <cstdint>
2019#include <cstdio>
2120#include <string>
……@@ -26,7 +25,7 @@ public:
2625 bool has_gpu_shader() const override { return volumes_ok_; }
2726 const char* gpu_shader_source() const override { return GEISS_SDF_GLSL; }
2827
29 const float* const* gpu_noise_volumes() const override {
28 const void* const* gpu_noise_volumes() const override {
3029 return volumes_ok_ ? volume_ptrs_ : nullptr;
3130 }
3231 int gpu_noise_dim() const override { return dim_; }
……@@ -76,8 +75,8 @@ public:
7675private:
7776 // 5-int header (w, h, d, bytes-per-texel, body-bytes), then RGBA half
7877 // floats, mip 0 first. Only mip 0 is read: the density samples
79 // textureLod(..., 0) everywhere.
80 static bool _load_one(const std::string& path, std::vector<float>& out) {
78 // textureLod(..., 0) everywhere. Kept as HALF -- see gpu_noise_volumes().
79 static bool _load_one(const std::string& path, std::vector<uint16_t>& out) {
8180 FILE* f = fopen(path.c_str(), "rb");
8281 if (!f) return false;
8382 int32_t hdr[5];
……@@ -85,40 +84,14 @@ private:
8584 const int w = hdr[0], h = hdr[1], d = hdr[2];
8685 if (w <= 0 || h <= 0 || d <= 0 || w != h || h != d) { fclose(f); return false; }
8786 const size_t n = size_t(w) * h * d * 4;
88 std::vector<uint16_t> halfs(n);
89 const bool read_ok = fread(halfs.data(), sizeof(uint16_t), n, f) == n;
90 fclose(f);
91 if (!read_ok) return false;
9287 out.resize(n);
93 for (size_t i = 0; i < n; i++) out[i] = _half_to_float(halfs[i]);
94 return true;
95 }
96
97 static float _half_to_float(uint16_t h) {
98 const uint32_t sign = uint32_t(h & 0x8000) << 16;
99 uint32_t exp = (h >> 10) & 0x1F;
100 uint32_t man = h & 0x3FF;
101 uint32_t bits;
102 if (exp == 0) {
103 if (man == 0) { bits = sign; }
104 else { // subnormal: normalise it
105 exp = 1;
106 while ((man & 0x400) == 0) { man <<= 1; exp--; }
107 man &= 0x3FF;
108 bits = sign | ((exp + 112) << 23) | (man << 13);
109 }
110 } else if (exp == 31) { // inf / nan
111 bits = sign | 0x7F800000u | (man << 13);
112 } else {
113 bits = sign | ((exp + 112) << 23) | (man << 13);
114 }
115 float out;
116 __builtin_memcpy(&out, &bits, 4);
117 return out;
88 const bool read_ok = fread(out.data(), sizeof(uint16_t), n, f) == n;
89 fclose(f);
90 return read_ok;
11891 }
11992
120 std::vector<float> storage_[8];
121 const float* volume_ptrs_[8] = {};
93 std::vector<uint16_t> storage_[8];
94 const void* volume_ptrs_[8] = {};
12295 int dim_ = 16;
12396 uint32_t version_ = 0;
12497 bool volumes_ok_ = false;
src/dc_compute.cpp +5 -5
……@@ -387,14 +387,14 @@ void DCCompute::_create_noise_placeholders() {
387387 }
388388 if (noise_tex_[0].is_valid()) return;
389389 Ref<RDTextureFormat> fmt; fmt.instantiate();
390 fmt->set_format(RenderingDevice::DATA_FORMAT_R32G32B32A32_SFLOAT);
390 fmt->set_format(RenderingDevice::DATA_FORMAT_R16G16B16A16_SFLOAT);
391391 fmt->set_width(1); fmt->set_height(1); fmt->set_depth(1);
392392 fmt->set_array_layers(1); fmt->set_mipmaps(1);
393393 fmt->set_texture_type(RenderingDevice::TEXTURE_TYPE_3D);
394394 fmt->set_usage_bits(RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT
395395 | RenderingDevice::TEXTURE_USAGE_CAN_UPDATE_BIT);
396396 Ref<RDTextureView> view; view.instantiate();
397 PackedByteArray zero; zero.resize(16); memset(zero.ptrw(), 0, 16);
397 PackedByteArray zero; zero.resize(8); memset(zero.ptrw(), 0, 8); // 1 RGBA half texel
398398 TypedArray<PackedByteArray> data; data.push_back(zero);
399399 for (int i = 0; i < 8; i++) noise_tex_[i] = rd_->texture_create(fmt, view, data);
400400 noise_dim_ = 1;
……@@ -407,20 +407,20 @@ void DCCompute::_free_noise() {
407407 noise_dim_ = 0;
408408}
409409
410void DCCompute::set_noise_volumes(const float* const* volumes, int dim) {
410void DCCompute::set_noise_volumes(const void* const* volumes, int dim) {
411411 if (rd_ == nullptr) return;
412412 _free_noise();
413413 if (volumes == nullptr || dim <= 0) { _create_noise_placeholders(); return; }
414414
415415 Ref<RDTextureFormat> fmt; fmt.instantiate();
416 fmt->set_format(RenderingDevice::DATA_FORMAT_R32G32B32A32_SFLOAT);
416 fmt->set_format(RenderingDevice::DATA_FORMAT_R16G16B16A16_SFLOAT);
417417 fmt->set_width(dim); fmt->set_height(dim); fmt->set_depth(dim);
418418 fmt->set_array_layers(1); fmt->set_mipmaps(1);
419419 fmt->set_texture_type(RenderingDevice::TEXTURE_TYPE_3D);
420420 fmt->set_usage_bits(RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT
421421 | RenderingDevice::TEXTURE_USAGE_CAN_UPDATE_BIT);
422422 Ref<RDTextureView> view; view.instantiate();
423 const int64_t n_bytes = int64_t(dim) * dim * dim * 4 * 4;
423 const int64_t n_bytes = int64_t(dim) * dim * dim * 4 * 2; // RGBA half
424424 for (int i = 0; i < 8; i++) {
425425 PackedByteArray b; b.resize(n_bytes);
426426 memcpy(b.ptrw(), volumes[i], size_t(n_bytes));
src/dc_compute.h +2 -2
……@@ -105,9 +105,9 @@ public:
105105 RID noise_sampler_linear() const { return noise_sampler_linear_; }
106106 RID noise_sampler_nearest() const { return noise_sampler_nearest_; }
107107 RID noise_tex(int i) const { return noise_tex_[i]; }
108 // data: 8 volumes of dim^3 RGBA float32, plain 0-3 then packed 4-7.
108 // data: 8 volumes of dim^3 RGBA HALF, plain 0-3 then packed 4-7.
109109 // Pass nullptr to drop back to the placeholders.
110 void set_noise_volumes(const float* const* volumes, int dim);
110 void set_noise_volumes(const void* const* volumes, int dim);
111111 // Upload a baked custom data block (metres, n*n row-major, row = Z).
112112 // Pass n = 0 to release it back to the one-float placeholder.
113113 void set_custom_data(const float* data, int n);
src/dc_gpu_thread.h +3 -3
……@@ -42,7 +42,7 @@ struct GpuFillRequest {
4242 const float* custom_data = nullptr; // optional: baked custom data (binding 13)
4343 int custom_data_n = 0;
4444 uint32_t custom_data_version = 0; // content version; see DCGenerator
45 const float* const* noise_volumes = nullptr; // optional: 8 sampler3D volumes
45 const void* const* noise_volumes = nullptr; // optional: 8 sampler3D volumes (RGBA half)
4646 int noise_dim = 0; // side length (16 for the GPU Gems volumes)
4747 uint32_t noise_version = 0;
4848 const std::vector<TerrainEdit>* edits = nullptr; // terrain edits for GPU evaluation
……@@ -108,7 +108,7 @@ public:
108108 uint32_t mesh_quality_flags = 0,
109109 const float* custom_data = nullptr, int custom_data_n = 0,
110110 uint32_t custom_data_version = 0,
111 const float* const* noise_volumes = nullptr, int noise_dim = 0,
111 const void* const* noise_volumes = nullptr, int noise_dim = 0,
112112 uint32_t noise_version = 0) {
113113 if (!running_.load() || !gpu_available_.load()) {
114114 return false;
……@@ -166,7 +166,7 @@ private:
166166 DCGpuMeshPipeline mesh_pipeline_;
167167 bool mesh_pipeline_available_ = false;
168168 const char* current_custom_glsl_ = nullptr; // track to avoid recompiling same shader
169 const float* const* current_noise_volumes_ = nullptr;
169 const void* const* current_noise_volumes_ = nullptr;
170170 uint32_t current_noise_version_ = 0;
171171 const float* current_custom_data_ = nullptr; // same trick: re-upload only on change
172172 uint32_t current_custom_data_version_ = 0; // ...and on content version, for growth
src/dc_terrain.cpp +1 -1
……@@ -3199,7 +3199,7 @@ PackedFloat32Array DCTerrain::debug_gpu_density(const Vector3 &origin, float vox
31993199 const float* hb = generator_ptr_->gpu_custom_data();
32003200 int hbn = generator_ptr_->gpu_custom_data_count();
32013201 uint32_t hbv = generator_ptr_->gpu_custom_data_version();
3202 const float* const* nv = generator_ptr_->gpu_noise_volumes();
3202 const void* const* nv = generator_ptr_->gpu_noise_volumes();
32033203 int nvd = generator_ptr_->gpu_noise_dim();
32043204 uint32_t nvv = generator_ptr_->gpu_noise_version();
32053205
src/dc_worker_cpu.cpp +1 -1
……@@ -239,7 +239,7 @@ void DCWorkerCPU::_process_cell(const CellRequest &request, int64_t picked_up_us
239239 const float* custom_data = nullptr;
240240 int custom_data_n = 0;
241241 uint32_t custom_data_version = 0;
242 const float* const* noise_volumes = nullptr;
242 const void* const* noise_volumes = nullptr;
243243 int noise_dim = 0;
244244 uint32_t noise_version = 0;
245245 if (request.generator && request.generator->has_gpu_shader()) {