From 522b123d8265983cf65c2e3d499e624918e13e04 Mon Sep 17 00:00:00 2001 From: Hakan Date: Sat, 23 May 2026 09:47:07 +0300 Subject: [PATCH] perf(wire): upload vertex buffers via mapped_at_creation Step 1 of the wire pipeline cold-open optimization plan. Replaces `create_buffer_init` (intermediate staging-buffer memcpy) with a `mapped_at_creation` buffer the build path writes into directly. Cuts one alloc + one memcpy per wire buffer; on cold open with hundreds of MB of wire data this shows up immediately as lower peak memory and faster upload. Guarded against size-0 buffers by allocating a single-vertex placeholder when the wire is empty (the renderer already skips draws on vertex_count below the per-segment minimum). Co-Authored-By: Claude Opus 4.7 --- src/scene/pipeline/wire_gpu.rs | 43 +++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/scene/pipeline/wire_gpu.rs b/src/scene/pipeline/wire_gpu.rs index 826e4ad93..692c68d27 100644 --- a/src/scene/pipeline/wire_gpu.rs +++ b/src/scene/pipeline/wire_gpu.rs @@ -25,7 +25,34 @@ use crate::scene::wire_model::WireModel; use iced::wgpu; -use iced::wgpu::util::DeviceExt; + +/// Allocate a VERTEX buffer with `mapped_at_creation` and write `data` directly +/// into the mapped slice. Skips the intermediate staging copy that +/// `create_buffer_init` performs and avoids holding a second `Vec` worth of +/// memory during upload — meaningful on cold open where wire buffers can run +/// into the hundreds of MB. +fn vertex_buffer_mapped( + device: &wgpu::Device, + label: &str, + data: &[WireVertex], +) -> wgpu::Buffer { + let bytes: &[u8] = bytemuck::cast_slice(data); + // wgpu rejects size-0 buffers; the renderer already guards `vertex_count` + // before issuing a draw, so a placeholder allocation is fine here. + let size = bytes.len().max(std::mem::size_of::()) as u64; + let buf = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage: wgpu::BufferUsages::VERTEX, + mapped_at_creation: true, + }); + { + let mut view = buf.slice(..).get_mapped_range_mut(); + view[..bytes.len()].copy_from_slice(bytes); + } + buf.unmap(); + buf +} // ── Vertex layout ───────────────────────────────────────────────────────── @@ -222,11 +249,8 @@ impl WireGpu { .chunks(MAX_VERTS) .enumerate() .map(|(i, chunk)| { - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("wire.batch.vbuf.{i}")), - contents: bytemuck::cast_slice(chunk), - usage: wgpu::BufferUsages::VERTEX, - }); + let label = format!("wire.batch.vbuf.{i}"); + let vertex_buffer = vertex_buffer_mapped(device, &label, chunk); Self { vertex_buffer, vertex_count: chunk.len() as u32, @@ -323,11 +347,8 @@ impl WireGpu { vertices.push(make(0.0, 1.0)); } - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("wire.vbuf.{}", wire.name)), - contents: bytemuck::cast_slice(&vertices), - usage: wgpu::BufferUsages::VERTEX, - }); + let label = format!("wire.vbuf.{}", wire.name); + let vertex_buffer = vertex_buffer_mapped(device, &label, &vertices); Self { vertex_buffer,