OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / src / dc_compute.h

197 lines · c ref:
#ifndef DC_COMPUTE_H
#define DC_COMPUTE_H

#include <godot_cpp/classes/rendering_device.hpp>
#include <godot_cpp/classes/rendering_server.hpp>
#include <godot_cpp/classes/rd_shader_source.hpp>
#include <godot_cpp/classes/rd_shader_spirv.hpp>
#include <godot_cpp/classes/rd_uniform.hpp>
#include <godot_cpp/variant/rid.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include "dc_density.h"
#include "dc_density_grid.h"
#include "dc_edit.h"

#include <vector>

class DCGeneratorMeshSDF;   // pointer-only: see bvh_uploaded_for_

using namespace godot;

struct GpuFillRequest;  // forward declaration

class DCCompute {
public:
    DCCompute();
    ~DCCompute();

    // Initialize: create local RenderingDevice and compile shader.
    // MUST be called from the thread that will use the device (thread affinity).
    // Returns true on success.
    bool initialize();

    // Cleanup all GPU resources. Must be called from the same thread as initialize().
    void cleanup();

    // Set a custom GLSL density shader (proc-nodes erosion etc.)
    // Recompiles the pipeline. Pass nullptr to reset to default.
    bool set_custom_shader(const char* glsl);

    // Fill a single density grid using GPU compute.
    void fill_grid(DensityGrid& grid, const DCDensity& density);

    // Prepare buffers for density dispatch (uploads params, creates buffers).
    // Must be called BEFORE compute_list_begin().
    void prepare_density(DensityGrid& grid, const DCDensity& density);

    // Dispatch density fill into an existing compute list (no buffer updates).
    // Call prepare_density() first, BEFORE compute_list_begin().
    bool dispatch_density(int64_t compute_list, int stride);

    // Bake the settled-edit SDF delta into the brick buffer (own submit/sync).
    // Reuses the density pipeline in FLAG_BAKE mode: writes brick_delta[] =
    // apply_settled(base) - base, so base cancels exactly when later sampled.
    // Call after upload_edits(settled) + prepare_density(); then upload_edits(HOT)
    // + set_brick_active(true) before the mesh dispatch.
    void bake_brick(int stride);

    // Toggle settled-brick sampling for the next density/mesh dispatch (FLAG_BRICK).
    void set_brick_active(bool active);

    // Fill multiple grids in a single batched dispatch.
    // All grids are dispatched in one compute list with one submit+sync.
    // Returns true if all succeeded.
    bool fill_grid_batch(std::vector<GpuFillRequest>& batch);

    // What an upload actually managed to fit. THE CAPS DROP THE EXCESS, and
    // before this returned anything they dropped it SILENTLY: `break` at
    // MAX_GPU_EDITS and `continue` at MAX_CONVEX_PLANES, with no error and no
    // count. Terrain then leaks back into the level by an amount that varies
    // with LOD and camera, which is indistinguishable from a meshing bug and is
    // the single worst failure mode in this file.
    //
    // The caller is the one that knows WHICH chunk this was (dc_gpu_thread.h has
    // req.lod_level and req.node_position; upload_edits does not), so the counts
    // are returned rather than reported here.
    struct EditUploadStats {
        int culled = 0;          // what the per-chunk cull handed us
        int uploaded = 0;        // what reached the GPU
        int dropped_cap = 0;     // lost to MAX_GPU_EDITS (tail truncation)
        int dropped_planes = 0;  // convex edits skipped for want of plane budget
        bool complete() const { return dropped_cap == 0 && dropped_planes == 0; }
    };

    // Upload terrain edits to GPU buffer for shader-side evaluation.
    // Pass nullptr or empty vector to clear (edit_count=0).
    // Mesh SDF edits trigger BVH upload to bindings 8-10.
    EditUploadStats upload_edits(const std::vector<TerrainEdit>* edits);

    bool is_available() const { return available_; }
    RenderingDevice* rd() const { return rd_; }

    // Buffer accessors for mesh pipeline (shared across stages)
    RID density_buf() const { return density_buf_; }
    RID params_buf() const { return params_buf_; }
    RID voronoi_buf() const { return voronoi_buf_; }
    RID perm_buf() const { return perm_buf_; }
    RID edit_buf() const { return edit_buf_; }
    RID brick_buf() const { return brick_buf_; }
    RID custom_data_buf() const { return custom_data_buf_; }

    // Noise VOLUMES for texture-fetch generators (GPU Gems 3 ch.1 samples eight
    // 16^3 RGBA volumes instead of computing noise). Placeholders exist from
    // _create_buffers so the descriptors are always live; set_noise_volumes()
    // replaces them with real data.
    RID noise_sampler_linear() const { return noise_sampler_linear_; }
    RID noise_sampler_nearest() const { return noise_sampler_nearest_; }
    RID noise_tex(int i) const { return noise_tex_[i]; }
    // data: 8 volumes of dim^3 RGBA HALF, plain 0-3 then packed 4-7.
    // Pass nullptr to drop back to the placeholders.
    void set_noise_volumes(const void* const* volumes, int dim);
    // Upload a baked custom data block (metres, n*n row-major, row = Z).
    // Pass n = 0 to release it back to the one-float placeholder.
    void set_custom_data(const float* data, int n);
    RID bvh_node_buf() const { return bvh_node_buf_; }
    RID bvh_tri_buf() const { return bvh_tri_buf_; }
    RID bvh_idx_buf() const { return bvh_idx_buf_; }
    RID convex_plane_buf() const { return convex_plane_buf_; }
    RID convex_faceproj_buf() const { return convex_faceproj_buf_; }

private:
    bool available_ = false;
    RenderingDevice* rd_ = nullptr;

    RID shader_;
    RID pipeline_;

    // Buffers
    RID density_buf_;
    RID convex_plane_buf_;  // convex-brush half-space planes (vec4 nx,ny,nz,d); binding 12 (density) / 11 (mesh)
    RID convex_faceproj_buf_;  // per-face projection index (uint), parallel to convex_plane_buf_;
                               // bake_planeset binding 12 (Stage-2 per-face texturing)
    RID nx_buf_, ny_buf_, nz_buf_;
    RID params_buf_;
    RID voronoi_buf_;
    RID perm_buf_;
    RID edit_buf_;
    RID brick_buf_;
    RID custom_data_buf_;          // baked diffusion custom data; binding 13 in BOTH shaders
    RID noise_sampler_linear_;     // edge_intersect bindings 14..21
    RID noise_sampler_nearest_;
    RID noise_tex_[8] = {};
    int noise_dim_ = 0;
    bool sdf_uses_noise_ = false;   // body samples the volumes -> declare + bind 15..22
    void _create_noise_placeholders();
    void _free_noise();
    // ---- ATLAS TRANSPORT for the same data, binding 14 ----------------------
    //
    // The SSBO above must be re-uploaded WHOLE when the field changes: 22.4 M
    // floats / 90 MB for the shipped 122.88 km block. That is what makes a
    // moving streaming window expensive. A texture can be updated a SUB-REGION
    // at a time (proved end-to-end in scenes/test/gate_texture_atlas.gd:
    // texture_update a small staging texture, then texture_copy it into place),
    // so one tile costs ~266 KB instead of 90 MB.
    //
    // Carried ALONGSIDE the SSBO on purpose. Both hold the same bytes and the
    // shader chooses with HBLK_USE_ATLAS, so the two can be compared against
    // each other rather than the swap being taken on faith. A half-migration
    // here is exactly what shipped #55's count/size SIGSEGV.
    RID atlas_tex_;                // R32F, linear index -> (x, y); binding 14
    RID atlas_staging_;            // one row-block, for texture_update + copy
    int atlas_w_ = 0;
    int atlas_h_ = 0;
    void _upload_atlas(const float* data, int n);
    int custom_data_n_ = 0;
    RID bvh_node_buf_;
    RID bvh_tri_buf_;
    // Which mesh SDF the BVH buffers currently hold. upload_edits() runs PER
    // CHUNK (dc_gpu_thread.h:277-282), and it used to re-flatten and re-upload
    // the BVH on every one of those calls. That is affordable for the mesh this
    // path was built for — a fandisk, a few thousand triangles — and ruinous for
    // the §5a.2 level shell at 563544, which is ~72 MB of node+triangle data
    // rebuilt on the CPU and pushed to the GPU once per overlapping chunk, tearing
    // down the density uniform set each time. Chunks near the level never finished
    // meshing, so they rendered terrain that had never had the subtract applied —
    // reported as "the terrain was inside the start.map".
    //
    // The mesh does not change between chunks, so it is uploaded once and this
    // remembers which one. Compared by POINTER: DCGeneratorMeshSDF instances are
    // owned by the edit list and a new stamp is a new allocation, so a changed
    // mesh is a changed pointer.
    const DCGeneratorMeshSDF* bvh_uploaded_for_ = nullptr;
    RID bvh_idx_buf_;
    RID uniform_set_;

    int current_stride_ = 0;
    bool uniform_set_dirty_ = true;  // skip rebuild when buffers haven't changed

    void _create_buffers(int stride);
    void _destroy_buffers();
    void _rebuild_uniform_set();
    void _upload_params(const DensityGrid& grid, const DCDensity& density);
    void _upload_flags(int32_t flags);  // patch the trailing GpuParams.flags in place
    void _dispatch_and_readback(DensityGrid& grid);
};

#endif // DC_COMPUTE_H