fix(pipeline): batched-hatch pattern math + invisible-instance clip

Two issues in the step-3 batched path surfaced during testing:

1. WGSL rejects compile-time NaN literals (`0.0 / 0.0`), so the
   visibility-skip path that emitted a NaN clip position failed
   `Device::create_shader_module` validation. Replaced with an
   out-of-frustum clip position `(2, 2, 2, 1)` — the GPU rasterizer
   clips the primitive and the fragment shader never runs.

2. Pattern hatches rendered with the wrong spacing / direction. The
   batched builder was rotating `perp_step` / `along_step` into the
   global frame, but the per-hatch shader (`hatch.wgsl`) expects them
   in QCAD PAT local-frame convention: `perp_step = family.dy`,
   `along_step = family.dx`, with the cos_off/sin_off rotation applied
   inside the shader. Now matches `build_family_batch` from
   `hatch_gpu.rs` line-for-line. `line_width` also reset to 0 (the
   shader picks 1 px from a screen-space derivative; the stored field
   is unused).

3. Padding's `max_spacing` likewise dropped its bogus rotation and
   now uses `|dy|` per family, matching the new perp_step convention.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.7 committed 2026-05-17 20:04:16 +03:00
1 parent 3593e9dabc
commit cfe53bd789
2 files changed
+21 -13

No files matched your search

+6 -3
View File
@@ -100,10 +100,13 @@ fn corner_xy(c: u32, aabb: vec4<f32>) -> vec2<f32> {
var o: VOut;
let inst = instances[v.instance_index];
// CPU-driven visibility flag (Phase 4-B frustum skip). NaN clip
// position degenerates the triangle so no fragment runs for it.
// CPU-driven visibility flag (Phase 4-B frustum skip). Emit a clip
// position whose x/y exceed |w|, so the GPU frustum-culls the
// primitive and no fragment runs for it. (WGSL forbids literal NaN
// so we can't go that route; pushing the vertex out of the unit
// cube is the equivalent degenerate-triangle trick.)
if inst.visible == 0u {
o.clip = vec4<f32>(0.0 / 0.0, 0.0 / 0.0, 0.0 / 0.0, 1.0);
o.clip = vec4<f32>(2.0, 2.0, 2.0, 1.0);
o.xz = vec2<f32>(0.0, 0.0);
o.instance_index = v.instance_index;
return o;