perf(wire): switch wire pipeline to instanced rendering

Step 3 of the wire pipeline cold-open optimization plan — the biggest
single lever.

Each segment used to upload as 6 vertices × 96 B = 576 B with the segment
payload (pos_a/pos_b/color/half_width/distance/pattern_length/pat0/pat1)
duplicated across all six corners of the quad. Wire pipeline now uploads
one INSTANCE per segment (88 B) and the vertex shader derives `which_end`
and `side` from `@builtin(vertex_index)` against a 6-entry corner table.

Net effect on cold open: ~6.5× less wire vertex memory and PCIe upload,
plus a smaller Vec to allocate and copy on the CPU side.

Wire instance attributes also collapse distance_a / distance_b into a
single mix() inside the vertex shader, so per-segment distance still
interpolates correctly for the GPU-side linetype dash test.

Touches the wire, wire_xray, and face3d-edges draw passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Hakan 2026-05-23 09:52:23 +03:00
commit 4b4f5c94cd
3 changed files with 123 additions and 133 deletions

View file

@ -158,7 +158,7 @@ impl Pipeline {
vertex: wgpu::VertexState {
module: &wire_shader,
entry_point: Some("vs_main"),
buffers: &[wire_gpu::WireVertex::layout()],
buffers: &[wire_gpu::WireInstance::layout()],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
primitive: wgpu::PrimitiveState {
@ -201,7 +201,7 @@ impl Pipeline {
vertex: wgpu::VertexState {
module: &wire_shader,
entry_point: Some("vs_main"),
buffers: &[wire_gpu::WireVertex::layout()],
buffers: &[wire_gpu::WireInstance::layout()],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
primitive: wgpu::PrimitiveState {
@ -1073,9 +1073,9 @@ impl Pipeline {
pass.set_pipeline(&self.wire_pipeline);
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
for edges in &self.gpu_face3d_edges {
if edges.vertex_count >= 6 {
pass.set_vertex_buffer(0, edges.vertex_buffer.slice(..));
pass.draw(0..edges.vertex_count, 0..1);
if edges.instance_count > 0 {
pass.set_vertex_buffer(0, edges.instance_buffer.slice(..));
pass.draw(0..6, 0..edges.instance_count);
}
}
}
@ -1109,7 +1109,7 @@ impl Pipeline {
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
let mut scissor_active = false;
for (i, wire) in self.gpu_wires.iter().enumerate() {
if wire.vertex_count < 6 {
if wire.instance_count == 0 {
continue;
}
match self.wire_pixel_scissors.get(i) {
@ -1123,8 +1123,8 @@ impl Pipeline {
}
_ => {}
}
pass.set_vertex_buffer(0, wire.vertex_buffer.slice(..));
pass.draw(0..wire.vertex_count, 0..1);
pass.set_vertex_buffer(0, wire.instance_buffer.slice(..));
pass.draw(0..6, 0..wire.instance_count);
}
if scissor_active {
pass.set_scissor_rect(0, 0, vp.width, vp.height);
@ -1213,9 +1213,9 @@ impl Pipeline {
pass.set_pipeline(&self.wire_xray_pipeline);
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
for wire in &self.gpu_selected_wires {
if wire.vertex_count >= 6 {
pass.set_vertex_buffer(0, wire.vertex_buffer.slice(..));
pass.draw(0..wire.vertex_count, 0..1);
if wire.instance_count > 0 {
pass.set_vertex_buffer(0, wire.instance_buffer.slice(..));
pass.draw(0..6, 0..wire.instance_count);
}
}
}