#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