forked from 1b-agent/opencadstudio-upstream
Hatch/wipeout boundary sub-loops were separated by NaN vertices that the fill shaders detected with `p.x == p.x`. Intel Mesa folds that NaN self-compare to `true` under fast math, so separators read as real vertices, the ray-cast crossing count broke, and fills bled outside their boundary — only on Intel iGPUs. GPU uploads now rewrite the separators to a finite 1e30 sentinel (GPU_BOUNDARY_SEP) and the shaders test `abs(x) < 1e29`, which every driver evaluates identically. The CPU-side NaN representation is unchanged. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
0c578b03f3
commit
805da6428a
7 files changed
+46
-10
No files matched your search
@@ -133,6 +133,13 @@ pub enum HatchPattern {
|
||||
},
|
||||
}
|
||||
|
||||
/// GPU-side separator between disconnected boundary sub-loops. A finite
|
||||
/// magnitude sentinel instead of NaN: shaders detect it with a plain
|
||||
/// `abs(x) < 1e29` compare, which every driver evaluates identically,
|
||||
/// unlike NaN self-comparison (#386, #416). Boundary verts are local
|
||||
/// (anchor-relative), so real coordinates never approach it.
|
||||
pub const GPU_BOUNDARY_SEP: f32 = 1.0e30;
|
||||
|
||||
/// A hatched region defined by a closed polygon boundary.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HatchModel {
|
||||
@@ -152,6 +159,10 @@ pub struct HatchModel {
|
||||
/// World-XY coordinates of the boundary polygon vertices, stored as
|
||||
/// f32 offsets from `world_origin`. NaN-NaN sentinels separate
|
||||
/// disconnected paths and must be preserved un-shifted by consumers.
|
||||
/// GPU uploads rewrite them to [`GPU_BOUNDARY_SEP`] — some drivers
|
||||
/// (Intel Mesa) fold the shader-side `x == x` NaN test to `true`
|
||||
/// under fast math, which turned separators into real vertices and
|
||||
/// bled fills outside their boundary (#386, #416).
|
||||
pub boundary: Arc<Vec<[f32; 2]>>,
|
||||
/// Exact absolute-WCS boundary in f64, set only by the draw commands so a
|
||||
/// typed boundary vertex is persisted without the f32 quantization the
|
||||
|
||||
@@ -216,7 +216,16 @@ impl HatchGpu {
|
||||
for h in hatches {
|
||||
let boundary_offset = boundary.len() as u32;
|
||||
for &[x, y] in h.boundary.iter() {
|
||||
boundary.push([x, y, 0.0, 0.0]);
|
||||
// NaN sub-loop separators become the finite sentinel on the
|
||||
// GPU: Intel drivers fold the shader NaN self-compare to
|
||||
// `true`, turning separators into vertices and bleeding the
|
||||
// fill outside its boundary (#386, #416).
|
||||
if x.is_finite() && y.is_finite() {
|
||||
boundary.push([x, y, 0.0, 0.0]);
|
||||
} else {
|
||||
let s = crate::scene::model::hatch_model::GPU_BOUNDARY_SEP;
|
||||
boundary.push([s, s, 0.0, 0.0]);
|
||||
}
|
||||
}
|
||||
let boundary_count = boundary.len() as u32 - boundary_offset;
|
||||
|
||||
|
||||
@@ -203,13 +203,15 @@ impl HatchWebGpu {
|
||||
|
||||
// ── Pack the data texture: boundary | families | dashes ───────────
|
||||
let mut texels: Vec<[f32; 4]> = Vec::new();
|
||||
// Boundary section (texels 0..vcount). NaN separators preserved (they
|
||||
// survive the RGBA32F fetch byte-for-byte).
|
||||
// Boundary section (texels 0..vcount). NaN separators become the
|
||||
// finite GPU sentinel — the shader NaN self-compare is folded to
|
||||
// `true` on some drivers (#386, #416).
|
||||
for &[x, y] in model.boundary.iter() {
|
||||
if x.is_finite() && y.is_finite() {
|
||||
texels.push([x + drift[0], y + drift[1], 0.0, 0.0]);
|
||||
} else {
|
||||
texels.push([f32::NAN, f32::NAN, 0.0, 0.0]);
|
||||
let s = crate::scene::model::hatch_model::GPU_BOUNDARY_SEP;
|
||||
texels.push([s, s, 0.0, 0.0]);
|
||||
}
|
||||
}
|
||||
let vcount = texels.len() as u32;
|
||||
|
||||
@@ -310,12 +310,14 @@ impl WipeoutGpu {
|
||||
.take(MAX_HATCH_BOUNDARY_VERTS)
|
||||
.enumerate()
|
||||
{
|
||||
// Preserve NaN separators — they signal path breaks to the
|
||||
// in_polygon ray-cast, must not be shifted into finite numbers.
|
||||
// Path-break separators go up as the finite GPU sentinel, not
|
||||
// NaN — the shader NaN self-compare is folded to `true` on some
|
||||
// drivers (#386, #416) — and must not be shifted by `drift`.
|
||||
if x.is_finite() && y.is_finite() {
|
||||
boundary_data.verts[i] = [x + drift[0], y + drift[1], 0.0, 0.0];
|
||||
} else {
|
||||
boundary_data.verts[i] = [f32::NAN, f32::NAN, 0.0, 0.0];
|
||||
let s = crate::scene::model::hatch_model::GPU_BOUNDARY_SEP;
|
||||
boundary_data.verts[i] = [s, s, 0.0, 0.0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,11 @@ struct VOut {
|
||||
// ── Point-in-polygon (ray casting) over a sub-range of BoundaryBuffer ────
|
||||
|
||||
fn valid_vertex(p: vec2<f32>) -> bool {
|
||||
return p.x == p.x && p.y == p.y;
|
||||
// Sub-loop separators are a huge finite sentinel (1e30, see
|
||||
// GPU_BOUNDARY_SEP), not NaN: `x == x` NaN detection gets folded to
|
||||
// `true` by some drivers (Intel Mesa fast math), which made separators
|
||||
// read as real vertices and bleed fills past their boundary (#386, #416).
|
||||
return abs(p.x) < 1.0e29 && abs(p.y) < 1.0e29;
|
||||
}
|
||||
|
||||
fn edge_crosses(p: vec2<f32>, a: vec2<f32>, c: vec2<f32>) -> bool {
|
||||
|
||||
@@ -116,7 +116,11 @@ struct VOut {
|
||||
// ── Point-in-polygon (ray casting) ────────────────────────────────────────
|
||||
|
||||
fn valid_vertex(p: vec2<f32>) -> bool {
|
||||
return p.x == p.x && p.y == p.y;
|
||||
// Sub-loop separators are a huge finite sentinel (1e30, see
|
||||
// GPU_BOUNDARY_SEP), not NaN: `x == x` NaN detection gets folded to
|
||||
// `true` by some drivers (Intel Mesa fast math), which made separators
|
||||
// read as real vertices and bleed fills past their boundary (#386, #416).
|
||||
return abs(p.x) < 1.0e29 && abs(p.y) < 1.0e29;
|
||||
}
|
||||
|
||||
fn edge_crosses(p: vec2<f32>, a: vec2<f32>, c: vec2<f32>) -> bool {
|
||||
|
||||
@@ -118,7 +118,11 @@ struct VOut {
|
||||
// ── Point-in-polygon (ray casting) ────────────────────────────────────────
|
||||
|
||||
fn valid_vertex(p: vec2<f32>) -> bool {
|
||||
return p.x == p.x && p.y == p.y;
|
||||
// Sub-loop separators are a huge finite sentinel (1e30, see
|
||||
// GPU_BOUNDARY_SEP), not NaN: `x == x` NaN detection gets folded to
|
||||
// `true` by some drivers (Intel Mesa fast math), which made separators
|
||||
// read as real vertices and bleed fills past their boundary (#386, #416).
|
||||
return abs(p.x) < 1.0e29 && abs(p.y) < 1.0e29;
|
||||
}
|
||||
|
||||
fn edge_crosses(p: vec2<f32>, a: vec2<f32>, c: vec2<f32>) -> bool {
|
||||
|
||||
Reference in new issue
Block a user