fix: no GPU allocation path maps, however small the buffer

The previous commit converted the allocations that scale with the
drawing and deliberately left the small fixed-size ones -- uniforms, the
viewcube's static geometry, per-image and per-wipeout quads -- reasoning
that an allocation failure was not a realistic outcome for a few dozen
bytes.

That reasoning was wrong, and the log says so precisely. On the
reproducer the vertex buffers degraded exactly as intended:

  [gpu] uncaptured ... Out of Memory
  [gpu] uncaptured ... In Queue::write_buffer
          Buffer with 'block_wire.vertices' label is invalid
  ... errors #4 #8 #16 #32 #64 #128, session kept alive

and then the process died here:

  thread 'main' panicked at wgpu_core.rs:2253:
  Error in Buffer::get_mapped_range: Validation Error
  Caused by: Buffer with 'block_wire.const' label is invalid

'block_wire.const' is a single uniform struct. Once the device is out of
memory every allocation fails, including a 32-byte one, and
create_buffer_init maps it and panics. Size was never the relevant
property -- whether the path maps is.

So every remaining allocation goes through gpu_upload too: block-wire
const, the viewcube's seven buffers, the blit and clip-boundary buffers,
image instances/params/vertices, mesh material params and surface and the
empty index stub, the texture-hatch uniform, and all five wipeout
buffers. `grep -r create_buffer_init src` now finds only a comment, and
no mapped_at_creation:true remains outside gpu_upload's own docs.

`queue` is threaded through the last holdouts: upload_wipeouts,
upload_clip_boundary, MeshBatchStubs::new and
create_material_bind_group_from_resources.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-09-04 16:21:08 +03:00
1 parent 0d11421dea
commit 6747d7125e
8 files changed
+181 -129

No files matched your search

+22 -15
View File
@@ -16,7 +16,6 @@ pub mod wire_arena;
pub mod wire_gpu;
use iced::wgpu;
use iced::wgpu::util::DeviceExt;
use iced::{Rectangle, Size};
pub use face3d_gpu::Face3DGpu;
@@ -2122,11 +2121,13 @@ impl Pipeline {
// UV crop uniform: [uv_offset_x, uv_offset_y, uv_scale_x, uv_scale_y]
// padded to 16 bytes (std140 vec2 alignment). Defaulted to the
// identity crop (offset 0, scale 1) for the common on-canvas case.
let blit_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("blit.uniform_buffer"),
contents: bytemuck::cast_slice(&[0.0f32, 0.0, 1.0, 1.0]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let blit_uniform_buffer = gpu_upload::upload_buffer(
device,
queue,
"blit.uniform_buffer",
&[0.0f32, 0.0, 1.0, 1.0],
wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
);
let blit_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("blit.pipeline_layout"),
@@ -2969,8 +2970,7 @@ impl Pipeline {
/// so the mask pipeline stamps it straight into the stencil with `Invert`
/// (even-odd fill → interior marked, any convexity). Empty input clears the
/// boundary so the viewport renders unclipped (its render rectangle clips).
pub fn upload_clip_boundary(&mut self, device: &wgpu::Device, boundary_ndc: &[[f32; 2]]) {
use wgpu::util::DeviceExt;
pub fn upload_clip_boundary(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, boundary_ndc: &[[f32; 2]]) {
if boundary_ndc.len() < 3 {
self.clip_boundary = None;
return;
@@ -2986,11 +2986,13 @@ impl Pipeline {
self.clip_boundary = None;
return;
}
let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("clip_boundary.vbuf"),
contents: bytemuck::cast_slice(&verts),
usage: wgpu::BufferUsages::VERTEX,
});
let vbuf = gpu_upload::upload_buffer(
device,
queue,
"clip_boundary.vbuf",
&verts,
wgpu::BufferUsages::VERTEX,
);
self.clip_boundary = Some((vbuf, (verts.len() / 2) as u32));
}
@@ -3378,13 +3380,18 @@ impl Pipeline {
self.hatch_gpu.upload_preview(device, queue, hatches);
}
pub fn upload_wipeouts(&mut self, device: &wgpu::Device, wipeouts: &[HatchModel]) {
pub fn upload_wipeouts(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
wipeouts: &[HatchModel],
) {
let renderable: Vec<HatchModel> = wipeouts
.iter()
.filter(|h| h.boundary.len() >= 3)
.cloned()
.collect();
self.gpu_wipeouts = WipeoutGpu::from_models(device, &renderable, &self.wipeout_bgl1);
self.gpu_wipeouts = WipeoutGpu::from_models(device, queue, &renderable, &self.wipeout_bgl1);
}
pub fn upload_images(