| … | … | @@ -154,6 +154,10 @@ layout(set = 0, binding = 10, std430) readonly buffer BrickBuf { float brick_del |
| 154 | 154 | // reference to it. |
| 155 | 155 | layout(set = 0, binding = 13, std430) readonly buffer CustomDataBuf { float custom_data[]; }; |
| 156 | 156 | |
| 157 | // GENERATOR_SAMPLER_BINDINGS | |
| 158 | // Bindings 14..21 (eight 16^3 noise volumes) are injected HERE, and only when | |
| 159 | // the generator body actually samples them -- see _build_edge_intersect_glsl. | |
| 160 | ||
| 157 | 161 | |
| 158 | 162 | layout(push_constant) uniform PushConstants { |
| 159 | 163 | vec3 cell_origin; |
| … | … | @@ -9,6 +9,7 @@ |
| 9 | 9 | #include <godot_cpp/classes/rd_uniform.hpp> |
| 10 | 10 | #include <godot_cpp/classes/rd_texture_format.hpp> |
| 11 | 11 | #include <godot_cpp/classes/rd_texture_view.hpp> |
| 12 | #include <godot_cpp/classes/rd_sampler_state.hpp> | |
| 12 | 13 | #include <godot_cpp/variant/utility_functions.hpp> |
| 13 | 14 | |
| 14 | 15 | #include <cstring> |
| … | … | @@ -189,6 +190,9 @@ void DCCompute::cleanup() { |
| 189 | 190 | // The custom data outlives stride changes by design (see _destroy_buffers), |
| 190 | 191 | // so this is the only place it is released. |
| 191 | 192 | if (custom_data_buf_.is_valid()) { rd_->free_rid(custom_data_buf_); custom_data_buf_ = RID(); custom_data_n_ = 0; } |
| 193 | _free_noise(); | |
| 194 | if (noise_sampler_linear_.is_valid()) { rd_->free_rid(noise_sampler_linear_); noise_sampler_linear_ = RID(); } | |
| 195 | if (noise_sampler_nearest_.is_valid()) { rd_->free_rid(noise_sampler_nearest_); noise_sampler_nearest_ = RID(); } | |
| 192 | 196 | // The atlas pair. Textures are RIDs like any buffer and cleanup() did not |
| 193 | 197 | // free them — the first real run reported |
| 194 | 198 | // WARNING: 1 RID of type "Texture" was leaked. |
| … | … | @@ -277,6 +281,8 @@ void DCCompute::_create_buffers(int stride) { |
| 277 | 281 | custom_data_buf_ = rd_->storage_buffer_create(4, hz); |
| 278 | 282 | } |
| 279 | 283 | |
| 284 | _create_noise_placeholders(); | |
| 285 | ||
| 280 | 286 | // Mesh BVH buffers: start with small empties (resized on first mesh upload) |
| 281 | 287 | // Binding 8: BVH nodes (16-byte header + nodes) |
| 282 | 288 | PackedByteArray bvh_zero; |
| … | … | @@ -357,6 +363,70 @@ void DCCompute::_destroy_buffers() { |
| 357 | 363 | // Upload a baked custom data block. Reallocates, so the uniform set is |
| 358 | 364 | // invalidated -- a storage buffer cannot be resized in place and the old RID |
| 359 | 365 | // stays referenced by the set until it is rebuilt. |
| 366 | // A sampler descriptor, like a buffer descriptor, MUST point at a live object | |
| 367 | // even when the injected SDF body never reads it. Eight 1^3 volumes cost | |
| 368 | // nothing and keep every uniform set valid for generators that use no noise. | |
| 369 | void DCCompute::_create_noise_placeholders() { | |
| 370 | if (!noise_sampler_linear_.is_valid()) { | |
| 371 | for (int pass = 0; pass < 2; pass++) { | |
| 372 | Ref<RDSamplerState> ss; ss.instantiate(); | |
| 373 | RenderingDevice::SamplerFilter f = pass == 0 | |
| 374 | ? RenderingDevice::SAMPLER_FILTER_LINEAR | |
| 375 | : RenderingDevice::SAMPLER_FILTER_NEAREST; | |
| 376 | ss->set_min_filter(f); | |
| 377 | ss->set_mag_filter(f); | |
| 378 | ss->set_repeat_u(RenderingDevice::SAMPLER_REPEAT_MODE_REPEAT); | |
| 379 | ss->set_repeat_v(RenderingDevice::SAMPLER_REPEAT_MODE_REPEAT); | |
| 380 | ss->set_repeat_w(RenderingDevice::SAMPLER_REPEAT_MODE_REPEAT); | |
| 381 | RID r = rd_->sampler_create(ss); | |
| 382 | if (pass == 0) noise_sampler_linear_ = r; else noise_sampler_nearest_ = r; | |
| 383 | } | |
| 384 | } | |
| 385 | if (noise_tex_[0].is_valid()) return; | |
| 386 | Ref<RDTextureFormat> fmt; fmt.instantiate(); | |
| 387 | fmt->set_format(RenderingDevice::DATA_FORMAT_R32G32B32A32_SFLOAT); | |
| 388 | fmt->set_width(1); fmt->set_height(1); fmt->set_depth(1); | |
| 389 | fmt->set_array_layers(1); fmt->set_mipmaps(1); | |
| 390 | fmt->set_texture_type(RenderingDevice::TEXTURE_TYPE_3D); | |
| 391 | fmt->set_usage_bits(RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT | |
| 392 | | RenderingDevice::TEXTURE_USAGE_CAN_UPDATE_BIT); | |
| 393 | Ref<RDTextureView> view; view.instantiate(); | |
| 394 | PackedByteArray zero; zero.resize(16); memset(zero.ptrw(), 0, 16); | |
| 395 | TypedArray<PackedByteArray> data; data.push_back(zero); | |
| 396 | for (int i = 0; i < 8; i++) noise_tex_[i] = rd_->texture_create(fmt, view, data); | |
| 397 | noise_dim_ = 1; | |
| 398 | } | |
| 399 | ||
| 400 | void DCCompute::_free_noise() { | |
| 401 | for (int i = 0; i < 8; i++) { | |
| 402 | if (noise_tex_[i].is_valid()) { rd_->free_rid(noise_tex_[i]); noise_tex_[i] = RID(); } | |
| 403 | } | |
| 404 | noise_dim_ = 0; | |
| 405 | } | |
| 406 | ||
| 407 | void DCCompute::set_noise_volumes(const float* const* volumes, int dim) { | |
| 408 | if (rd_ == nullptr) return; | |
| 409 | _free_noise(); | |
| 410 | if (volumes == nullptr || dim <= 0) { _create_noise_placeholders(); return; } | |
| 411 | ||
| 412 | Ref<RDTextureFormat> fmt; fmt.instantiate(); | |
| 413 | fmt->set_format(RenderingDevice::DATA_FORMAT_R32G32B32A32_SFLOAT); | |
| 414 | fmt->set_width(dim); fmt->set_height(dim); fmt->set_depth(dim); | |
| 415 | fmt->set_array_layers(1); fmt->set_mipmaps(1); | |
| 416 | fmt->set_texture_type(RenderingDevice::TEXTURE_TYPE_3D); | |
| 417 | fmt->set_usage_bits(RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT | |
| 418 | | RenderingDevice::TEXTURE_USAGE_CAN_UPDATE_BIT); | |
| 419 | Ref<RDTextureView> view; view.instantiate(); | |
| 420 | const int64_t n_bytes = int64_t(dim) * dim * dim * 4 * 4; | |
| 421 | for (int i = 0; i < 8; i++) { | |
| 422 | PackedByteArray b; b.resize(n_bytes); | |
| 423 | memcpy(b.ptrw(), volumes[i], size_t(n_bytes)); | |
| 424 | TypedArray<PackedByteArray> data; data.push_back(b); | |
| 425 | noise_tex_[i] = rd_->texture_create(fmt, view, data); | |
| 426 | } | |
| 427 | noise_dim_ = dim; | |
| 428 | } | |
| 429 | ||
| 360 | 430 | void DCCompute::set_custom_data(const float* data, int n) { |
| 361 | 431 | if (!rd_) return; |
| 362 | 432 | if (custom_data_buf_.is_valid()) { rd_->free_rid(custom_data_buf_); custom_data_buf_ = RID(); } |
| … | … | @@ -97,6 +97,17 @@ public: |
| 97 | 97 | RID edit_buf() const { return edit_buf_; } |
| 98 | 98 | RID brick_buf() const { return brick_buf_; } |
| 99 | 99 | RID custom_data_buf() const { return custom_data_buf_; } |
| 100 | ||
| 101 | // Noise VOLUMES for texture-fetch generators (GPU Gems 3 ch.1 samples eight | |
| 102 | // 16^3 RGBA volumes instead of computing noise). Placeholders exist from | |
| 103 | // _create_buffers so the descriptors are always live; set_noise_volumes() | |
| 104 | // replaces them with real data. | |
| 105 | RID noise_sampler_linear() const { return noise_sampler_linear_; } | |
| 106 | RID noise_sampler_nearest() const { return noise_sampler_nearest_; } | |
| 107 | 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. | |
| 109 | // Pass nullptr to drop back to the placeholders. | |
| 110 | void set_noise_volumes(const float* const* volumes, int dim); | |
| 100 | 111 | // Upload a baked custom data block (metres, n*n row-major, row = Z). |
| 101 | 112 | // Pass n = 0 to release it back to the one-float placeholder. |
| 102 | 113 | void set_custom_data(const float* data, int n); |
| … | … | @@ -125,6 +136,12 @@ private: |
| 125 | 136 | RID edit_buf_; |
| 126 | 137 | RID brick_buf_; |
| 127 | 138 | RID custom_data_buf_; // baked diffusion custom data; binding 13 in BOTH shaders |
| 139 | RID noise_sampler_linear_; // edge_intersect bindings 14..21 | |
| 140 | RID noise_sampler_nearest_; | |
| 141 | RID noise_tex_[8] = {}; | |
| 142 | int noise_dim_ = 0; | |
| 143 | void _create_noise_placeholders(); | |
| 144 | void _free_noise(); | |
| 128 | 145 | // ---- ATLAS TRANSPORT for the same data, binding 14 ---------------------- |
| 129 | 146 | // |
| 130 | 147 | // The SSBO above must be re-uploaded WHOLE when the field changes: 22.4 M |
| … | … | @@ -248,6 +248,29 @@ std::string DCGpuMeshPipeline::_build_edge_intersect_glsl(const char* sdf_body) |
| 248 | 248 | body = noise_sdf; |
| 249 | 249 | } |
| 250 | 250 | |
| 251 | // Noise volume samplers, only if the body samples them. A declared-but- | |
| 252 | // unused binding can be optimised away, and then the uniform set supplies a | |
| 253 | // descriptor the shader does not reflect and creation fails. | |
| 254 | { | |
| 255 | const std::string smarker = "// GENERATOR_SAMPLER_BINDINGS"; | |
| 256 | size_t spos = tmpl.find(smarker); | |
| 257 | if (spos != std::string::npos) { | |
| 258 | std::string decls; | |
| 259 | if (body.find("noiseVol") != std::string::npos) { | |
| 260 | for (int i = 0; i < 4; i++) { | |
| 261 | decls += "layout(set = 0, binding = " + std::to_string(14 + i) | |
| 262 | + ") uniform sampler3D noiseVol" + std::to_string(i) + ";\n"; | |
| 263 | } | |
| 264 | for (int i = 0; i < 4; i++) { | |
| 265 | decls += "layout(set = 0, binding = " + std::to_string(18 + i) | |
| 266 | + ") uniform sampler3D packedNoiseVol" + std::to_string(i) + ";\n"; | |
| 267 | } | |
| 268 | } | |
| 269 | tmpl.replace(spos, smarker.length(), decls); | |
| 270 | } | |
| 271 | } | |
| 272 | sdf_uses_noise_ = body.find("noiseVol") != std::string::npos; | |
| 273 | ||
| 251 | 274 | // Replace the marker with the SDF body |
| 252 | 275 | const std::string marker = "// GENERATOR_SDF_BODY"; |
| 253 | 276 | size_t pos = tmpl.find(marker); |
| … | … | @@ -362,9 +385,24 @@ void DCGpuMeshPipeline::_free_buffers() { |
| 362 | 385 | // Uniform set helper |
| 363 | 386 | // ------------------------------------------------------------------------- |
| 364 | 387 | |
| 388 | // Bindings 14..21 of edge_intersect: the eight noise volumes, plain 0-3 with a | |
| 389 | // linear sampler and packed 4-7 with a nearest one (the packed volumes carry | |
| 390 | // neighbour values in gba and must NOT be hardware-filtered). | |
| 391 | std::vector<SamplerBinding> DCGpuMeshPipeline::_noise_sampler_bindings( | |
| 392 | const DensityBuffers& bufs) const { | |
| 393 | std::vector<SamplerBinding> out; | |
| 394 | if (!sdf_uses_noise_) return out; // template declared none; supply none | |
| 395 | for (int i = 0; i < 8; i++) { | |
| 396 | RID smp = (i < 4) ? bufs.noise_sampler_linear : bufs.noise_sampler_nearest; | |
| 397 | out.push_back({14 + i, smp, bufs.noise_tex[i]}); | |
| 398 | } | |
| 399 | return out; | |
| 400 | } | |
| 401 | ||
| 365 | 402 | RID DCGpuMeshPipeline::_make_uniform_set(const RID& shader, |
| 366 | 403 | const std::vector<std::pair<int, RID>>& bindings, |
| 367 | bool is_ubo_binding_4) { | |
| 404 | bool is_ubo_binding_4, | |
| 405 | const std::vector<SamplerBinding>& samplers) { | |
| 368 | 406 | TypedArray<RDUniform> uniforms; |
| 369 | 407 | for (auto& [binding, rid] : bindings) { |
| 370 | 408 | Ref<RDUniform> u; |
| … | … | @@ -379,6 +417,17 @@ RID DCGpuMeshPipeline::_make_uniform_set(const RID& shader, |
| 379 | 417 | u->add_id(rid); |
| 380 | 418 | uniforms.push_back(u); |
| 381 | 419 | } |
| 420 | // Combined image samplers. Order matters: sampler id FIRST, then texture. | |
| 421 | for (auto& sb : samplers) { | |
| 422 | if (!sb.sampler.is_valid() || !sb.texture.is_valid()) continue; | |
| 423 | Ref<RDUniform> u; | |
| 424 | u.instantiate(); | |
| 425 | u->set_uniform_type(RenderingDevice::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE); | |
| 426 | u->set_binding(sb.binding); | |
| 427 | u->add_id(sb.sampler); | |
| 428 | u->add_id(sb.texture); | |
| 429 | uniforms.push_back(u); | |
| 430 | } | |
| 382 | 431 | return rd_->uniform_set_create(uniforms, shader, 0); |
| 383 | 432 | } |
| 384 | 433 | |
| … | … | @@ -496,7 +545,7 @@ void DCGpuMeshPipeline::_build_uniform_sets(const DensityBuffers& bufs) { |
| 496 | 545 | {4, bufs.params}, {5, bufs.voronoi}, {6, bufs.perm}, {7, bufs.edit}, |
| 497 | 546 | {8, active_edges_buf_}, {9, counts_buf_}, {10, bufs.brick}, |
| 498 | 547 | {11, bufs.convex_planes}, {13, bufs.custom_data} |
| 499 | }, true); | |
| 548 | }, true, _noise_sampler_bindings(bufs)); | |
| 500 | 549 | |
| 501 | 550 | // Stage 7: voxel_classify |
| 502 | 551 | us_voxel_classify_ = _make_uniform_set(sp_voxel_classify_.shader, { |
| … | … | @@ -35,6 +35,26 @@ struct DensityBuffers { |
| 35 | 35 | RID convex_planes; // edge_intersect binding 11: convex-brush half-space planes |
| 36 | 36 | RID convex_faceproj; // bake_planeset binding 12: per-face projection index (parallel to convex_planes) |
| 37 | 37 | RID custom_data; // binding 13 in BOTH density_fill and edge_intersect: baked custom data |
| 38 | ||
| 39 | // Noise VOLUMES for texture-fetch generators (GPU Gems 3 ch.1 samples eight | |
| 40 | // 16^3 volumes rather than computing noise). Bound as SAMPLER_WITH_TEXTURE | |
| 41 | // at bindings 14..21 of edge_intersect, which unlike a buffer descriptor | |
| 42 | // takes TWO ids: the sampler, then the texture. | |
| 43 | // | |
| 44 | // Like custom_data, these must point at LIVE objects even when the injected | |
| 45 | // SDF body never reads them -- a descriptor in the set cannot be invalid -- | |
| 46 | // so DCCompute creates 1^3 placeholders when no generator supplies volumes. | |
| 47 | RID noise_sampler_linear; // NLQ/NMQ: filter_linear, repeat | |
| 48 | RID noise_sampler_nearest; // NHQ: filter_nearest, repeat (packed volumes) | |
| 49 | RID noise_tex[8]; // 0-3 plain, 4-7 packed | |
| 50 | }; | |
| 51 | ||
| 52 | // A combined image sampler binding. Kept separate from the buffer bindings | |
| 53 | // because it needs two ids and a different uniform type. | |
| 54 | struct SamplerBinding { | |
| 55 | int binding; | |
| 56 | RID sampler; | |
| 57 | RID texture; | |
| 38 | 58 | }; |
| 39 | 59 | |
| 40 | 60 | class DCGpuMeshPipeline { |
| … | … | @@ -151,9 +171,15 @@ private: |
| 151 | 171 | void _prefix_scan(int64_t compute_list, RID input_buf, RID output_buf, int N); |
| 152 | 172 | |
| 153 | 173 | // Create a uniform set for a specific dispatch stage |
| 174 | // True when the compiled edge_intersect declares bindings 14..21, i.e. the | |
| 175 | // injected SDF body samples the noise volumes. | |
| 176 | bool sdf_uses_noise_ = false; | |
| 177 | std::vector<SamplerBinding> _noise_sampler_bindings(const DensityBuffers& bufs) const; | |
| 178 | ||
| 154 | 179 | RID _make_uniform_set(const RID& shader, |
| 155 | 180 | const std::vector<std::pair<int, RID>>& bindings, |
| 156 | bool is_ubo_binding_4 = false); | |
| 181 | bool is_ubo_binding_4 = false, | |
| 182 | const std::vector<SamplerBinding>& samplers = {}); | |
| 157 | 183 | }; |
| 158 | 184 | |
| 159 | 185 | #endif // DC_GPU_MESH_PIPELINE_H |
| … | … | @@ -407,6 +407,9 @@ private: |
| 407 | 407 | bufs.bvh_tris = compute_.bvh_tri_buf(); |
| 408 | 408 | bufs.bvh_indices = compute_.bvh_idx_buf(); |
| 409 | 409 | bufs.custom_data = compute_.custom_data_buf(); |
| 410 | bufs.noise_sampler_linear = compute_.noise_sampler_linear(); | |
| 411 | bufs.noise_sampler_nearest = compute_.noise_sampler_nearest(); | |
| 412 | for (int ni = 0; ni < 8; ni++) bufs.noise_tex[ni] = compute_.noise_tex(ni); | |
| 410 | 413 | mesh_pipeline_.prepare_mesh(grid_dims, bufs); |
| 411 | 414 | |
| 412 | 415 | // Single compute list: density + mesh pipeline |
| … | … | @@ -1548,10 +1548,17 @@ void DCTerrainGeneratorProcNodes::_recompile_glsl() { |
| 1548 | 1548 | + String::num(submarine_buffer_end_, 3) + ", sea_depth);\n" |
| 1549 | 1549 | " if (sub_w > 0.0) {\n" |
| 1550 | 1550 | " float sub_f = 1.0 / " + String::num(submarine_wavelength_, 4) + ";\n" |
| 1551 | // AMPLITUDE IS CAPPED BY THE WATER OVER IT. A 1500 m feature in 400 m | |
| 1552 | // of water pushes the seabed straight through the surface and puts an | |
| 1553 | // island where there should be a reef. Half the local depth is the | |
| 1554 | // most that can be added without breaching, and it makes the caves | |
| 1555 | // scale with the basin they are in -- big ones offshore, gentle relief | |
| 1556 | // in the shallows, which is what a real seabed does. | |
| 1557 | " float sub_a = min(" + String::num(submarine_amp_, 4) | |
| 1558 | + ", max(sea_depth, 0.0) * 0.5);\n" | |
| 1551 | 1559 | " d += ps_fbm_noise_3d(x * sub_f, y * sub_f, z * sub_f, " |
| 1552 | 1560 | + String::num_int64(submarine_octaves_) + ", " |
| 1553 | + String::num(submarine_roughness_, 4) + ", 2.0) * " | |
| 1554 | + String::num(submarine_amp_, 4) + " * sub_w;\n" | |
| 1561 | + String::num(submarine_roughness_, 4) + ", 2.0) * sub_a * sub_w;\n" | |
| 1555 | 1562 | " }\n" : String()) |
| 1556 | 1563 | + String(" // Clip: solid below floor_y, air above ceiling_y.\n" |
| 1557 | 1564 | " d = max(d, (") + floor_str + " - y) * 2.0);\n" |