#ifndef DC_GPU_MESH_PIPELINE_H
#define DC_GPU_MESH_PIPELINE_H
// GPU Dual Contouring mesh pipeline.
//
// Runs 10 compute shader stages to generate triangle meshes directly
// on the GPU, eliminating the CPU octree + DC + smooth pipeline.
//
// Pipeline: density → edge_classify → prefix_scan → stream_compact →
// edge_intersect → voxel_classify → prefix_scan → stream_compact →
// qef_solve → quad_emit → readback
//
// Runs on the same RenderingDevice as DCCompute (thread affinity).
#include <godot_cpp/classes/rendering_device.hpp>
#include <godot_cpp/variant/rid.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include "dc_contour.h" // DCMeshData
#include <string>
using namespace godot;
// Buffers owned by DCCompute that the mesh pipeline needs to read.
struct DensityBuffers {
RID density; // binding 0: density grid (33^3 floats)
RID params; // binding 4: Params UBO (noise params, std140)
RID voronoi; // binding 5: Voronoi cell data
RID perm; // binding 6: Permutation tables
RID edit; // binding 7: CSG edit list
RID bvh_nodes; // binding 8: BVH nodes (mesh SDF)
RID bvh_tris; // binding 9: BVH triangles
RID bvh_indices; // binding 10: BVH triangle indices
RID brick; // edge_intersect binding 10: settled-edit SDF delta brick
RID convex_planes; // edge_intersect binding 11: convex-brush half-space planes
RID convex_faceproj; // bake_planeset binding 12: per-face projection index (parallel to convex_planes)
RID custom_data; // binding 13 in BOTH density_fill and edge_intersect: baked custom data
// Noise VOLUMES for texture-fetch generators (GPU Gems 3 ch.1 samples eight
// 16^3 volumes rather than computing noise). Bound as SAMPLER_WITH_TEXTURE
// at bindings 14..21 of edge_intersect, which unlike a buffer descriptor
// takes TWO ids: the sampler, then the texture.
//
// Like custom_data, these must point at LIVE objects even when the injected
// SDF body never reads them -- a descriptor in the set cannot be invalid --
// so DCCompute creates 1^3 placeholders when no generator supplies volumes.
RID noise_sampler_linear; // NLQ/NMQ: filter_linear, repeat
RID noise_sampler_nearest; // NHQ: filter_nearest, repeat (packed volumes)
RID noise_tex[8]; // 0-3 plain, 4-7 packed
};
// A combined image sampler binding. Kept separate from the buffer bindings
// because it needs two ids and a different uniform type.
struct SamplerBinding {
int binding;
RID sampler;
RID texture;
};
class DCGpuMeshPipeline {
public:
DCGpuMeshPipeline() = default;
~DCGpuMeshPipeline();
// Initialize: compile shaders and create pipelines.
// Must be called on the GPU thread (same RD as DCCompute).
bool initialize(RenderingDevice* rd);
// Cleanup all GPU resources.
void cleanup();
// Set custom SDF body for edge_intersect shader (proc-nodes etc.).
// Pass nullptr to reset to default noise.
bool set_custom_sdf(const char* sdf_body);
// Generate mesh from density buffer.
// density_buf must be filled by DCCompute::fill_grid() first.
// grid_dims = voxel grid dimension (e.g., 32).
// cell_origin, voxel_size = world-space chunk parameters.
// out_mesh receives positions, normals, and indices.
// Returns true on success, false if pipeline failed or chunk is empty.
bool generate_mesh(const DensityBuffers& bufs, int grid_dims,
const float cell_origin[3], float voxel_size,
uint32_t mesh_quality_flags,
DCMeshData& out_mesh);
// Split version: prepare buffers, dispatch stages, then readback separately.
// prepare_mesh() must be called BEFORE compute_list_begin() (does buffer_update).
// dispatch_mesh_stages() runs inside an active compute list (no buffer updates).
void prepare_mesh(int grid_dims, const DensityBuffers& bufs);
bool dispatch_mesh_stages(int64_t compute_list,
const DensityBuffers& bufs, int grid_dims,
const float cell_origin[3], float voxel_size,
uint32_t mesh_quality_flags, bool has_edits = true);
bool readback_mesh(DCMeshData& out_mesh);
bool is_available() const { return available_; }
private:
RenderingDevice* rd_ = nullptr;
bool available_ = false;
// --- Shader + Pipeline RIDs (compiled once, or on SDF change) ---
struct ShaderPipeline {
RID shader;
RID pipeline;
};
ShaderPipeline sp_edge_classify_;
ShaderPipeline sp_scan_blocks_;
ShaderPipeline sp_scan_single_;
ShaderPipeline sp_scan_propagate_;
ShaderPipeline sp_compact_index_;
ShaderPipeline sp_prepare_dispatch_;
ShaderPipeline sp_edge_intersect_; // recompiled when SDF changes
ShaderPipeline sp_vertex_polish_; // stage 9: clamped gradient descent
ShaderPipeline sp_voxel_classify_;
ShaderPipeline sp_qef_solve_;
ShaderPipeline sp_quad_emit_;
ShaderPipeline sp_bake_planeset_; // plane-set brick bake (per cell, before qef_solve)
// --- Intermediate Buffers (allocated per grid_dims, reused) ---
RID edge_flags_buf_; // V^3 * 3 uints
RID edge_scan_buf_; // V^3 * 3 uints (prefix sum)
RID active_edges_buf_; // V^3 * 3 uints (compacted indices)
RID edge_data_buf_; // active * 8 floats (worst case)
RID voxel_flags_buf_; // V^3 uints
RID voxel_scan_buf_; // V^3 uints (prefix sum)
RID active_voxels_buf_; // V^3 uints (compacted indices)
RID vertex_buf_; // V^3 * 12 floats (QEF output)
RID index_buf_; // V^3 * 3 * 6 uints (worst case quads)
RID counts_buf_; // 4 uints: [edge_count, index_count, voxel_count, pad]
RID dispatch_args_buf_; // 8 uints: 2 sets of (x, y, z) + pad
RID scan_block_sums_buf_; // ceil(N/1024) uints for multi-level scan
RID scan_block_sums_scanned_buf_; // same size, scanned sums
RID planeset_buf_; // V^3 * 64 bytes: per-cell baked plane-set (cl_count+material+3 planes)
int current_grid_dims_ = 0;
std::string current_sdf_body_;
// Cached uniform sets (rebuilt in prepare_mesh when buffers change)
RID us_edge_classify_, us_compact_edge_, us_prepare_edge_;
RID us_edge_intersect_;
RID us_voxel_classify_, us_compact_voxel_, us_prepare_voxel_;
RID us_qef_solve_, us_vertex_polish_, us_quad_emit_;
RID us_bake_planeset_;
// Scan uniform sets (edge scan: 3, voxel scan: 3)
RID us_scan_edge_[3], us_scan_voxel_[3];
// 2a: the 15 uniform sets bind only stable buffers (pipeline intermediates,
// which change only on grid_dims; and DCCompute density/params/voronoi/perm/
// edit, all alloc-once — edit is buffer_update'd in place, never realloc'd),
// so they are built once and reused across chunks. Invalidated (rebuilt) only
// on a grid_dims change (_free_uniform_sets via _free_buffers) or an
// edge_intersect shader recompile (_compile_edge_intersect).
bool uniform_sets_valid_ = false;
void _build_uniform_sets(const DensityBuffers& bufs);
void _free_uniform_sets();
// Helpers
ShaderPipeline _compile_shader(const char* glsl_source);
void _free_shader_pipeline(ShaderPipeline& sp);
bool _compile_all_shaders();
bool _compile_edge_intersect(const char* sdf_body);
void _allocate_buffers(int grid_dims);
void _free_buffers();
// Build edge_intersect GLSL by injecting SDF body into template
std::string _build_edge_intersect_glsl(const char* sdf_body);
// Prefix scan (3-pass: blocks → single → propagate)
void _prefix_scan(int64_t compute_list, RID input_buf, RID output_buf, int N);
// Create a uniform set for a specific dispatch stage
// True when the compiled edge_intersect declares bindings 14..21, i.e. the
// injected SDF body samples the noise volumes.
bool sdf_uses_noise_ = false;
std::vector<SamplerBinding> _noise_sampler_bindings(const DensityBuffers& bufs) const;
RID _make_uniform_set(const RID& shader,
const std::vector<std::pair<int, RID>>& bindings,
bool is_ubo_binding_4 = false,
const std::vector<SamplerBinding>& samplers = {});
};
#endif // DC_GPU_MESH_PIPELINE_H