From 8552450aa8c3b142070ecde287d40fe29dc0c2b4 Mon Sep 17 00:00:00 2001 From: Panos <25664832+mechpanos@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:57:24 +0300 Subject: [PATCH] fix: chunk block-wire vertex buffers BlockWireGpu::from_wires built one unbounded vertex buffer per (source_id, mesh_edge) group. On a block-heavy drawing that reached ~505 MiB in a single create_buffer_init, which fails on a 2 GB card; wgpu then returns an invalid buffer and the unchecked get_mapped_range_mut() inside create_buffer_init panics. The instance buffer in the same function was already chunked. Convert the vertex buffer the same way, matching the solid-mesh chunking in mesh_gpu.rs (#203) and the fill chunking in face3d_gpu.rs (#358), including mesh_gpu.rs's clamp: max_buffer_size is an API validation limit, not physical VRAM, and NVIDIA reports several GB on a 2 GB card. Chunk sizes are rounded down to a multiple of 6 so a segment's quad never straddles two buffers -- the shader derives its corner from vertex_index % 6 and every draw restarts that counter at zero. Instance buffers are hoisted out so they are built once and reused across vertex chunks instead of re-uploaded per chunk. Co-Authored-By: Claude Opus 5 --- src/scene/pipeline/wire_gpu.rs | 67 +++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/scene/pipeline/wire_gpu.rs b/src/scene/pipeline/wire_gpu.rs index 351889ddb..458bfe7c4 100644 --- a/src/scene/pipeline/wire_gpu.rs +++ b/src/scene/pipeline/wire_gpu.rs @@ -740,11 +740,33 @@ impl BlockWireGpu { }; vertices.extend_from_slice(&[vertex; 6]); } - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("block_wire.vertices"), - contents: bytemuck::cast_slice(&vertices), - usage: wgpu::BufferUsages::VERTEX, - }); + // Split vertex data into buffers small enough to allocate on + // low-VRAM devices. Same scheme as the solid-mesh chunking in + // `mesh_gpu.rs` (#203) and the fill chunking in `face3d_gpu.rs` + // (#358); the block-wire path was never converted, so a + // block-heavy drawing produced one multi-hundred-MB buffer. + // `max_buffer_size` is an API validation limit, not physical VRAM + // (NVIDIA reports several GB on a 2 GB card), so clamp it the way + // `mesh_gpu.rs` already does. + let vertex_budget = ((device.limits().max_buffer_size as usize / 10) * 9) + .min(32 * 1024 * 1024); + let vsize = std::mem::size_of::(); + // Round down to a multiple of 6: each segment expands to six + // vertices and must not straddle two chunks. + let max_verts = ((vertex_budget / vsize).max(6) / 6) * 6; + let vertex_chunks: Vec<(wgpu::Buffer, u32)> = vertices + .chunks(max_verts) + .map(|chunk| { + ( + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("block_wire.vertices"), + contents: bytemuck::cast_slice(chunk), + usage: wgpu::BufferUsages::VERTEX, + }), + chunk.len() as u32, + ) + }) + .collect(); let const_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("block_wire.const"), contents: bytemuck::bytes_of(&constant), @@ -791,19 +813,28 @@ impl BlockWireGpu { let max_instances = ((device.limits().max_buffer_size as usize / 10) * 9 / std::mem::size_of::()) .max(1); - for chunk in instances.chunks(max_instances) { - out.push(Self { - vertex_buffer: vertex_buffer.clone(), - instance_buffer: instance_buffer_mapped( - device, - "block_wire.instances", - chunk, - ), - vertex_count: vertices.len() as u32, - instance_count: chunk.len() as u32, - is_3d_mesh_edge: mesh_edge, - const_bind_group: const_bind_group.clone(), - }); + // Build each instance buffer once and reuse it across vertex + // chunks, rather than re-uploading it per chunk. + let instance_chunks: Vec<(wgpu::Buffer, u32)> = instances + .chunks(max_instances) + .map(|chunk| { + ( + instance_buffer_mapped(device, "block_wire.instances", chunk), + chunk.len() as u32, + ) + }) + .collect(); + for (vertex_buffer, vertex_count) in &vertex_chunks { + for (instance_buffer, instance_count) in &instance_chunks { + out.push(Self { + vertex_buffer: vertex_buffer.clone(), + instance_buffer: instance_buffer.clone(), + vertex_count: *vertex_count, + instance_count: *instance_count, + is_3d_mesh_edge: mesh_edge, + const_bind_group: const_bind_group.clone(), + }); + } } } out