OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain commits / 396da97

atlas: carry the heightfield as a texture too, binding 14 — RUNTIME UNVERIFIED
⚠⚠ THE GPU PATH OF THIS COMMIT HAS NOT BEEN EXECUTED. It compiles, and the CPU field is unchanged (gate_dc_vs_lookdev PASSES at 0.001 m with its control REJECTED at 9.1 m), but DCCompute initialises lazily on the first GPU dispatch, which needs chunks actually meshing in a running session. Headless has no Vulkan at all, and the gate scene only walks sample_density, so neither exercises it. DO NOT TREAT THIS AS WORKING until a real run prints the atlas line. WHY IT IS HERE. The SSBO at binding 13 must be re-uploaded WHOLE when the field changes — 22.4 M floats / 90 MB for the shipped 122.88 km block — which is what makes a moving streaming window expensive. A texture can be updated a SUB-REGION at a time, so one tile costs ~266 KB. That mechanism is PROVEN end-to-end in hyper-tps scenes/test/gate_texture_atlas.gd (texture_update a small staging texture, then texture_copy it into place, then read it back from a compute shader, with a control that catches a copy which clobbers the atlas). CARRIED ALONGSIDE THE SSBO, NOT INSTEAD OF IT. Both hold the same bytes at the same indices, and the emitted sampler still reads the SSBO. A half-migration is exactly what shipped #55's count/size SIGSEGV, so the swap is a later step that can be proved by comparing the two rather than taken on faith. LINEAR MAPPING, NOT A TILE GRID: index i -> (i % 4096, i / 4096). This step is a transport swap only. Laying the mip pyramid out as 2-D tile rectangles is a LAYOUT change and belongs in its own commit where it can fail on its own. ⚠ THE RISK THIS CARRIES, stated plainly: binding 14 is now part of the uniform set. If texture_create or texture_update fails at runtime, uniform_set_create fails, and then EVERY density dispatch reads nothing — which looks like a wrong field, not like an error. dc_compute.cpp already logs that case loudly; the first real run should be checked for it. WHAT TO LOOK FOR on the next session (dev-run.sh, walk into the diffusion map): DCCompute: atlas 4096xN holds M floats (X MB) <- wanted DCCompute: atlas texture_create FAILED <- broken DCCompute: atlas texture_update FAILED <- broken DCCompute: uniform_set_create FAILED <- broken, and fatal ⚠ TEXTURE_USAGE_CAN_UPDATE_BIT is required by texture_update and its absence is NOT a creation error — texture_create succeeds and the update fails later. It is set here; the gate found that the hard way.
396da976840eac2baae60982804f7b67825adf25
by plankatron <fleeky@gmail.com> · 2026-09-02T17:30:19.000Z

Changed files (3) +96 -0

libdc/include/dc_shader_source.h +12 -0
……@@ -188,6 +188,18 @@ layout(set = 0, binding = 13, std430) readonly buffer CustomDataBuf {
188188 float custom_data[];
189189};
190190
191// binding 14: THE SAME DATA AS A TEXTURE.
192//
193// Declared unconditionally because a uniform set must match the shader's
194// bindings exactly; whether it is READ is chosen by HBLK_USE_ATLAS in the
195// emitted sampler. Carrying both lets the two be compared rather than the swap
196// being taken on faith.
197//
198// Linear index i maps to (i % HBLK_ATLAS_W, i / HBLK_ATLAS_W) — a transport
199// swap, not a layout change, so both hold identical values at identical
200// indices. The generator emits HBLK_ATLAS_W beside the block constants.
201layout(set = 0, binding = 14, r32f) uniform readonly image2D custom_data_tex;
202
191203// --- Mesh SDF helper functions ---
192204
193205float aabb_point_dist_sq(vec3 p, vec3 bmin, vec3 bmax) {
src/dc_compute.cpp +66 -0
……@@ -6,6 +6,8 @@
66#include <godot_cpp/classes/rd_shader_source.hpp>
77#include <godot_cpp/classes/rd_shader_spirv.hpp>
88#include <godot_cpp/classes/rd_uniform.hpp>
9#include <godot_cpp/classes/rd_texture_format.hpp>
10#include <godot_cpp/classes/rd_texture_view.hpp>
911#include <godot_cpp/variant/utility_functions.hpp>
1012
1113#include <cstring>
……@@ -370,9 +372,62 @@ void DCCompute::set_custom_data(const float* data, int n) {
370372 fprintf(stderr, "DCCompute: custom data uploaded %d floats (%zu bytes), range %.0f..%.0f\n",
371373 n, bytes, (double)lo, (double)hi);
372374 }
375 _upload_atlas(data, n);
373376 uniform_set_dirty_ = true;
374377}
375378
379
380// The SAME floats, as a 2-D texture. Linear index i -> (i % W, i / W).
381//
382// A LINEAR MAPPING, NOT A TILE GRID, and deliberately so for this step: it is a
383// pure TRANSPORT swap, so custom_data[i] and the atlas hold identical values at
384// identical indices and the two can be proved equivalent by the existing
385// gate. Laying the mip pyramid out as 2-D tile rectangles is a LAYOUT change
386// and belongs in its own step, where it can fail on its own.
387void DCCompute::_upload_atlas(const float* data, int n) {
388 if (!rd_) return;
389 if (atlas_tex_.is_valid()) { rd_->free_rid(atlas_tex_); atlas_tex_ = RID(); }
390 if (atlas_staging_.is_valid()) { rd_->free_rid(atlas_staging_); atlas_staging_ = RID(); }
391
392 const int W = 4096;
393 int count = (data == nullptr || n <= 0) ? 1 : n;
394 int H = (count + W - 1) / W;
395 atlas_w_ = W; atlas_h_ = H;
396
397 Ref<RDTextureFormat> fmt; fmt.instantiate();
398 fmt->set_format(RenderingDevice::DATA_FORMAT_R32_SFLOAT);
399 fmt->set_width((uint32_t)W); fmt->set_height((uint32_t)H); fmt->set_depth(1);
400 fmt->set_array_layers(1); fmt->set_mipmaps(1);
401 fmt->set_texture_type(RenderingDevice::TEXTURE_TYPE_2D);
402 // ⚠ CAN_UPDATE_BIT is required by texture_update and its ABSENCE IS NOT A
403 // CREATION ERROR — texture_create succeeds and the update fails much later.
404 fmt->set_usage_bits(RenderingDevice::TEXTURE_USAGE_STORAGE_BIT
405 | RenderingDevice::TEXTURE_USAGE_CAN_UPDATE_BIT
406 | RenderingDevice::TEXTURE_USAGE_CAN_COPY_TO_BIT
407 | RenderingDevice::TEXTURE_USAGE_CAN_COPY_FROM_BIT);
408
409 Ref<RDTextureView> view; view.instantiate();
410 atlas_tex_ = rd_->texture_create(fmt, view, TypedArray<PackedByteArray>());
411 if (!atlas_tex_.is_valid()) {
412 fprintf(stderr, "DCCompute: atlas texture_create FAILED (%dx%d)\n", W, H);
413 return;
414 }
415
416 // texture_update is WHOLE-LAYER, so the padded full-size image is written
417 // once here. Per-tile sub-region blits are the point of the texture and
418 // come with the streaming window; this step only proves the transport.
419 PackedByteArray pb;
420 pb.resize((int64_t)W * H * 4);
421 memset(pb.ptrw(), 0, (size_t)W * H * 4);
422 if (data != nullptr && n > 0) memcpy(pb.ptrw(), data, (size_t)n * sizeof(float));
423 if (rd_->texture_update(atlas_tex_, 0, pb) != 0) {
424 fprintf(stderr, "DCCompute: atlas texture_update FAILED\n");
425 return;
426 }
427 fprintf(stderr, "DCCompute: atlas %dx%d holds %d floats (%.1f MB)\n",
428 W, H, count, (double)W * H * 4 / 1048576.0);
429}
430
376431void DCCompute::_rebuild_uniform_set() {
377432 // Skip rebuild if buffers haven't changed (optimization 5)
378433 if (!uniform_set_dirty_ && uniform_set_.is_valid()) return;
……@@ -473,6 +528,16 @@ void DCCompute::_rebuild_uniform_set() {
473528 u13->set_binding(13);
474529 u13->add_id(custom_data_buf_);
475530
531 // binding 14: the SAME data as a texture. A descriptor must point at a live
532 // resource even when the shader never reads it (see the note on binding 13's
533 // buffer), so this is created unconditionally.
534 if (!atlas_tex_.is_valid()) _upload_atlas(nullptr, 0);
535 Ref<RDUniform> u14;
536 u14.instantiate();
537 u14->set_uniform_type(RenderingDevice::UNIFORM_TYPE_IMAGE);
538 u14->set_binding(14);
539 u14->add_id(atlas_tex_);
540
476541 // binding 12: convex-brush planes SSBO
477542 Ref<RDUniform> u12;
478543 u12.instantiate();
……@@ -495,6 +560,7 @@ void DCCompute::_rebuild_uniform_set() {
495560 uniforms.push_back(u11);
496561 uniforms.push_back(u12);
497562 uniforms.push_back(u13);
563 uniforms.push_back(u14);
498564
499565 uniform_set_ = rd_->uniform_set_create(uniforms, shader_, 0);
500566 if (!uniform_set_.is_valid()) {
src/dc_compute.h +18 -0
……@@ -125,6 +125,24 @@ private:
125125 RID edit_buf_;
126126 RID brick_buf_;
127127 RID custom_data_buf_; // baked diffusion custom data; binding 13 in BOTH shaders
128 // ---- ATLAS TRANSPORT for the same data, binding 14 ----------------------
129 //
130 // The SSBO above must be re-uploaded WHOLE when the field changes: 22.4 M
131 // floats / 90 MB for the shipped 122.88 km block. That is what makes a
132 // moving streaming window expensive. A texture can be updated a SUB-REGION
133 // at a time (proved end-to-end in scenes/test/gate_texture_atlas.gd:
134 // texture_update a small staging texture, then texture_copy it into place),
135 // so one tile costs ~266 KB instead of 90 MB.
136 //
137 // Carried ALONGSIDE the SSBO on purpose. Both hold the same bytes and the
138 // shader chooses with HBLK_USE_ATLAS, so the two can be compared against
139 // each other rather than the swap being taken on faith. A half-migration
140 // here is exactly what shipped #55's count/size SIGSEGV.
141 RID atlas_tex_; // R32F, linear index -> (x, y); binding 14
142 RID atlas_staging_; // one row-block, for texture_update + copy
143 int atlas_w_ = 0;
144 int atlas_h_ = 0;
145 void _upload_atlas(const float* data, int n);
128146 int custom_data_n_ = 0;
129147 RID bvh_node_buf_;
130148 RID bvh_tri_buf_;