refactor(renderer): select paths by GPU limits

Keep storage fast paths on capable adapters and use packed or texture fallbacks only when device limits require them.\n\nCompact mesh-edge varyings to avoid WebGL shader rejection.
This commit is contained in:
Hakan Seven committed 2026-07-29 12:49:41 +03:00
1 parent b9799e4421
commit 93b9a840ff
16 files changed
+1010 -181

No files matched your search

+3 -3
View File
@@ -1,12 +1,12 @@
// Hatch shader (WebGL2) — texture-backed, UNCAPPED variant of wipeout.wgsl.
//
// WebGL2 has no vertex/fragment storage buffers, so the batched storage-buffer
// renderer (hatch.wgsl) is disabled on wasm. This shader keeps the exact
// Devices without vertex/fragment storage buffers cannot use the batched
// storage-buffer renderer (hatch.wgsl). This shader keeps the exact
// wipeout.wgsl hatch algorithm (in_polygon + per-family line/dash/dot + solid +
// gradient) but reads the variable-length boundary / family / dash arrays from a
// single RGBA32F data texture via textureLoad instead of fixed-size uniforms —
// removing the MAX_FAMILIES (16) / MAX_HATCH_BOUNDARY_VERTS (1024) / MAX_DASHES
// (128) caps of the uniform path. Native (hatch.wgsl) is untouched.
// (128) caps of the uniform path.
//
// Data texture layout (row-major, width = h.tex_width, one vec4 per texel):
// [ 0 .. vcount ) boundary verts, .xy = local XY, NaN = break
+45 -13
View File
@@ -112,6 +112,35 @@ struct VertexOut {
@location(15) uv_normal: vec2<f32>,
};
struct EdgeVertexIn {
@location(0) position: vec3<f32>,
@location(2) color: vec4<f32>,
@location(3) position_low: vec3<f32>,
};
struct EdgeVertexOut {
@builtin(position) clip_pos: vec4<f32>,
@location(0) color: vec4<f32>,
};
fn relative_position(
position: vec3<f32>,
position_low: vec3<f32>,
instance: MeshInstance,
) -> vec3<f32> {
let world_high = vec3<f32>(
dot(instance.model_row_0.xyz, position) + instance.model_row_0.w,
dot(instance.model_row_1.xyz, position) + instance.model_row_1.w,
dot(instance.model_row_2.xyz, position) + instance.model_row_2.w,
);
let world_low = vec3<f32>(
dot(instance.model_row_0.xyz, position_low),
dot(instance.model_row_1.xyz, position_low),
dot(instance.model_row_2.xyz, position_low),
) + instance.translation_low.xyz;
return (world_high - u.eye_high) + (world_low - u.eye_low);
}
@vertex
fn vs_main(
v: VertexIn,
@@ -119,17 +148,7 @@ fn vs_main(
) -> VertexOut {
var out: VertexOut;
let instance = mesh_instances[instance_index];
let world_high = vec3<f32>(
dot(instance.model_row_0.xyz, v.position) + instance.model_row_0.w,
dot(instance.model_row_1.xyz, v.position) + instance.model_row_1.w,
dot(instance.model_row_2.xyz, v.position) + instance.model_row_2.w,
);
let world_low = vec3<f32>(
dot(instance.model_row_0.xyz, v.position_low),
dot(instance.model_row_1.xyz, v.position_low),
dot(instance.model_row_2.xyz, v.position_low),
) + instance.translation_low.xyz;
let rel = (world_high - u.eye_high) + (world_low - u.eye_low);
let rel = relative_position(v.position, v.position_low, instance);
out.clip_pos = u.view_rot * vec4<f32>(rel, 1.0);
out.color = v.color;
out.normal = normalize(vec3<f32>(
@@ -154,6 +173,19 @@ fn vs_main(
return out;
}
@vertex
fn vs_edge(
v: EdgeVertexIn,
@builtin(instance_index) instance_index: u32,
) -> EdgeVertexOut {
var out: EdgeVertexOut;
let instance = mesh_instances[instance_index];
let rel = relative_position(v.position, v.position_low, instance);
out.clip_pos = u.view_rot * vec4<f32>(rel, 1.0);
out.color = v.color;
return out;
}
@fragment
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
var n: vec3<f32>;
@@ -327,14 +359,14 @@ fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
// Edge fragment (LineList): flat entity colour, no lighting. Used for the
// wireframe and hidden-line edge passes so lines read at their true colour.
@fragment
fn fs_edge(in: VertexOut) -> @location(0) vec4<f32> {
fn fs_edge(in: EdgeVertexOut) -> @location(0) vec4<f32> {
return vec4<f32>(in.color.rgb, 1.0);
}
// Edge fragment for filled render modes: force black so edges frame the shaded
// fill regardless of the solid's colour.
@fragment
fn fs_edge_black(in: VertexOut) -> @location(0) vec4<f32> {
fn fs_edge_black(_in: EdgeVertexOut) -> @location(0) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
+8
View File
@@ -263,6 +263,11 @@ fn cap_clipped(cap: vec2<f32>, cap_ends: vec3<f32>) -> bool {
}
@fragment fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
// Negative pattern length is the persistent-arena tombstone sentinel.
// Discard before cap/alpha work so deleted slabs cannot write color/depth.
if in.pattern_length < 0.0 {
discard;
}
if cap_clipped(in.cap, in.cap_ends) {
discard;
}
@@ -287,6 +292,9 @@ fn cap_clipped(cap: vec2<f32>, cap_ends: vec3<f32>) -> bool {
// mesh reads as a shaded surface framed by black edges. Keeps the dash/LOD
// logic identical to `fs_main`; only the RGB is forced to black.
@fragment fn fs_black(in: VertexOut) -> @location(0) vec4<f32> {
if in.pattern_length < 0.0 {
discard;
}
if cap_clipped(in.cap, in.cap_ends) {
discard;
}
+8
View File
@@ -220,6 +220,11 @@ fn cap_clipped(cap: vec2<f32>, cap_ends: vec3<f32>) -> bool {
}
@fragment fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
// Negative pattern length is the persistent-arena tombstone sentinel.
// Discard before cap/alpha work so deleted slabs cannot write color/depth.
if in.pattern_length < 0.0 {
discard;
}
if cap_clipped(in.cap, in.cap_ends) {
discard;
}
@@ -238,6 +243,9 @@ fn cap_clipped(cap: vec2<f32>, cap_ends: vec3<f32>) -> bool {
// mesh reads as a shaded surface framed by black edges. Keeps the dash/LOD
// logic identical to `fs_main`; only the RGB is forced to black.
@fragment fn fs_black(in: VertexOut) -> @location(0) vec4<f32> {
if in.pattern_length < 0.0 {
discard;
}
if cap_clipped(in.cap, in.cap_ends) {
discard;
}