forked from 1b-agent/opencadstudio-upstream
fix(render): hatch dpdx in uniform control flow (#414)
Tint (Chrome/Edge WebGPU) rejects derivative builtins reached after the family loop's non-uniform early return, invalidating hatch_web/wipeout shaders on Windows. Take dpdx/dpdy(xz) once at fs_main top and rotate into the family frame in check_family — same math, uniform flow. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
87049967a2
commit
1cf64161d3
3 files changed
+54
-12
No files matched your search
+18
-4
@@ -190,8 +190,13 @@ fn in_polygon(p: vec2<f32>, offset: u32, count: u32) -> bool {
|
||||
// ── Per-family hatch test (same math as hatch.wgsl, dashes from
|
||||
// global DashBuffer instead of per-hatch FamilyBatch) ────────────────────
|
||||
|
||||
// `ddx_xz`/`ddy_xz` are screen-space derivatives of `xz`, taken once in
|
||||
// fs_main: derivative builtins must run in uniform control flow, and the
|
||||
// per-family loop's early return makes later iterations non-uniform.
|
||||
fn check_family(
|
||||
xz: vec2<f32>,
|
||||
ddx_xz: vec2<f32>,
|
||||
ddy_xz: vec2<f32>,
|
||||
fam: LineFamily,
|
||||
cos_off: f32,
|
||||
sin_off: f32,
|
||||
@@ -213,13 +218,18 @@ fn check_family(
|
||||
let k = round(perp / perp_step);
|
||||
let dperp = perp - k * perp_step;
|
||||
let d = abs(dperp);
|
||||
let half_px = length(vec2<f32>(dpdx(perp), dpdy(perp))) * 0.5;
|
||||
// perp is linear in xz (offsets are constant), so its derivatives are the
|
||||
// xz derivatives rotated into the family frame.
|
||||
let half_px = length(vec2<f32>(
|
||||
-ddx_xz.x * sin_a + ddx_xz.y * cos_a,
|
||||
-ddy_xz.x * sin_a + ddy_xz.y * cos_a,
|
||||
)) * 0.5;
|
||||
|
||||
// World units per screen pixel on each axis — used to light exactly the
|
||||
// one pixel that contains a dot's centre (pixel-snapped, so the dot stays
|
||||
// a steady single pixel at any pattern angle instead of flickering).
|
||||
let wpx = length(vec2<f32>(dpdx(xz.x), dpdy(xz.x)));
|
||||
let wpy = length(vec2<f32>(dpdx(xz.y), dpdy(xz.y)));
|
||||
let wpx = length(vec2<f32>(ddx_xz.x, ddy_xz.x));
|
||||
let wpy = length(vec2<f32>(ddx_xz.y, ddy_xz.y));
|
||||
|
||||
// A fragment within ~1px of a line may be a dot; everything further out is
|
||||
// empty fill. (A dot's pixel sits on a line, so its perp offset is < 1px.)
|
||||
@@ -257,6 +267,10 @@ fn check_family(
|
||||
// ── Fragment shader ──────────────────────────────────────────────────────
|
||||
|
||||
@fragment fn fs_main(v: VOut) -> @location(0) vec4<f32> {
|
||||
// Taken here, in uniform control flow — see check_family.
|
||||
let ddx_xz = dpdx(v.xz);
|
||||
let ddy_xz = dpdy(v.xz);
|
||||
|
||||
let inst = instances[v.instance_index];
|
||||
|
||||
// 1. Boundary test — only on the fallback path (poly_test==1). On the
|
||||
@@ -318,7 +332,7 @@ fn check_family(
|
||||
let cos_off = cos(inst.angle_offset);
|
||||
let sin_off = sin(inst.angle_offset);
|
||||
for (var i = 0u; i < inst.family_count; i++) {
|
||||
if check_family(v.xz, families[inst.family_offset + i], cos_off, sin_off, inst.scale) {
|
||||
if check_family(v.xz, ddx_xz, ddy_xz, families[inst.family_offset + i], cos_off, sin_off, inst.scale) {
|
||||
return inst.color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +161,13 @@ fn in_polygon(p: vec2<f32>) -> bool {
|
||||
|
||||
// ── Per-family hatch test (identical math to wipeout.wgsl) ─────────────────
|
||||
|
||||
// `ddx_xz`/`ddy_xz` are screen-space derivatives of `xz`, taken once in
|
||||
// fs_main: derivative builtins must run in uniform control flow, and the
|
||||
// per-family loop's early return makes later iterations non-uniform.
|
||||
fn check_family(
|
||||
xz: vec2<f32>,
|
||||
ddx_xz: vec2<f32>,
|
||||
ddy_xz: vec2<f32>,
|
||||
fam: LineFamily,
|
||||
cos_off: f32,
|
||||
sin_off: f32,
|
||||
@@ -183,10 +188,15 @@ fn check_family(
|
||||
let k = round(perp / perp_step);
|
||||
let dperp = perp - k * perp_step;
|
||||
let d = abs(dperp);
|
||||
let half_px = length(vec2<f32>(dpdx(perp), dpdy(perp))) * 0.5;
|
||||
// perp is linear in xz (offsets are constant), so its derivatives are the
|
||||
// xz derivatives rotated into the family frame.
|
||||
let half_px = length(vec2<f32>(
|
||||
-ddx_xz.x * sin_a + ddx_xz.y * cos_a,
|
||||
-ddy_xz.x * sin_a + ddy_xz.y * cos_a,
|
||||
)) * 0.5;
|
||||
|
||||
let wpx = length(vec2<f32>(dpdx(xz.x), dpdy(xz.x)));
|
||||
let wpy = length(vec2<f32>(dpdx(xz.y), dpdy(xz.y)));
|
||||
let wpx = length(vec2<f32>(ddx_xz.x, ddy_xz.x));
|
||||
let wpy = length(vec2<f32>(ddx_xz.y, ddy_xz.y));
|
||||
|
||||
if d > half_px * 2.0 { return false; }
|
||||
|
||||
@@ -220,6 +230,10 @@ fn check_family(
|
||||
// ── Fragment shader ────────────────────────────────────────────────────────
|
||||
|
||||
@fragment fn fs_main(v: VOut) -> @location(0) vec4<f32> {
|
||||
// Taken here, in uniform control flow — see check_family.
|
||||
let ddx_xz = dpdx(v.xz);
|
||||
let ddy_xz = dpdy(v.xz);
|
||||
|
||||
if !in_polygon(v.xz) { discard; }
|
||||
|
||||
let base_mode = h.mode & 0xFFu;
|
||||
@@ -271,7 +285,7 @@ fn check_family(
|
||||
let cos_off = cos(h.angle_offset);
|
||||
let sin_off = sin(h.angle_offset);
|
||||
for (var i = 0u; i < h.n_families; i++) {
|
||||
if check_family(v.xz, load_family(i), cos_off, sin_off, h.scale) {
|
||||
if check_family(v.xz, ddx_xz, ddy_xz, load_family(i), cos_off, sin_off, h.scale) {
|
||||
return h.color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,8 +170,13 @@ fn in_polygon(p: vec2<f32>) -> bool {
|
||||
// `cos_off` / `sin_off` are cos/sin of `h.angle_offset` (precomputed once).
|
||||
// `scale` is `h.scale`.
|
||||
|
||||
// `ddx_xz`/`ddy_xz` are screen-space derivatives of `xz`, taken once in
|
||||
// fs_main: derivative builtins must run in uniform control flow, and the
|
||||
// per-family loop's early return makes later iterations non-uniform.
|
||||
fn check_family(
|
||||
xz: vec2<f32>,
|
||||
ddx_xz: vec2<f32>,
|
||||
ddy_xz: vec2<f32>,
|
||||
fam: LineFamily,
|
||||
cos_off: f32,
|
||||
sin_off: f32,
|
||||
@@ -196,13 +201,18 @@ fn check_family(
|
||||
let k = round(perp / perp_step);
|
||||
let dperp = perp - k * perp_step;
|
||||
let d = abs(dperp);
|
||||
let half_px = length(vec2<f32>(dpdx(perp), dpdy(perp))) * 0.5;
|
||||
// perp is linear in xz (offsets are constant), so its derivatives are the
|
||||
// xz derivatives rotated into the family frame.
|
||||
let half_px = length(vec2<f32>(
|
||||
-ddx_xz.x * sin_a + ddx_xz.y * cos_a,
|
||||
-ddy_xz.x * sin_a + ddy_xz.y * cos_a,
|
||||
)) * 0.5;
|
||||
|
||||
// World units per screen pixel on each axis — used to light exactly the
|
||||
// one pixel that contains a dot's centre (pixel-snapped, so a dot stays a
|
||||
// steady single pixel at any pattern angle instead of flickering).
|
||||
let wpx = length(vec2<f32>(dpdx(xz.x), dpdy(xz.x)));
|
||||
let wpy = length(vec2<f32>(dpdx(xz.y), dpdy(xz.y)));
|
||||
let wpx = length(vec2<f32>(ddx_xz.x, ddy_xz.x));
|
||||
let wpy = length(vec2<f32>(ddx_xz.y, ddy_xz.y));
|
||||
|
||||
// A fragment within ~1px of a line may be a dot; further out is empty fill.
|
||||
if d > half_px * 2.0 { return false; }
|
||||
@@ -242,6 +252,10 @@ fn check_family(
|
||||
// ── Fragment shader ────────────────────────────────────────────────────────
|
||||
|
||||
@fragment fn fs_main(v: VOut) -> @location(0) vec4<f32> {
|
||||
// Taken here, in uniform control flow — see check_family.
|
||||
let ddx_xz = dpdx(v.xz);
|
||||
let ddy_xz = dpdy(v.xz);
|
||||
|
||||
// 1. Boundary test.
|
||||
if !in_polygon(v.xz) { discard; }
|
||||
|
||||
@@ -282,7 +296,7 @@ fn check_family(
|
||||
let cos_off = cos(h.angle_offset);
|
||||
let sin_off = sin(h.angle_offset);
|
||||
for (var i = 0u; i < f.n_families; i++) {
|
||||
if check_family(v.xz, f.families[i], cos_off, sin_off, h.scale) {
|
||||
if check_family(v.xz, ddx_xz, ddy_xz, f.families[i], cos_off, sin_off, h.scale) {
|
||||
return h.color;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user