perf(render): hoist per-wire constants to a storage buffer (native)
Every wire segment is one instance, and each carried the wire's color, line-weight, dash pattern and draw-depth (~44 B) inline — re-fetched per segment even though they're constant along the wire. On native, move those into a per-wire `WireConst` storage buffer (group 1) indexed by a `wire_id` on the instance. The instance drops from 104 B to 60 B (~42 %) across every entity's wires, and the shared constants are fetched once per wire (cached) instead of once per segment — less vertex-attribute bandwidth and less GPU upload on rebuilds. WebGL2 has no vertex-stage storage buffers, so the wasm build keeps the original self-contained fat instance and wire.wgsl; native uses the new slim instance, wire_indexed.wgsl, and a group-1 bind group built per batch in WireGpu::from_run / from_batch. Visual output is unchanged (colors are now full f32 rather than 8-bit, if anything more precise). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0db513e07a
commit
0317dad14e
3 changed files with 515 additions and 102 deletions
|
|
@ -32,6 +32,11 @@ pub struct Pipeline {
|
|||
/// Same shader as wire_pipeline but depth_compare=Greater, depth_write_enabled=false.
|
||||
/// Used to draw ghost copies of selected wires through occluding geometry.
|
||||
wire_xray_pipeline: wgpu::RenderPipeline,
|
||||
/// Layout for the per-wire `WireConst` storage buffer (group 1 of the wire /
|
||||
/// xray pipelines). `Some` on native; `None` on web (fat instance, no
|
||||
/// storage). Passed to `WireGpu::from_run` / `from_batch` so they can build
|
||||
/// each batch's per-wire bind group.
|
||||
wire_const_bgl: Option<wgpu::BindGroupLayout>,
|
||||
hatch_pipeline: wgpu::RenderPipeline,
|
||||
/// Phase 4-B — single-draw batched hatch pipeline. Per-instance
|
||||
/// data lives in storage buffers; one draw call covers every
|
||||
|
|
@ -181,9 +186,19 @@ impl Pipeline {
|
|||
});
|
||||
|
||||
// ── Wire pipeline ──────────────────────────────────────────────────
|
||||
// Native: per-wire constants live in a storage buffer (group 1), so the
|
||||
// slim instance only carries per-segment data. WebGL2 has no
|
||||
// vertex-stage storage buffers, so the wasm build keeps the fat instance
|
||||
// and a single bind group.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let wire_const_bgl = wire_gpu::WireConst::bind_group_layout(device);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let wire_bgls: &[&wgpu::BindGroupLayout] = &[&frame_bgl, &wire_const_bgl];
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let wire_bgls: &[&wgpu::BindGroupLayout] = &[&frame_bgl];
|
||||
let wire_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("wire.pipeline_layout"),
|
||||
bind_group_layouts: &[&frame_bgl],
|
||||
bind_group_layouts: wire_bgls,
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
|
|
@ -192,6 +207,11 @@ impl Pipeline {
|
|||
|
||||
let wire_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("wire.shader"),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
|
||||
"../../shaders/wire_indexed.wgsl"
|
||||
))),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
|
||||
"../../shaders/wire.wgsl"
|
||||
))),
|
||||
|
|
@ -909,6 +929,10 @@ impl Pipeline {
|
|||
Self {
|
||||
wire_pipeline,
|
||||
wire_xray_pipeline,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
wire_const_bgl: Some(wire_const_bgl),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wire_const_bgl: None,
|
||||
hatch_pipeline,
|
||||
hatch_batched_pipeline,
|
||||
image_pipeline,
|
||||
|
|
@ -983,7 +1007,7 @@ impl Pipeline {
|
|||
{
|
||||
j += 1;
|
||||
}
|
||||
batches.extend(WireGpu::from_run(device, &wires[i..j], depth_map, scissor, mesh_edge));
|
||||
batches.extend(WireGpu::from_run(device, &wires[i..j], depth_map, scissor, mesh_edge, self.wire_const_bgl.as_ref()));
|
||||
i = j;
|
||||
}
|
||||
self.gpu_wires = batches;
|
||||
|
|
@ -1044,7 +1068,8 @@ impl Pipeline {
|
|||
if let Some(h) = hover {
|
||||
push(h.value(), WireModel::HOVER, wires, &mut out);
|
||||
}
|
||||
self.gpu_selected_wires = WireGpu::from_run(device, &out, depth_map, None, false);
|
||||
self.gpu_selected_wires =
|
||||
WireGpu::from_run(device, &out, depth_map, None, false, self.wire_const_bgl.as_ref());
|
||||
}
|
||||
|
||||
/// Upload the live overlay (command preview / interim / grip-drag) wires.
|
||||
|
|
@ -1059,7 +1084,7 @@ impl Pipeline {
|
|||
self.gpu_preview_wires = if wires.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
WireGpu::from_run(device, wires, depth_map, None, false)
|
||||
WireGpu::from_run(device, wires, depth_map, None, false, self.wire_const_bgl.as_ref())
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1141,7 +1166,8 @@ impl Pipeline {
|
|||
) {
|
||||
// Edge buffer is always built from `face3d_wires`, so 3DFACE
|
||||
// outlines stay on the screen regardless of mode.
|
||||
self.gpu_face3d_edges = WireGpu::from_batch(device, face3d_wires, depth_map);
|
||||
self.gpu_face3d_edges =
|
||||
WireGpu::from_batch(device, face3d_wires, depth_map, self.wire_const_bgl.as_ref());
|
||||
// Fill buffer split: 3D quads + PolyfaceMesh / PolygonMesh face
|
||||
// tris go to `vertex_buffer_3d` (gated by `keep_3d_mesh_fills`);
|
||||
// 2D fills (text-LOD greek, MultiLeader background) go to
|
||||
|
|
@ -1612,6 +1638,9 @@ impl Pipeline {
|
|||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
for edges in &self.gpu_face3d_edges {
|
||||
if edges.instance_count > 0 {
|
||||
if let Some(bg) = &edges.const_bind_group {
|
||||
pass.set_bind_group(1, bg.as_ref(), &[]);
|
||||
}
|
||||
pass.set_vertex_buffer(0, edges.instance_buffer.slice(..));
|
||||
pass.draw(0..6, 0..edges.instance_count);
|
||||
}
|
||||
|
|
@ -1670,6 +1699,9 @@ impl Pipeline {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
if let Some(bg) = &wire.const_bind_group {
|
||||
pass.set_bind_group(1, bg.as_ref(), &[]);
|
||||
}
|
||||
pass.set_vertex_buffer(0, wire.instance_buffer.slice(..));
|
||||
pass.draw(0..6, 0..wire.instance_count);
|
||||
}
|
||||
|
|
@ -1685,6 +1717,9 @@ impl Pipeline {
|
|||
pass.set_pipeline(&self.wire_xray_pipeline);
|
||||
for pw in &self.gpu_preview_wires {
|
||||
if pw.instance_count > 0 {
|
||||
if let Some(bg) = &pw.const_bind_group {
|
||||
pass.set_bind_group(1, bg.as_ref(), &[]);
|
||||
}
|
||||
pass.set_vertex_buffer(0, pw.instance_buffer.slice(..));
|
||||
pass.draw(0..6, 0..pw.instance_count);
|
||||
}
|
||||
|
|
@ -1775,6 +1810,9 @@ impl Pipeline {
|
|||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
for wire in &self.gpu_selected_wires {
|
||||
if wire.instance_count > 0 {
|
||||
if let Some(bg) = &wire.const_bind_group {
|
||||
pass.set_bind_group(1, bg.as_ref(), &[]);
|
||||
}
|
||||
pass.set_vertex_buffer(0, wire.instance_buffer.slice(..));
|
||||
pass.draw(0..6, 0..wire.instance_count);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue