OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / src / dc_gpu_mesh_pipeline.h

186 lines · c ref:
#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