forked from 1b-agent/opencadstudio-upstream
perf: store block-wire segments once instead of six times
The layout-switch OOM names its buffer: `Buffer with 'block_wire.vertices' label is invalid`, right after `uncaptured wgpu error #1 … Out of Memory`. That buffer is the largest thing this renderer allocates, and five sixths of it is duplication. A segment reaches the shader once per corner of its quad, and the packed path satisfies that by writing the whole 60-byte record six times — 360 bytes a segment — with `vs_main` recovering the corner from `vertex_index % 6`. Six identical copies, on a 2 GB card. `block_wire_storage.wgsl` puts the segments in a read-only storage buffer indexed by `vertex_index / 6` instead, so each record is stored once. The geometry vertex buffer disappears; the placements move to slot 0 and the segments arrive through the definition's bind group, which becomes per-chunk because each chunk binds its own slice. This is the pattern `wire_indexed.wgsl` already uses for per-wire constants, down to the capability gate: `supports_wire_storage` asks for exactly one vertex-stage storage binding, which is exactly what this needs. Where it is false — WebGL, the forced compatibility renderer — the packed shader and its vertex buffer are built as before. `Segment` is declared with scalar members rather than `vec3<f32>`, which would align to 16 in storage and reintroduce the padding this removes. A test validates the shader through naga and asserts the struct's stride equals `size_of::<BlockWireVertex>()`, because a layout mismatch would otherwise surface only as garbage geometry on a real device. `block-wire-geometry` reports the bytes uploaded and what the packed path would have taken, so the saving is measured rather than assumed. The cache value becomes `Arc<Vec<BlockGeometryChunk>>` while keeping the translation-aware key from #1006 — both modes still have to invalidate when the base instance moves, because the records hold its world coordinates either way. `const_bgl` and the mode are decided together when the pipeline is built, so they travel as one `BlockWireTarget` rather than pushing `from_wires` past the argument limit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e4f755e6f
commit
6599b82776
4 files changed
+634
-107
No files matched your search
+62
-20
@@ -94,6 +94,10 @@ pub struct Pipeline {
|
||||
/// compatibility mode. Passed to `WireGpu::from_run` / `from_batch`.
|
||||
pub(crate) wire_const_bgl: Option<wgpu::BindGroupLayout>,
|
||||
block_wire_const_bgl: wgpu::BindGroupLayout,
|
||||
/// Which block-wire pipeline was built. Decides whether a definition's
|
||||
/// segments go to a vertex buffer six times over or to a storage buffer
|
||||
/// once.
|
||||
block_wire_mode: wire_gpu::WirePipelineMode,
|
||||
wipeout_pipeline: wgpu::RenderPipeline,
|
||||
/// Capability-selected hatch renderer. Storage and texture transports are
|
||||
/// private backends behind one upload/LOD/draw lifecycle.
|
||||
@@ -581,7 +585,8 @@ impl Pipeline {
|
||||
bind_group_layouts: &wire_bgls,
|
||||
immediate_size: 0,
|
||||
});
|
||||
let block_wire_const_bgl = wire_gpu::block_const_bind_group_layout(device);
|
||||
let block_wire_const_bgl =
|
||||
wire_gpu::block_const_bind_group_layout(device, wire_mode.uses_storage());
|
||||
let block_wire_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("block_wire.pipeline_layout"),
|
||||
bind_group_layouts: &[Some(&frame_bgl), Some(&block_wire_const_bgl)],
|
||||
@@ -602,9 +607,13 @@ impl Pipeline {
|
||||
});
|
||||
let block_wire_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("block_wire.shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
|
||||
"../../shaders/block_wire.wgsl"
|
||||
))),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
if wire_mode.uses_storage() {
|
||||
include_str!("../../shaders/block_wire_storage.wgsl")
|
||||
} else {
|
||||
include_str!("../../shaders/block_wire.wgsl")
|
||||
},
|
||||
)),
|
||||
});
|
||||
|
||||
// Stencil test shared by every paper content pipeline: draw only where
|
||||
@@ -880,6 +889,14 @@ impl Pipeline {
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
let block_wire_buffers: &[wgpu::VertexBufferLayout<'_>] = if wire_mode.uses_storage() {
|
||||
&[wire_gpu::BlockWireInstance::layout()]
|
||||
} else {
|
||||
&[
|
||||
wire_gpu::BlockWireVertex::layout(),
|
||||
wire_gpu::BlockWireInstance::layout(),
|
||||
]
|
||||
};
|
||||
let make_block_wire_pipeline = |
|
||||
label: &'static str,
|
||||
fragment: &'static str,
|
||||
@@ -892,10 +909,10 @@ impl Pipeline {
|
||||
vertex: wgpu::VertexState {
|
||||
module: &block_wire_shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[
|
||||
wire_gpu::BlockWireVertex::layout(),
|
||||
wire_gpu::BlockWireInstance::layout(),
|
||||
],
|
||||
// Storage mode has no geometry vertex buffer: the
|
||||
// instances become slot 0 and the segments arrive through
|
||||
// the bind group.
|
||||
buffers: block_wire_buffers,
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
@@ -2215,6 +2232,7 @@ impl Pipeline {
|
||||
block_wire_xray_pipeline,
|
||||
wire_const_bgl,
|
||||
block_wire_const_bgl,
|
||||
block_wire_mode: wire_mode,
|
||||
wipeout_pipeline,
|
||||
hatch_gpu,
|
||||
image_pipeline,
|
||||
@@ -2349,7 +2367,10 @@ impl Pipeline {
|
||||
wires,
|
||||
depth_map,
|
||||
None,
|
||||
&self.block_wire_const_bgl,
|
||||
wire_gpu::BlockWireTarget {
|
||||
const_bgl: &self.block_wire_const_bgl,
|
||||
mode: self.block_wire_mode,
|
||||
},
|
||||
Some(&mut self.block_geometry),
|
||||
)
|
||||
}
|
||||
@@ -2415,7 +2436,10 @@ impl Pipeline {
|
||||
&block_wires,
|
||||
depth_map,
|
||||
None,
|
||||
&self.block_wire_const_bgl,
|
||||
wire_gpu::BlockWireTarget {
|
||||
const_bgl: &self.block_wire_const_bgl,
|
||||
mode: self.block_wire_mode,
|
||||
},
|
||||
Some(&mut self.block_geometry),
|
||||
);
|
||||
|
||||
@@ -2554,7 +2578,10 @@ impl Pipeline {
|
||||
&selected_blocks,
|
||||
depth_map,
|
||||
Some(tint),
|
||||
&self.block_wire_const_bgl,
|
||||
wire_gpu::BlockWireTarget {
|
||||
const_bgl: &self.block_wire_const_bgl,
|
||||
mode: self.block_wire_mode,
|
||||
},
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
@@ -2566,7 +2593,10 @@ impl Pipeline {
|
||||
&hover_blocks,
|
||||
depth_map,
|
||||
Some(WireModel::HOVER),
|
||||
&self.block_wire_const_bgl,
|
||||
wire_gpu::BlockWireTarget {
|
||||
const_bgl: &self.block_wire_const_bgl,
|
||||
mode: self.block_wire_mode,
|
||||
},
|
||||
None,
|
||||
));
|
||||
self.gpu_selected_block_wires = block_gpu;
|
||||
@@ -4288,10 +4318,7 @@ impl Pipeline {
|
||||
});
|
||||
block_black_active = use_black;
|
||||
}
|
||||
pass.set_bind_group(1, wire.const_bind_group.as_ref(), &[]);
|
||||
pass.set_vertex_buffer(0, wire.vertex_buffer.slice(..));
|
||||
pass.set_vertex_buffer(1, wire.instance_buffer.slice(..));
|
||||
pass.draw(0..wire.vertex_count, 0..wire.instance_count);
|
||||
bind_and_draw_block_wire(&mut pass, wire);
|
||||
}
|
||||
// Live overlay wires (command preview / interim / grip drag) always
|
||||
// on top: the xray pipeline (depth_compare=Always, no depth write)
|
||||
@@ -4480,10 +4507,7 @@ impl Pipeline {
|
||||
if wire.instance_count == 0 {
|
||||
continue;
|
||||
}
|
||||
pass.set_bind_group(1, wire.const_bind_group.as_ref(), &[]);
|
||||
pass.set_vertex_buffer(0, wire.vertex_buffer.slice(..));
|
||||
pass.set_vertex_buffer(1, wire.instance_buffer.slice(..));
|
||||
pass.draw(0..wire.vertex_count, 0..wire.instance_count);
|
||||
bind_and_draw_block_wire(&mut pass, wire);
|
||||
}
|
||||
}
|
||||
if let Some(atlas) = &self.text_atlas_gpu {
|
||||
@@ -4705,6 +4729,24 @@ fn aabb_below_pixel(
|
||||
(max_px - min_px).max(max_py - min_py) < threshold_px
|
||||
}
|
||||
|
||||
/// Bind one block-wire batch and draw it.
|
||||
///
|
||||
/// The two pipeline modes lay the vertex slots out differently — packed puts
|
||||
/// the six-per-segment geometry in slot 0 and the placements in slot 1, while
|
||||
/// storage has no geometry vertex buffer at all and the placements become slot
|
||||
/// 0. Both draw loops go through here so they cannot drift apart.
|
||||
fn bind_and_draw_block_wire(pass: &mut wgpu::RenderPass<'_>, wire: &wire_gpu::BlockWireGpu) {
|
||||
pass.set_bind_group(1, wire.const_bind_group.as_ref(), &[]);
|
||||
match &wire.vertex_buffer {
|
||||
Some(vertices) => {
|
||||
pass.set_vertex_buffer(0, vertices.slice(..));
|
||||
pass.set_vertex_buffer(1, wire.instance_buffer.slice(..));
|
||||
}
|
||||
None => pass.set_vertex_buffer(0, wire.instance_buffer.slice(..)),
|
||||
}
|
||||
pass.draw(0..wire.vertex_count, 0..wire.instance_count);
|
||||
}
|
||||
|
||||
fn create_depth_texture(device: &wgpu::Device, size: Size<u32>) -> wgpu::Texture {
|
||||
device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("viewer.depth_texture"),
|
||||
|
||||
Reference in new issue
Block a user