OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / distribute / hypertps-win64 / shaders / quad_emit.glsl

236 lines · glsl ref:
#version 450

// ============================================================================
// Stage 10: Quad Emit (GPU mesh generation)
//
// For each voxel in the 16³ grid, check its 3 owned edges (+X, +Y, +Z).
// For each sign-change edge, find the 4 cells sharing that edge and emit
// a quad (2 triangles = 6 indices) via atomic append. When only 3 of 4
// neighbors are valid (chunk boundary), emit a single degenerate triangle.
//
// The 4 cells sharing an edge are determined by the edge axis:
//   X-edge at (x,y,z): cells (x,y,z), (x,y-1,z), (x,y,z-1), (x,y-1,z-1)
//   Y-edge at (x,y,z): cells (x,y,z), (x-1,y,z), (x,y,z-1), (x-1,y,z-1)
//   Z-edge at (x,y,z): cells (x,y,z), (x-1,y,z), (x,y-1,z), (x-1,y-1,z)
//
// Winding is determined by the density sign at the edge's starting corner.
//
// Input:
//   density[17³]        — density grid (DENSITY_STRIDE = 17)
//   edge_flags[16³×3]   — sign-change flags per edge
//   voxel_flags[16³]    — active voxel flags (1 = has QEF vertex)
//   voxel_scan[16³]     — exclusive prefix sum of voxel_flags (compacted index)
//
// Output:
//   indices[]           — triangle index buffer (atomic append)
//   counts[1]           — total index count (atomically incremented)
//
// Dispatch: ceil(4096 / 256), 1, 1
// ============================================================================

#define THREADS 256

layout(local_size_x = THREADS, local_size_y = 1, local_size_z = 1) in;

layout(set = 0, binding = 0, std430) readonly  buffer DensityBuf   { float density[];     };
layout(set = 0, binding = 1, std430) readonly  buffer EdgeFlagBuf  { uint  edge_flags[];  };
layout(set = 0, binding = 4, std430) readonly  buffer VoxelFlagBuf { uint  voxel_flags[]; };
layout(set = 0, binding = 5, std430) readonly  buffer VoxelScanBuf { uint  voxel_scan[];  };
layout(set = 0, binding = 6, std430) readonly  buffer VertexBuf    { float vertices[];    };
layout(set = 0, binding = 8, std430) writeonly buffer IndexBuf     { uint  indices[];     };
layout(set = 0, binding = 9, std430)           buffer CountBuf     { uint  counts[];      };

layout(push_constant) uniform PushConstants {
    vec3  cell_origin;
    float voxel_size;
    ivec3 grid_dims;       // (16, 16, 16)
    uint  total_elements;  // 4096
    uint  mesh_quality_flags;
    uint  _pc_pad0;
    uint  _pc_pad1;
    uint  _pc_pad2; // bit 0 = shortest_diagonal
};

// Neighbor cell offsets for the 4 cells sharing each edge type.
// Each edge axis has 4 neighbor cells; offsets are (dy, dz) for X-edge, etc.
//
// X-edge at (x,y,z): cells at (x, y+dy, z+dz) for each neighbor
const ivec2 X_NEIGHBORS[4] = ivec2[4](
    ivec2( 0,  0),   // (x, y,   z  )
    ivec2(-1,  0),   // (x, y-1, z  )
    ivec2( 0, -1),   // (x, y,   z-1)
    ivec2(-1, -1)    // (x, y-1, z-1)
);

// Y-edge at (x,y,z): cells at (x+dx, y, z+dz) for each neighbor
// IMPORTANT: Z first, then X (matches CPU edgeCellOffsets — swapped from
// naive X,Z order to get correct winding)
const ivec2 Y_NEIGHBORS[4] = ivec2[4](
    ivec2( 0,  0),   // (x,   y, z  )
    ivec2( 0, -1),   // (x,   y, z-1)
    ivec2(-1,  0),   // (x-1, y, z  )
    ivec2(-1, -1)    // (x-1, y, z-1)
);

// Z-edge at (x,y,z): cells at (x+dx, y+dy, z) for each neighbor
const ivec2 Z_NEIGHBORS[4] = ivec2[4](
    ivec2( 0,  0),   // (x,   y,   z)
    ivec2(-1,  0),   // (x-1, y,   z)
    ivec2( 0, -1),   // (x,   y-1, z)
    ivec2(-1, -1)    // (x-1, y-1, z)
);

void try_emit_quad(int x, int y, int z, int axis) {
    int V = grid_dims.x;  // 16
    int S = V + 1;        // 17 (density stride)

    // Check edge flag
    int voxel_idx = z * V * V + y * V + x;
    if (edge_flags[voxel_idx * 3 + axis] == 0u) return;

    // Gather the 4 neighbor cell indices.
    // Track validity with a bitmask (bit i = neighbor i is valid).
    uint cell_vi[4];
    uint valid_mask = 0u;
    int nv = 0;  // count of valid neighbors

    for (int i = 0; i < 4; i++) {
        int cx, cy, cz;

        if (axis == 0) {
            cx = x;
            cy = y + X_NEIGHBORS[i].x;
            cz = z + X_NEIGHBORS[i].y;
        } else if (axis == 1) {
            cx = x + Y_NEIGHBORS[i].x;
            cy = y;
            cz = z + Y_NEIGHBORS[i].y;
        } else {
            cx = x + Z_NEIGHBORS[i].x;
            cy = y + Z_NEIGHBORS[i].y;
            cz = z;
        }

        // Bounds check: neighbor cell must be in [0, V-2] (QEF active region)
        if (cx < 0 || cy < 0 || cz < 0 || cx > V - 2 || cy > V - 2 || cz > V - 2) {
            cell_vi[i] = 0u;
            continue;
        }

        int cell_idx = cz * V * V + cy * V + cx;

        // Active check: cell must have a QEF vertex
        if (voxel_flags[cell_idx] == 0u) {
            cell_vi[i] = 0u;
            continue;
        }

        cell_vi[i] = voxel_scan[cell_idx];
        valid_mask |= (1u << uint(i));
        nv++;
    }

    // Need at least 3 valid neighbors to form a triangle
    if (nv < 3) return;

    // Determine winding from density sign at edge starting corner.
    // CPU convention: s0 = (density > 0) = corner bit set = outside/air.
    // Must match edge_classify sign convention (d >= 0.0 = positive/outside).
    float d = density[z * S * S + y * S + x];
    bool s0 = (d >= 0.0);  // must match edge_classify sign convention (>=)

    if (nv == 4) {
        // Full quad: emit 2 triangles.
        // Choose split diagonal based on mesh_quality_flags:
        //   bit 0 set: split along shortest diagonal (better aspect ratio)
        //   bit 0 clear: always split along 0-3 diagonal (original behavior)
        bool split_02 = false; // false = split 0-3, true = split 1-2
        if ((mesh_quality_flags & 1u) != 0u) {
            // Read vertex positions to compare diagonal lengths.
            // Vertex layout: 12 floats per vertex (pos.xyz at offset 0-2).
            vec3 p0 = vec3(vertices[cell_vi[0]*12u], vertices[cell_vi[0]*12u+1u], vertices[cell_vi[0]*12u+2u]);
            vec3 p1 = vec3(vertices[cell_vi[1]*12u], vertices[cell_vi[1]*12u+1u], vertices[cell_vi[1]*12u+2u]);
            vec3 p2 = vec3(vertices[cell_vi[2]*12u], vertices[cell_vi[2]*12u+1u], vertices[cell_vi[2]*12u+2u]);
            vec3 p3 = vec3(vertices[cell_vi[3]*12u], vertices[cell_vi[3]*12u+1u], vertices[cell_vi[3]*12u+2u]);
            float diag03_sq = dot(p3 - p0, p3 - p0);
            float diag12_sq = dot(p2 - p1, p2 - p1);
            split_02 = (diag12_sq < diag03_sq);
        }

        uint offset = atomicAdd(counts[1], 6u);
        if (!split_02) {
            // Split along 0-3 diagonal: triangles (0,1,3) + (0,3,2)
            if (!s0) {
                indices[offset + 0] = cell_vi[0];
                indices[offset + 1] = cell_vi[1];
                indices[offset + 2] = cell_vi[3];
                indices[offset + 3] = cell_vi[0];
                indices[offset + 4] = cell_vi[3];
                indices[offset + 5] = cell_vi[2];
            } else {
                indices[offset + 0] = cell_vi[0];
                indices[offset + 1] = cell_vi[3];
                indices[offset + 2] = cell_vi[1];
                indices[offset + 3] = cell_vi[0];
                indices[offset + 4] = cell_vi[2];
                indices[offset + 5] = cell_vi[3];
            }
        } else {
            // Split along 1-2 diagonal: triangles (0,1,2) + (1,3,2)
            if (!s0) {
                indices[offset + 0] = cell_vi[0];
                indices[offset + 1] = cell_vi[1];
                indices[offset + 2] = cell_vi[2];
                indices[offset + 3] = cell_vi[1];
                indices[offset + 4] = cell_vi[3];
                indices[offset + 5] = cell_vi[2];
            } else {
                indices[offset + 0] = cell_vi[0];
                indices[offset + 1] = cell_vi[2];
                indices[offset + 2] = cell_vi[1];
                indices[offset + 3] = cell_vi[1];
                indices[offset + 4] = cell_vi[2];
                indices[offset + 5] = cell_vi[3];
            }
        }
    } else {
        // Degenerate quad (3 of 4 valid): emit single triangle.
        // Collect the 3 valid vertex indices.
        uint tri[3];
        int t = 0;
        for (int i = 0; i < 4; i++) {
            if ((valid_mask & (1u << uint(i))) != 0u) {
                tri[t] = cell_vi[i];
                t++;
            }
        }
        uint offset = atomicAdd(counts[1], 3u);
        if (!s0) {
            indices[offset + 0] = tri[0];
            indices[offset + 1] = tri[1];
            indices[offset + 2] = tri[2];
        } else {
            indices[offset + 0] = tri[0];
            indices[offset + 1] = tri[2];
            indices[offset + 2] = tri[1];
        }
    }
}

void main() {
    uint gid = gl_GlobalInvocationID.x;
    if (gid >= total_elements) return;

    int V = grid_dims.x;  // 16

    // Decode linear voxel index to (x, y, z)
    int z = int(gid) / (V * V);
    int y = (int(gid) % (V * V)) / V;
    int x = int(gid) % V;

    // Check each of the 3 owned edges
    try_emit_quad(x, y, z, 0);  // +X edge
    try_emit_quad(x, y, z, 1);  // +Y edge
    try_emit_quad(x, y, z, 2);  // +Z edge
}