OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / libdc / include / dc_shader_source.h

783 lines · c ref:
#ifndef DC_SHADER_SOURCE_H
#define DC_SHADER_SOURCE_H

// =========================================================================
// The density compute shader's GLSL source, as plain C strings.
//
// WHY THIS IS ITS OWN FILE, and godot-free.
//
// It used to live in dc_compute.cpp, which pulls in godot_cpp for
// RDShaderSource / RenderingDevice. Nothing about the SOURCE needs Godot —
// only its COMPILATION does — but living in that translation unit meant the
// only way to run these shaders was to boot the engine. So the GPU pipeline
// had no oracle: every question about it (per-stage cost, occupancy, whether
// the mesh stage and the density stage agree) could only be answered by
// launching the game and looking, which is why the one performance audit we
// have (docs/plans/2026-03-24-gpu-pipeline-optimization-clas-spec.md, "the
// GPU is 99% idle") records a conclusion and no method.
//
// THE INVARIANT THIS FILE EXISTS TO HOLD:
//
//   The oracle and the engine compile the SAME STRING.
//
// Not a copy kept in step by hand. A GPU oracle with its own GLSL certifies a
// shader the game does not run, which is the gated-oracle failure hyper-games
// #43 §6 paid four rounds for — a test whose subject is not the shipping
// artifact proves only that the test is self-consistent. Anything that needs
// this source includes this header; there is one copy and it is this one.
//
// Assembly is HEADER + <body> + MAIN, where <body> defines
//   float evaluate_density(float x, float y, float z)
// and is either DENSITY_GLSL_BODY (built-in terrain) or a custom generator's
// (proc-nodes). HEADER fixes the buffer layout and MAIN the dispatch logic, so
// a custom body cannot change either — that is the whole point of the split.
// =========================================================================

#include <string>

// -------------------------------------------------------------------------
// Embedded GLSL shader source — split into HEADER / BODY / MAIN
// so custom shaders (proc-nodes) can replace the body while keeping
// the same buffer layout and dispatch logic.
// -------------------------------------------------------------------------

// HEADER: version, layouts, uniform blocks.
// Custom shaders are sandwiched between HEADER and MAIN.
static const char* DENSITY_GLSL_HEADER = R"(
#version 450

layout(local_size_x = 8, local_size_y = 8, local_size_z = 8) in;

layout(set = 0, binding = 0, std430) buffer DensityBuf  { float densities[]; };
layout(set = 0, binding = 1, std430) buffer NormalXBuf   { float normal_x[];  };
layout(set = 0, binding = 2, std430) buffer NormalYBuf   { float normal_y[];  };
layout(set = 0, binding = 3, std430) buffer NormalZBuf   { float normal_z[];  };

layout(set = 0, binding = 4, std140) uniform Params {
    int   grid_stride;
    float voxel_scale;
    float origin_x;
    float origin_y;
    float origin_z;
    float primary_frequency;
    float primary_strength;
    float tunnel_frequency;
    float tunnel_strength;
    float tunnel_radius;
    float ridge_frequency;
    float ridge_strength;
    float warp_frequency;
    float warp_strength;
    float detail_frequency;
    float detail_strength;
    float ground_bias;
    float solid_floor_depth;
    float sky_height;
    float flat_center_radius;
    float floor_strength;
    float floor_height_min;
    float floor_height_max;
    float room_height;
    float terrain_size;
    int primary_octaves;
    int tunnel_octaves;
    int ridge_octaves;
    int warp_octaves;
    int detail_octaves;
    int compute_normals;
    int seed;
    int voronoi_cell_count;
    int use_noise;
    int spherical;
    float planet_radius;
    float planet_center_x;
    float planet_center_y;
    float planet_center_z;
    float island_radius;
    int flags;            // bit0 = bake brick, bit1 = sample brick
};

#define FLAG_BAKE  1
#define FLAG_BRICK 2

layout(set = 0, binding = 5, std430) buffer VoronoiBuf {
    int   voronoi_count;
    float voronoi_data[];
};

layout(set = 0, binding = 6, std430) buffer PermBuf {
    int perm_tables[];
};

struct GpuEdit {
    uint  edit_type;    // 0=sphere_sub,1=sphere_add,2=box_sub,3=box_add,4=mesh_sub,5=mesh_add,6=sphere_hard_sub,7=sphere_hard_add
    float pos_x, pos_y, pos_z;
    float half_x, half_y, half_z;  // mesh: half_x = scale
    float radius;
    float strength;
    uint  _pad0, _pad1, _pad2;
};

layout(set = 0, binding = 7, std430) readonly buffer EditBuf {
    uint edit_count;
    uint _edit_pad0, _edit_pad1, _edit_pad2;
    GpuEdit gpu_edits[];
};

// --- Mesh SDF BVH structures (bindings 8-10) ---

struct GpuBVHNode {
    float aabb_min_x, aabb_min_y, aabb_min_z;
    uint  left_or_start;  // interior: left child, leaf: tri_start
    float aabb_max_x, aabb_max_y, aabb_max_z;
    uint  count;          // 0 = interior (right = left+1), >0 = leaf tri_count
};

struct GpuTriangle {
    float v0x, v0y, v0z;
    float v1x, v1y, v1z;
    float v2x, v2y, v2z;
    float fnx, fny, fnz;
    float en0x, en0y, en0z;
    float en1x, en1y, en1z;
    float en2x, en2y, en2z;
    float vn0x, vn0y, vn0z;
    float vn1x, vn1y, vn1z;
    float vn2x, vn2y, vn2z;
    float _tri_pad0, _tri_pad1;
};

layout(set = 0, binding = 8, std430) readonly buffer MeshBVHBuf {
    uint mesh_node_count;
    uint _mbvh_pad0, _mbvh_pad1, _mbvh_pad2;
    GpuBVHNode mesh_nodes[];
};

layout(set = 0, binding = 9, std430) readonly buffer MeshTriBuf {
    GpuTriangle mesh_triangles[];
};

layout(set = 0, binding = 10, std430) readonly buffer MeshTriIdxBuf {
    uint mesh_tri_indices[];
};

// Settled-edit SDF delta brick (binding 11). Chunk-local grid at the density
// stride (grid_stride). Trilinearly sampled so it agrees with the mesh stage's
// fractional evals (noise_sdf.glsl apply_brick_delta — keep identical). Written
// in bake mode (FLAG_BAKE), read in sample mode (FLAG_BRICK) — NOT readonly.
// Zero buffer / FLAG_BRICK unset => exact no-op.
layout(set = 0, binding = 11, std430) buffer BrickBuf {
    float brick_delta[];
};

// Convex-brush half-space planes (binding 12). A CONVEX edit (etype 8/9)
// references a contiguous run [ _pad1, _pad1+_pad2 ) of vec4(nx,ny,nz,d);
// inside the brush = (n·p <= d). MUST match edge_intersect.glsl binding 11.
layout(set = 0, binding = 12, std430) readonly buffer ConvexPlaneBuf {
    vec4 convex_planes[];
};

// --- Baked custom data (hyper-games #50 section 5.3) ---
// A terrain-diffusion block, elevation in METRES, row-major, row = world Z.
// The generator emits custom_data_height_m() around this with the block's geometry
// baked in as literals, so no Params field is needed and the std140 layout --
// which is declared here, in edge_intersect.glsl and in C++, and desynchronises
// SILENTLY -- is untouched. Unbound, this is a one-float placeholder that
// nothing references.
layout(set = 0, binding = 13, std430) readonly buffer CustomDataBuf {
    float custom_data[];
};

// binding 14: THE SAME DATA AS A TEXTURE.
//
// Declared unconditionally because a uniform set must match the shader's
// bindings exactly; whether it is READ is chosen by HBLK_USE_ATLAS in the
// emitted sampler. Carrying both lets the two be compared rather than the swap
// being taken on faith.
//
// Linear index i maps to (i % HBLK_ATLAS_W, i / HBLK_ATLAS_W) — a transport
// swap, not a layout change, so both hold identical values at identical
// indices. The generator emits HBLK_ATLAS_W beside the block constants.
layout(set = 0, binding = 14, r32f) uniform readonly image2D custom_data_tex;

// GENERATOR_SAMPLER_BINDINGS
// Bindings 15..22 (eight noise volumes) are injected HERE, and only when the
// generator body samples them -- see _build_custom_shader. NOTE the numbers
// differ from edge_intersect.glsl (14..21): binding 14 is already custom_data_tex
// on this side. The body refers to the volumes BY NAME, so the two shaders need
// not agree on numbering, only on the names.

// --- Mesh SDF helper functions ---

float aabb_point_dist_sq(vec3 p, vec3 bmin, vec3 bmax) {
    vec3 d = max(bmin - p, vec3(0.0)) + max(p - bmax, vec3(0.0));
    return dot(d, d);
}

// Ericson closest-point + Baerentzen pseudonormal sign.
// Returns signed squared distance: positive = outside, negative = inside.
float point_tri_dist_sq(vec3 P, GpuTriangle tri, out vec3 normal) {
    vec3 A = vec3(tri.v0x, tri.v0y, tri.v0z);
    vec3 B = vec3(tri.v1x, tri.v1y, tri.v1z);
    vec3 C = vec3(tri.v2x, tri.v2y, tri.v2z);

    vec3 AB = B - A, AC = C - A, AP = P - A;
    float d1 = dot(AB, AP), d2 = dot(AC, AP);

    if (d1 <= 0.0 && d2 <= 0.0) {
        normal = vec3(tri.vn0x, tri.vn0y, tri.vn0z);
        vec3 diff = P - A; float sd = dot(diff, normal);
        return (sd >= 0.0) ? dot(diff, diff) : -dot(diff, diff);
    }

    vec3 BP = P - B;
    float d3 = dot(AB, BP), d4 = dot(AC, BP);

    if (d3 >= 0.0 && d4 <= d3) {
        normal = vec3(tri.vn1x, tri.vn1y, tri.vn1z);
        vec3 diff = P - B; float sd = dot(diff, normal);
        return (sd >= 0.0) ? dot(diff, diff) : -dot(diff, diff);
    }

    float vc = d1 * d4 - d3 * d2;
    if (vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0) {
        float v = d1 / (d1 - d3);
        vec3 closest = A + AB * v;
        normal = vec3(tri.en0x, tri.en0y, tri.en0z);
        vec3 diff = P - closest; float sd = dot(diff, normal);
        return (sd >= 0.0) ? dot(diff, diff) : -dot(diff, diff);
    }

    vec3 CP = P - C;
    float d5 = dot(AB, CP), d6 = dot(AC, CP);

    if (d6 >= 0.0 && d5 <= d6) {
        normal = vec3(tri.vn2x, tri.vn2y, tri.vn2z);
        vec3 diff = P - C; float sd = dot(diff, normal);
        return (sd >= 0.0) ? dot(diff, diff) : -dot(diff, diff);
    }

    float vb = d5 * d2 - d1 * d6;
    if (vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0) {
        float w = d2 / (d2 - d6);
        vec3 closest = A + AC * w;
        normal = vec3(tri.en2x, tri.en2y, tri.en2z);
        vec3 diff = P - closest; float sd = dot(diff, normal);
        return (sd >= 0.0) ? dot(diff, diff) : -dot(diff, diff);
    }

    float va = d3 * d6 - d5 * d4;
    if (va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0) {
        float w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
        vec3 closest = B + (C - B) * w;
        normal = vec3(tri.en1x, tri.en1y, tri.en1z);
        vec3 diff = P - closest; float sd = dot(diff, normal);
        return (sd >= 0.0) ? dot(diff, diff) : -dot(diff, diff);
    }

    float denom = 1.0 / (va + vb + vc);
    float v = vb * denom, w = vc * denom;
    vec3 closest = A + AB * v + AC * w;
    normal = vec3(tri.fnx, tri.fny, tri.fnz);
    vec3 diff = P - closest; float sd = dot(diff, normal);
    return (sd >= 0.0) ? dot(diff, diff) : -dot(diff, diff);
}

// BVH traversal: returns signed distance (positive=outside, negative=inside)
float bvh_mesh_sdf(vec3 p) {
    if (mesh_node_count == 0u) return 1e10;

    float best_dsq = 1e30;
    vec3 best_n = vec3(0, 1, 0);

    int stack[32];
    int sp = 0;
    stack[sp++] = 0;

    while (sp > 0) {
        int ni = stack[--sp];
        GpuBVHNode node = mesh_nodes[ni];

        vec3 bmin = vec3(node.aabb_min_x, node.aabb_min_y, node.aabb_min_z);
        vec3 bmax = vec3(node.aabb_max_x, node.aabb_max_y, node.aabb_max_z);
        float adsq = aabb_point_dist_sq(p, bmin, bmax);
        if (adsq > abs(best_dsq)) continue;

        if (node.count > 0u) {
            for (uint i = 0u; i < node.count; i++) {
                uint ti = mesh_tri_indices[node.left_or_start + i];
                vec3 n;
                float dsq = point_tri_dist_sq(p, mesh_triangles[ti], n);
                if (abs(dsq) < abs(best_dsq)) {
                    best_dsq = dsq;
                    best_n = n;
                }
            }
        } else {
            uint left = node.left_or_start;
            uint right = left + 1u;

            vec3 lmin = vec3(mesh_nodes[left].aabb_min_x, mesh_nodes[left].aabb_min_y, mesh_nodes[left].aabb_min_z);
            vec3 lmax = vec3(mesh_nodes[left].aabb_max_x, mesh_nodes[left].aabb_max_y, mesh_nodes[left].aabb_max_z);
            vec3 rmin = vec3(mesh_nodes[right].aabb_min_x, mesh_nodes[right].aabb_min_y, mesh_nodes[right].aabb_min_z);
            vec3 rmax = vec3(mesh_nodes[right].aabb_max_x, mesh_nodes[right].aabb_max_y, mesh_nodes[right].aabb_max_z);

            float dl = aabb_point_dist_sq(p, lmin, lmax);
            float dr = aabb_point_dist_sq(p, rmin, rmax);

            // Push far child first (popped last = traversed last)
            if (dl < dr) {
                if (dr < abs(best_dsq)) stack[sp++] = int(right);
                if (dl < abs(best_dsq)) stack[sp++] = int(left);
            } else {
                if (dl < abs(best_dsq)) stack[sp++] = int(left);
                if (dr < abs(best_dsq)) stack[sp++] = int(right);
            }
        }
    }

    float sign = (best_dsq >= 0.0) ? 1.0 : -1.0;
    return sqrt(abs(best_dsq)) * sign;
}

// --- Settled-edit brick (trilinear; MUST match noise_sdf.glsl) ---

float apply_brick_delta(float wx, float wy, float wz) {
    int stride = grid_stride;
    vec3 lv = (vec3(wx, wy, wz) - vec3(origin_x, origin_y, origin_z)) / voxel_scale;
    lv = clamp(lv, vec3(0.0), vec3(float(stride - 1) - 1e-4));
    ivec3 i0 = ivec3(floor(lv));
    vec3 f = lv - vec3(i0);
    int sx = stride, sxy = stride * stride;
    int b = i0.z * sxy + i0.y * sx + i0.x;
    float c00 = mix(brick_delta[b],            brick_delta[b + 1],            f.x);
    float c10 = mix(brick_delta[b + sx],       brick_delta[b + sx + 1],       f.x);
    float c01 = mix(brick_delta[b + sxy],      brick_delta[b + sxy + 1],      f.x);
    float c11 = mix(brick_delta[b + sxy + sx], brick_delta[b + sxy + sx + 1], f.x);
    return mix(mix(c00, c10, f.y), mix(c01, c11, f.y), f.z);
}

// --- Edit application ---

float apply_gpu_edits(float d, float wx, float wy, float wz) {
    for (uint i = 0u; i < edit_count; i++) {
        GpuEdit e = gpu_edits[i];
        uint etype = e.edit_type;

        // Bounding sphere early reject
        float edx = wx - e.pos_x;
        float edy = wy - e.pos_y;
        float edz = wz - e.pos_z;
        float dist_sq = edx*edx + edy*edy + edz*edz;
        if (dist_sq > e.radius * e.radius) continue;

        if (etype <= 1u) {
            // Sphere: smooth quadratic falloff
            float r_sq = e.radius * e.radius;
            float t = 1.0 - dist_sq / r_sq;
            d += e.strength * t * t;
        } else if (etype <= 3u) {
            // Box: SDF-based CSG
            float dx = abs(wx - e.pos_x) - e.half_x;
            float dy = abs(wy - e.pos_y) - e.half_y;
            float dz = abs(wz - e.pos_z) - e.half_z;
            float outside = sqrt(
                max(dx, 0.0) * max(dx, 0.0) +
                max(dy, 0.0) * max(dy, 0.0) +
                max(dz, 0.0) * max(dz, 0.0));
            float inside = min(max(max(dx, dy), dz), 0.0);
            float box_sdf = outside + inside;
            if (e.strength < 0.0) {
                d = min(d, box_sdf);
            } else {
                d = max(d, -box_sdf);
            }
        } else if (etype == 6u || etype == 7u) {
            // Hard sphere CSG: SDF = dist - radius (neg inside, pos outside —
            // same convention as box_sdf). Union/difference by strength sign.
            float sphere_sdf = sqrt(dist_sq) - e.radius;
            if (e.strength < 0.0) {
                d = min(d, sphere_sdf);
            } else {
                d = max(d, -sphere_sdf);
            }
        } else if (etype == 8u || etype == 9u) {
            // Convex brush = intersection of half-spaces: f = max_i(n_i·p - d_i)
            // (neg inside, pos outside — same convention as box_sdf). Planes live
            // in [_pad1, _pad1+_pad2) of convex_planes[]. Mirror box CSG exactly.
            uint poff = e._pad1;
            uint pcnt = e._pad2;
            float f = -1e30;
            for (uint pi = 0u; pi < pcnt; pi++) {
                vec4 pl = convex_planes[poff + pi];
                f = max(f, pl.x * wx + pl.y * wy + pl.z * wz - pl.w);
            }
            if (e.strength < 0.0) {
                d = min(d, f);
            } else {
                d = max(d, -f);
            }
        } else {
            // Mesh SDF: transform to local space, query BVH
            float scale = e.half_x;  // scale stored in half_x for mesh edits
            if (scale <= 0.0) continue;
            vec3 local_p = vec3(edx, edy, edz) / scale;
            float mesh_d = bvh_mesh_sdf(local_p);
            // mesh_d: positive = outside, negative = inside (pseudonormal convention)
            // Same CSG as CPU: mesh_sdf = -mesh_d, then min/max
            float mesh_sdf = -mesh_d;
            if (e.strength < 0.0) {
                d = min(d, mesh_sdf);
            } else {
                d = max(d, -mesh_sdf);
            }
        }
    }
    return d;
}
)";

// MAIN: dispatch logic that calls evaluate_density().
// Custom shaders must define: float evaluate_density(float x, float y, float z)
static const char* DENSITY_GLSL_MAIN = R"(
void main() {
    uvec3 gid = gl_GlobalInvocationID;
    int stride = grid_stride;
    if (int(gid.x) >= stride || int(gid.y) >= stride || int(gid.z) >= stride)
        return;
    int idx = int(gid.z) * stride * stride + int(gid.y) * stride + int(gid.x);
    float wx = origin_x + float(gid.x) * voxel_scale;
    float wy = origin_y + float(gid.y) * voxel_scale;
    float wz = origin_z + float(gid.z) * voxel_scale;
    float base_d = evaluate_density(wx, wy, wz);

    // BAKE mode: write the settled-edit delta (applied - base) into the brick.
    // Uses this shader's own evaluate_density so base cancels exactly at sample
    // time (no CPU/GPU noise-match dependency). No density/normal output.
    if ((flags & FLAG_BAKE) != 0) {
        float applied = base_d;
        if (edit_count > 0u) applied = apply_gpu_edits(base_d, wx, wy, wz);
        brick_delta[idx] = applied - base_d;
        return;
    }

    float d = base_d;
    if ((flags & FLAG_BRICK) != 0) d += apply_brick_delta(wx, wy, wz);  // settled brick before HOT edits
    if (edit_count > 0u) {
        d = apply_gpu_edits(d, wx, wy, wz);
    }
    densities[idx] = d;
    if (compute_normals != 0) {
        float h = voxel_scale * 2.0;
        float d0 = evaluate_density(wx + h, wy - h, wz - h);
        float d1 = evaluate_density(wx - h, wy - h, wz + h);
        float d2 = evaluate_density(wx - h, wy + h, wz - h);
        float d3 = evaluate_density(wx + h, wy + h, wz + h);
        if ((flags & FLAG_BRICK) != 0) {
            d0 += apply_brick_delta(wx + h, wy - h, wz - h);
            d1 += apply_brick_delta(wx - h, wy - h, wz + h);
            d2 += apply_brick_delta(wx - h, wy + h, wz - h);
            d3 += apply_brick_delta(wx + h, wy + h, wz + h);
        }
        if (edit_count > 0u) {
            d0 = apply_gpu_edits(d0, wx + h, wy - h, wz - h);
            d1 = apply_gpu_edits(d1, wx - h, wy - h, wz + h);
            d2 = apply_gpu_edits(d2, wx - h, wy + h, wz - h);
            d3 = apply_gpu_edits(d3, wx + h, wy + h, wz + h);
        }
        float nx = -(d0 - d1 - d2 + d3);
        float ny = -(-d0 - d1 + d2 + d3);
        float nz = -(-d0 + d1 - d2 + d3);
        float len = sqrt(nx * nx + ny * ny + nz * nz);
        if (len > 1e-10) { nx /= len; ny /= len; nz /= len; }
        normal_x[idx] = nx;
        normal_y[idx] = ny;
        normal_z[idx] = nz;
    }
}
)";

// BODY: built-in noise functions + evaluate_density for default terrain.
// This is the part that gets replaced by custom shaders.
static const char* DENSITY_GLSL_BODY = R"(
#define PERM_PRIMARY  0
#define PERM_TUNNEL_A 512
#define PERM_TUNNEL_B 1024
#define PERM_RIDGE    1536
#define PERM_WARP     2048
#define PERM_DETAIL   2560

int perm(int table_offset, int idx) {
    return perm_tables[table_offset + (idx & 255)];
}

int fastfloor(float x) {
    int xi = int(x);
    return (x < float(xi)) ? xi - 1 : xi;
}

float grad3(int hash, float x, float y, float z) {
    int h = hash & 15;
    float u = (h < 8) ? x : y;
    float v = (h < 4) ? y : ((h == 12 || h == 14) ? x : z);
    return (((h & 1) != 0) ? -u : u) + (((h & 2) != 0) ? -v : v);
}

float contrib(int table_offset, int ii, int jj, int kk, float x, float y, float z) {
    float t = 0.6 - x * x - y * y - z * z;
    if (t < 0.0) return 0.0;
    t *= t;
    int gi = perm(table_offset, ii + perm(table_offset, jj + perm(table_offset, kk)));
    return t * t * grad3(gi, x, y, z);
}

float noise3(int table_offset, float x, float y, float z) {
    const float F3 = 1.0 / 3.0;
    const float G3 = 1.0 / 6.0;
    float s = (x + y + z) * F3;
    int i = fastfloor(x + s);
    int j = fastfloor(y + s);
    int k = fastfloor(z + s);
    float t = float(i + j + k) * G3;
    float X0 = float(i) - t;
    float Y0 = float(j) - t;
    float Z0 = float(k) - t;
    float x0 = x - X0;
    float y0 = y - Y0;
    float z0 = z - Z0;
    int i1, j1, k1, i2, j2, k2;
    if (x0 >= y0) {
        if (y0 >= z0)      { i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; }
        else if (x0 >= z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; }
        else               { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; }
    } else {
        if (y0 < z0)       { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; }
        else if (x0 < z0)  { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; }
        else               { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; }
    }
    float x1 = x0 - float(i1) + G3;
    float y1 = y0 - float(j1) + G3;
    float z1 = z0 - float(k1) + G3;
    float x2 = x0 - float(i2) + 2.0 * G3;
    float y2 = y0 - float(j2) + 2.0 * G3;
    float z2 = z0 - float(k2) + 2.0 * G3;
    float x3 = x0 - 1.0 + 3.0 * G3;
    float y3 = y0 - 1.0 + 3.0 * G3;
    float z3 = z0 - 1.0 + 3.0 * G3;
    int ii = i & 255;
    int jj = j & 255;
    int kk = k & 255;
    float n0 = contrib(table_offset, ii,     jj,     kk,     x0, y0, z0);
    float n1 = contrib(table_offset, ii+i1,  jj+j1,  kk+k1,  x1, y1, z1);
    float n2 = contrib(table_offset, ii+i2,  jj+j2,  kk+k2,  x2, y2, z2);
    float n3 = contrib(table_offset, ii+1,   jj+1,   kk+1,   x3, y3, z3);
    return 32.0 * (n0 + n1 + n2 + n3);
}

float fbm3(int table_offset, float x, float y, float z, int octaves) {
    float sum = 0.0;
    float amp = 1.0;
    float freq = 1.0;
    float max_amp = 0.0;
    for (int o = 0; o < octaves; o++) {
        sum += noise3(table_offset, x * freq, y * freq, z * freq) * amp;
        max_amp += amp;
        amp *= 0.5;
        freq *= 2.0;
    }
    return sum / max_amp;
}

float sample_noise(int table_offset, float freq, float px, float py, float pz, int oct) {
    return fbm3(table_offset, px * freq, py * freq, pz * freq, oct);
}

float clampf(float v, float lo, float hi) {
    return clamp(v, lo, hi);
}

float smoothstep_manual(float t) {
    return t * t * (3.0 - 2.0 * t);
}

// Hash-based procedural Voronoi (infinite tiling)
uint hash2d(int ix, int iz, int seed_offset) {
    uint h = uint(ix * 374761393 + iz * 668265263 + seed_offset);
    h = (h ^ (h >> 13u)) * 1274126177u;
    h = h ^ (h >> 16u);
    return h;
}

float hash_float(int ix, int iz, int seed_offset) {
    return float(hash2d(ix, iz, seed_offset) & 0xFFFFFFu) / 16777216.0;
}

float evaluate_density(float x, float y, float z) {
    float wx = x + sample_noise(PERM_WARP, warp_frequency, x, y, z, warp_octaves) * warp_strength;
    float wy = y + sample_noise(PERM_WARP, warp_frequency, x + 31.7, y + 47.3, z + 89.1, warp_octaves) * warp_strength;
    float wz = z + sample_noise(PERM_WARP, warp_frequency, x + 73.1, y + 11.9, z + 59.7, warp_octaves) * warp_strength;
    float d = -y * ground_bias;
    d += sample_noise(PERM_PRIMARY, primary_frequency, wx, wy, wz, primary_octaves) * primary_strength;
    float ta = sample_noise(PERM_TUNNEL_A, tunnel_frequency, wx, wy * 0.7, wz, tunnel_octaves);
    float tb = sample_noise(PERM_TUNNEL_B, tunnel_frequency, wx + 200.0, wy * 0.7, wz + 200.0, tunnel_octaves);
    float tube_dist_sq = ta * ta + tb * tb;
    float radius_sq = tunnel_radius * tunnel_radius;
    float tube_carve = max(1.0 - tube_dist_sq / radius_sq, 0.0);
    d -= tube_carve * tunnel_strength;
    float ridge_raw = sample_noise(PERM_RIDGE, ridge_frequency, wx, wy, wz, ridge_octaves);
    // Smooth-abs ridge — IDENTICAL to CPU DCDensity::evaluate_internal and the
    // mesh-stage noise_sdf.glsl. The old 1.0 - abs(ridge_raw) had a V-kink at
    // ridge_raw=0 that diverged from the carved/meshed surface (Finding 3).
    float ridge_eps = 0.08;
    float ridged = 1.0 - sqrt(ridge_raw * ridge_raw + ridge_eps * ridge_eps);
    ridged = max(ridged, 0.0);
    ridged = ridged * ridged;
    d += ridged * ridge_strength;
    d += sample_noise(PERM_DETAIL, detail_frequency, wx, wy, wz, detail_octaves) * detail_strength;
    if (floor_strength > 0.0) {
        float cell_spacing = terrain_size / float(voronoi_cell_count);
        int gx = int(floor(x / cell_spacing));
        int gz = int(floor(z / cell_spacing));
        int seed_base = seed + 100;

        float nearest_dist_sq = 3.402823e+38;
        float nearest_floor_h = 0.0;
        float second_dist_sq = 3.402823e+38;

        for (int dzi = -1; dzi <= 1; dzi++) {
            for (int dxi = -1; dxi <= 1; dxi++) {
                int cxi = gx + dxi;
                int czi = gz + dzi;
                float jx = hash_float(cxi, czi, seed_base);
                float jz = hash_float(cxi, czi, seed_base + 7);
                float cell_x = (float(cxi) + 0.2 + jx * 0.6) * cell_spacing;
                float cell_z = (float(czi) + 0.2 + jz * 0.6) * cell_spacing;
                float fh = hash_float(cxi, czi, seed_base + 13);
                float flr_h = floor_height_min + fh * (floor_height_max - floor_height_min);

                float ddx = x - cell_x;
                float ddz = z - cell_z;
                float vd = ddx * ddx + ddz * ddz;
                if (vd < nearest_dist_sq) {
                    second_dist_sq = nearest_dist_sq;
                    nearest_dist_sq = vd;
                    nearest_floor_h = flr_h;
                } else if (vd < second_dist_sq) {
                    second_dist_sq = vd;
                }
            }
        }

        float edge_ratio = 1.0 - nearest_dist_sq / (second_dist_sq + 0.001);
        float cell_blend = clampf(edge_ratio * 2.0, 0.0, 1.0);
        cell_blend = smoothstep_manual(cell_blend);
        float floor_dist = nearest_floor_h - y;
        float floor_effect = 0.0;
        if (floor_dist > 0.0) {
            floor_effect = clampf(floor_dist * 0.5, 0.0, 2.0);
        } else if (floor_dist > -1.5) {
            floor_effect = floor_dist * 1.5;
        }
        float ceiling_h = nearest_floor_h + room_height;
        float ceil_dist = y - ceiling_h;
        float ceil_effect = 0.0;
        if (ceil_dist > 0.0 && ceil_dist < 3.0) {
            ceil_effect = -clampf(ceil_dist * 0.5, 0.0, 2.0);
        }
        d += (floor_effect + ceil_effect) * cell_blend * floor_strength;
    }
    float floor_blend = max((-y - solid_floor_depth) * 0.1, 0.0);
    d += floor_blend;
    float sky_blend = max((y - sky_height) * 0.1, 0.0);
    d -= sky_blend;
    // Crater system: spawn crater + random craters across the island.
    // Ported verbatim from CPU DCDensity::evaluate_internal. Crater #0
    // (radius = flat_center_radius) SUBSUMES the old standalone flat-center
    // spawn block — do NOT keep both (double-flatten).
    float crater_cx[6];
    float crater_cz[6];
    float crater_r[6];
    int num_craters = 0;
    crater_cx[num_craters] = 0.0;
    crater_cz[num_craters] = 0.0;
    crater_r[num_craters]  = flat_center_radius;
    num_craters++;
    for (int ci = 0; ci < 5; ci++) {
        float angle = hash_float(ci, seed, 200) * 6.2831853;
        float cdistr = 40.0 + hash_float(ci, seed, 201) * (island_radius * 0.5);
        float cr = 10.0 + hash_float(ci, seed, 202) * 20.0;
        crater_cx[num_craters] = cos(angle) * cdistr;
        crater_cz[num_craters] = sin(angle) * cdistr;
        crater_r[num_craters]  = cr;
        num_craters++;
    }
    for (int ci = 0; ci < num_craters; ci++) {
        float cdx = x - crater_cx[ci];
        float cdz = z - crater_cz[ci];
        float cdist = sqrt(cdx * cdx + cdz * cdz);
        float cr = crater_r[ci];
        float ctransition = cr * 0.5;
        if (cdist < cr + ctransition) {
            float blend = 1.0 - clampf(
                (cdist - cr * 0.3) / (cr * 0.7 + ctransition),
                0.0, 1.0);
            blend = smoothstep_manual(blend);
            float flat_d = -y * 0.8;
            d = d + (flat_d - d) * blend;
            float rim_start = cr * 0.7;
            float rim_end = cr + ctransition * 0.5;
            if (cdist > rim_start && cdist < rim_end) {
                float rim_t = (cdist - rim_start) / (rim_end - rim_start);
                rim_t = sin(rim_t * 3.14159);
                float rim_height = cr * 0.2;
                float rim_noise = fbm3(PERM_DETAIL,
                    x * detail_frequency * 2.0,
                    y * detail_frequency,
                    z * detail_frequency * 2.0, 2) * rim_height * 0.4;
                float rim_pull = (rim_height + rim_noise) * rim_t;
                d += max(0.0, (rim_pull - y) * 0.3) * rim_t;
            }
        }
    }
    return d;
}
)";
// Full default shader = HEADER + BODY + MAIN (assembled once at init)
static std::string _build_default_shader() {
    return std::string(DENSITY_GLSL_HEADER) + DENSITY_GLSL_BODY + DENSITY_GLSL_MAIN;
}

// Wrap custom evaluate_density() function body in compute shader template
// True when a generator body samples the noise volumes, i.e. when the density
// shader must declare bindings 15..22 and DCCompute must supply descriptors for
// them. Declaring bindings the body never reads risks them being optimised out
// of the SPIR-V, after which uniform_set_create is handed descriptors the shader
// does not reflect and fails -- so absence has to agree too, not just presence.
static bool _body_uses_noise_volumes(const char* custom_body) {
    return custom_body != nullptr
        && std::string(custom_body).find("noiseVol") != std::string::npos;
}

static std::string _build_custom_shader(const char* custom_body) {
    std::string header(DENSITY_GLSL_HEADER);
    const std::string marker = "// GENERATOR_SAMPLER_BINDINGS";
    size_t pos = header.find(marker);
    if (pos != std::string::npos) {
        std::string decls;
        if (_body_uses_noise_volumes(custom_body)) {
            for (int i = 0; i < 4; i++) {
                decls += "layout(set = 0, binding = " + std::to_string(15 + i)
                       + ") uniform sampler3D noiseVol" + std::to_string(i) + ";\n";
            }
            for (int i = 0; i < 4; i++) {
                decls += "layout(set = 0, binding = " + std::to_string(19 + i)
                       + ") uniform sampler3D packedNoiseVol" + std::to_string(i) + ";\n";
            }
        }
        header.replace(pos, marker.length(), decls);
    }
    return header + "\n" + custom_body + "\n" + DENSITY_GLSL_MAIN;
}

#endif // DC_SHADER_SOURCE_H