forked from 1b-agent/opencadstudio-upstream
fix: an out-of-memory GPU no longer aborts the process
The crash in #358 was not the out-of-memory itself. wgpu reports OOM as recoverable and the app already installs a handler that logs and keeps the session alive. What killed the process was what happened next: create_buffer_init maps the new buffer unconditionally, and mapping the invalid buffer an OOM device returns panics. That panic unwinds through iced's main-thread redraw, trips the wgpu-hal swapchain assertion in a destructor, and becomes a non-unwinding abort -- no chance to autosave. Every allocation whose size scales with the drawing now goes through src/scene/pipeline/gpu_upload.rs, which creates the buffer unmapped and fills it with Queue::write_buffer. A failed allocation then surfaces as a validation error on the write, which reaches install_gpu_error_handler and degrades the frame -- exactly what that handler's comment promised: "A bad frame must degrade, not end the session." Converted: block-wire vertices and instances, the wire const storage buffer, all three wire-arena allocators, face3d vertices and instances, block-text instances and text vertices, both hatch backends, and the silhouette buffers. mesh_gpu already allocated this way, which is why the mesh path never produced this panic; the rest now match it. No mapped_at_creation:true remains in the renderer. The create_buffer_init calls left are fixed-size or bounded-small -- uniforms, the viewcube's static geometry, per-image and per-wipeout quads -- where an allocation failure is not a realistic outcome. Two trade-offs worth stating: * write_buffer stages through a copy, which mapping at creation avoided. Chunking bounds that transient cost, and the measured block-wire upload got faster with chunking, not slower. * `queue` had to be threaded through the wire, face3d, text and hatch builders and their callers. Mechanical, but it widens the diff. This touches every upload path, so it needs a visual check against a real drawing before it goes upstream. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
771a5b2c5c
commit
d0ef752532
10 files changed
+362
-208
No files matched your search
@@ -20,7 +20,6 @@
|
||||
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
use iced::wgpu;
|
||||
use iced::wgpu::util::DeviceExt;
|
||||
|
||||
pub(crate) fn planar_solid_faces_view(wire: &WireModel, view_dir: glam::Vec3) -> bool {
|
||||
if !wire.fill_is_2d_solid || wire.fill_is_3d {
|
||||
@@ -187,6 +186,7 @@ impl Face3DGpu {
|
||||
/// legacy planar SOLID interior while keeping other 2-D overlays.
|
||||
pub fn from_wires(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
face3d_wires: &[WireModel],
|
||||
all_wires: &[WireModel],
|
||||
keep_3d_mesh_fills: bool,
|
||||
@@ -302,6 +302,7 @@ impl Face3DGpu {
|
||||
|
||||
let (block_chunks_3d, block_chunks_2d) = upload_block_chunks(
|
||||
device,
|
||||
queue,
|
||||
all_wires,
|
||||
keep_3d_mesh_fills,
|
||||
show_2d_solid_fills,
|
||||
@@ -309,8 +310,8 @@ impl Face3DGpu {
|
||||
depth_map,
|
||||
);
|
||||
Self {
|
||||
chunks_3d: upload_chunks(device, &verts_3d, "face3d.vbuf.3d"),
|
||||
chunks_2d: upload_chunks(device, &verts_2d, "face3d.vbuf.2d"),
|
||||
chunks_3d: upload_chunks(device, queue, &verts_3d, "face3d.vbuf.3d"),
|
||||
chunks_2d: upload_chunks(device, queue, &verts_2d, "face3d.vbuf.2d"),
|
||||
block_chunks_3d,
|
||||
block_chunks_2d,
|
||||
}
|
||||
@@ -319,6 +320,7 @@ impl Face3DGpu {
|
||||
|
||||
fn upload_block_chunks(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[WireModel],
|
||||
keep_3d_mesh_fills: bool,
|
||||
show_2d_solid_fills: bool,
|
||||
@@ -415,21 +417,25 @@ fn upload_block_chunks(
|
||||
.chunks(max_instances)
|
||||
.map(|chunk| {
|
||||
(
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("face3d.block.instances"),
|
||||
contents: bytemuck::cast_slice(chunk),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
}),
|
||||
super::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"face3d.block.instances",
|
||||
chunk,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
),
|
||||
chunk.len() as u32,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
for vertex_chunk in vertices.chunks(max_vertices) {
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("face3d.block.vertices"),
|
||||
contents: bytemuck::cast_slice(vertex_chunk),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
let vertex_buffer = super::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"face3d.block.vertices",
|
||||
vertex_chunk,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
);
|
||||
for (instance_buffer, instance_count) in &instance_buffers {
|
||||
let chunk = BlockFace3DChunk {
|
||||
vertex_buffer: vertex_buffer.clone(),
|
||||
@@ -454,6 +460,7 @@ fn upload_block_chunks(
|
||||
/// range never truncates.
|
||||
fn upload_chunks(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
verts: &[Face3DVertex],
|
||||
label: &'static str,
|
||||
) -> Vec<Face3DChunk> {
|
||||
@@ -462,11 +469,13 @@ fn upload_chunks(
|
||||
verts
|
||||
.chunks(max_verts)
|
||||
.map(|c| Face3DChunk {
|
||||
vertex_buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(label),
|
||||
contents: bytemuck::cast_slice(c),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
}),
|
||||
vertex_buffer: super::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
label,
|
||||
c,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
),
|
||||
vertex_count: c.len() as u32,
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
//! Buffer creation that survives an out-of-memory device.
|
||||
//!
|
||||
//! `wgpu::util::DeviceExt::create_buffer_init` — and any
|
||||
//! `mapped_at_creation: true` followed by `get_mapped_range_mut()` — maps the
|
||||
//! new buffer unconditionally. When the allocation fails wgpu hands back an
|
||||
//! *invalid* buffer, and mapping an invalid buffer panics:
|
||||
//!
|
||||
//! ```text
|
||||
//! thread 'main' panicked at wgpu-29.0.4/src/backend/wgpu_core.rs:2253:18:
|
||||
//! Error in Buffer::get_mapped_range: Validation Error
|
||||
//! Caused by: Buffer with 'block_wire.vertices' label is invalid
|
||||
//! ```
|
||||
//!
|
||||
//! That panic unwinds through iced's main-thread redraw, trips the `wgpu-hal`
|
||||
//! swapchain assertion in a destructor, and turns into a **non-unwinding
|
||||
//! abort** — the process dies with the user's unsaved drawing in it.
|
||||
//!
|
||||
//! Writing through the queue instead never maps. A failed allocation produces
|
||||
//! a validation error on the `write_buffer`, which reaches the handler
|
||||
//! installed by `install_gpu_error_handler` and degrades the frame, which is
|
||||
//! what that handler was written for: "A bad frame must degrade, not end the
|
||||
//! session."
|
||||
|
||||
use iced::wgpu;
|
||||
|
||||
/// Create a buffer and fill it without ever mapping it.
|
||||
///
|
||||
/// `COPY_DST` is added to `usage` because the contents arrive via
|
||||
/// `Queue::write_buffer`. Callers pass the same usage flags they would have
|
||||
/// given `create_buffer_init`.
|
||||
///
|
||||
/// Empty `data` still produces a one-element buffer: wgpu rejects zero-sized
|
||||
/// buffers, and every draw path here already guards its instance/vertex count
|
||||
/// before issuing a draw, so a placeholder allocation is harmless.
|
||||
pub fn upload_buffer<T: bytemuck::Pod>(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
label: &str,
|
||||
data: &[T],
|
||||
usage: wgpu::BufferUsages,
|
||||
) -> wgpu::Buffer {
|
||||
let bytes: &[u8] = bytemuck::cast_slice(data);
|
||||
upload_bytes(device, queue, label, bytes, std::mem::size_of::<T>(), usage)
|
||||
}
|
||||
|
||||
/// `upload_buffer` for data already flattened to bytes. `min_size` is the
|
||||
/// placeholder size used when `bytes` is empty.
|
||||
pub fn upload_bytes(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
label: &str,
|
||||
bytes: &[u8],
|
||||
min_size: usize,
|
||||
usage: wgpu::BufferUsages,
|
||||
) -> wgpu::Buffer {
|
||||
const ALIGN: usize = wgpu::COPY_BUFFER_ALIGNMENT as usize;
|
||||
let logical = bytes.len().max(min_size).max(ALIGN);
|
||||
// `write_buffer` copies whole `COPY_BUFFER_ALIGNMENT` units, so both the
|
||||
// allocation and the source slice are rounded up to it. Every vertex and
|
||||
// instance type here is `#[repr(C)]` over 4-byte fields, so the padding
|
||||
// branch below is a safety net rather than a routine cost.
|
||||
let size = logical.div_ceil(ALIGN) * ALIGN;
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(label),
|
||||
size: size as u64,
|
||||
usage: usage | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
if !bytes.is_empty() {
|
||||
if bytes.len().is_multiple_of(ALIGN) {
|
||||
queue.write_buffer(&buffer, 0, bytes);
|
||||
} else {
|
||||
let mut padded = bytes.to_vec();
|
||||
padded.resize(bytes.len().div_ceil(ALIGN) * ALIGN, 0);
|
||||
queue.write_buffer(&buffer, 0, &padded);
|
||||
}
|
||||
}
|
||||
buffer
|
||||
}
|
||||
|
||||
/// Allocate `size` bytes and write `data` into the front of it.
|
||||
///
|
||||
/// For buffers deliberately larger than their initial contents — the wire
|
||||
/// arena reserves headroom so later edits patch in place instead of
|
||||
/// reallocating. Same no-mapping guarantee as [`upload_buffer`]; `usage` must
|
||||
/// already contain `COPY_DST` for the later patches, and it is added here
|
||||
/// regardless.
|
||||
pub fn alloc_with_prefix<T: bytemuck::Pod>(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
label: &str,
|
||||
size: u64,
|
||||
data: &[T],
|
||||
usage: wgpu::BufferUsages,
|
||||
) -> wgpu::Buffer {
|
||||
const ALIGN: u64 = wgpu::COPY_BUFFER_ALIGNMENT;
|
||||
let size = size.max(ALIGN).div_ceil(ALIGN) * ALIGN;
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
usage: usage | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let bytes: &[u8] = bytemuck::cast_slice(data);
|
||||
if !bytes.is_empty() {
|
||||
let unit = ALIGN as usize;
|
||||
if bytes.len().is_multiple_of(unit) {
|
||||
queue.write_buffer(&buffer, 0, bytes);
|
||||
} else {
|
||||
let mut padded = bytes.to_vec();
|
||||
padded.resize(bytes.len().div_ceil(unit) * unit, 0);
|
||||
queue.write_buffer(&buffer, 0, &padded);
|
||||
}
|
||||
}
|
||||
buffer
|
||||
}
|
||||
@@ -167,7 +167,7 @@ impl HatchGpu {
|
||||
.filter(|hatch| hatch.boundary.len() >= 3)
|
||||
.cloned()
|
||||
.collect();
|
||||
*resident = StorageHatchBatch::build(device, &self.bind_group_layout, &renderable);
|
||||
*resident = StorageHatchBatch::build(device, queue, &self.bind_group_layout, &renderable);
|
||||
}
|
||||
HatchBackend::Texture { resident, .. } => {
|
||||
let renderable: Vec<HatchModel> = hatches
|
||||
@@ -198,7 +198,7 @@ impl HatchGpu {
|
||||
.filter(|hatch| hatch.boundary.len() >= 3)
|
||||
.cloned()
|
||||
.collect();
|
||||
*preview = StorageHatchBatch::build(device, &self.bind_group_layout, &renderable);
|
||||
*preview = StorageHatchBatch::build(device, queue, &self.bind_group_layout, &renderable);
|
||||
}
|
||||
HatchBackend::Texture { preview, .. } => {
|
||||
let renderable: Vec<HatchModel> = hatches
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::scene::model::hatch_model::{HatchModel, HatchPattern, PatFamily};
|
||||
use iced::wgpu;
|
||||
use iced::wgpu::util::DeviceExt;
|
||||
|
||||
// ── GPU structs ────────────────────────────────────────────────────────────
|
||||
//
|
||||
@@ -188,6 +187,7 @@ impl StorageHatchBatch {
|
||||
/// Builds whole-hatch chunks within the device's buffer limits.
|
||||
pub(super) fn build(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
bgl: &wgpu::BindGroupLayout,
|
||||
hatches: &[HatchModel],
|
||||
) -> Vec<Self> {
|
||||
@@ -224,12 +224,13 @@ impl StorageHatchBatch {
|
||||
ranges.push(start..hatches.len());
|
||||
ranges
|
||||
.into_iter()
|
||||
.filter_map(|range| Self::build_chunk(device, bgl, &hatches[range]))
|
||||
.filter_map(|range| Self::build_chunk(device, queue, bgl, &hatches[range]))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build_chunk(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
bgl: &wgpu::BindGroupLayout,
|
||||
hatches: &[HatchModel],
|
||||
) -> Option<Self> {
|
||||
@@ -500,42 +501,56 @@ impl StorageHatchBatch {
|
||||
return None;
|
||||
}
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("hatch.vertex"),
|
||||
contents: bytemuck::cast_slice(&verts),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("hatch.index"),
|
||||
contents: bytemuck::cast_slice(&indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
let placement_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("hatch.placements"),
|
||||
contents: bytemuck::cast_slice(&placements),
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("hatch.instances"),
|
||||
contents: bytemuck::cast_slice(&instances),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
let family_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("hatch.families"),
|
||||
contents: bytemuck::cast_slice(&families),
|
||||
usage: wgpu::BufferUsages::STORAGE,
|
||||
});
|
||||
let dash_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("hatch.dashes"),
|
||||
contents: bytemuck::cast_slice(&dashes),
|
||||
usage: wgpu::BufferUsages::STORAGE,
|
||||
});
|
||||
let vertex_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"hatch.vertex",
|
||||
&verts,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
);
|
||||
let index_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"hatch.index",
|
||||
&indices,
|
||||
wgpu::BufferUsages::INDEX,
|
||||
);
|
||||
let placement_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"hatch.placements",
|
||||
&placements,
|
||||
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
let instance_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"hatch.instances",
|
||||
&instances,
|
||||
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
let family_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"hatch.families",
|
||||
&families,
|
||||
wgpu::BufferUsages::STORAGE,
|
||||
);
|
||||
let dash_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"hatch.dashes",
|
||||
&dashes,
|
||||
wgpu::BufferUsages::STORAGE,
|
||||
);
|
||||
|
||||
let visibility_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("hatch.visibility"),
|
||||
contents: bytemuck::cast_slice(&visibility),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
let visibility_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"hatch.visibility",
|
||||
&visibility,
|
||||
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
);
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("hatch.bg"),
|
||||
|
||||
@@ -199,16 +199,20 @@ impl TextureHatch {
|
||||
.into_iter()
|
||||
.map(|[x, y]| HatchVertex { pos: [x, y, 0.0], _pad: 0.0 })
|
||||
.collect();
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("hatch.texture.vbuf"),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("hatch.texture.ibuf"),
|
||||
contents: bytemuck::cast_slice(&indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
let vertex_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"hatch.texture.vbuf",
|
||||
&vertices,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
);
|
||||
let index_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"hatch.texture.ibuf",
|
||||
&indices,
|
||||
wgpu::BufferUsages::INDEX,
|
||||
);
|
||||
let base = model
|
||||
.render_instance
|
||||
.map_or([0.0; 3], |instance| instance.translation);
|
||||
@@ -231,11 +235,13 @@ impl TextureHatch {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let placement_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("hatch.texture.placements"),
|
||||
contents: bytemuck::cast_slice(&placements),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
let placement_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"hatch.texture.placements",
|
||||
&placements,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
);
|
||||
|
||||
// ── Gradient projection range (snapped-local space) ───────────────
|
||||
let base_mode = mode & 0xFF;
|
||||
|
||||
+34
-19
@@ -1,6 +1,7 @@
|
||||
mod device_capabilities;
|
||||
pub mod face3d_gpu;
|
||||
pub mod gpu_budget;
|
||||
pub mod gpu_upload;
|
||||
pub mod hatch_gpu;
|
||||
pub mod wipeout_gpu;
|
||||
pub mod image_gpu;
|
||||
@@ -2341,6 +2342,7 @@ impl Pipeline {
|
||||
pub fn build_wire_buffers(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) -> (
|
||||
@@ -2383,6 +2385,7 @@ impl Pipeline {
|
||||
let refs: Vec<&WireModel> = wires[i..j].iter().collect();
|
||||
batches.extend(WireGpu::from_run_refs(
|
||||
device,
|
||||
queue,
|
||||
&refs,
|
||||
depth_map,
|
||||
mesh_edge,
|
||||
@@ -2392,6 +2395,7 @@ impl Pipeline {
|
||||
}
|
||||
let block_batches = BlockWireGpu::from_wires(
|
||||
device,
|
||||
queue,
|
||||
&block_wires,
|
||||
depth_map,
|
||||
None,
|
||||
@@ -2426,6 +2430,7 @@ impl Pipeline {
|
||||
pub fn upload_selected_wires(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[WireModel],
|
||||
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||
hovered: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||
@@ -2495,6 +2500,7 @@ impl Pipeline {
|
||||
let mut gpu = if let Some(tint) = selected_tint {
|
||||
WireGpu::from_highlight_refs(
|
||||
device,
|
||||
queue,
|
||||
&selected_regular,
|
||||
tint,
|
||||
depth_map,
|
||||
@@ -2505,6 +2511,7 @@ impl Pipeline {
|
||||
};
|
||||
gpu.extend(WireGpu::from_highlight_refs(
|
||||
device,
|
||||
queue,
|
||||
&hover_regular,
|
||||
WireModel::HOVER,
|
||||
depth_map,
|
||||
@@ -2514,6 +2521,7 @@ impl Pipeline {
|
||||
let mut block_gpu = if let Some(tint) = selected_tint {
|
||||
BlockWireGpu::from_wires(
|
||||
device,
|
||||
queue,
|
||||
&selected_blocks,
|
||||
depth_map,
|
||||
Some(tint),
|
||||
@@ -2524,6 +2532,7 @@ impl Pipeline {
|
||||
};
|
||||
block_gpu.extend(BlockWireGpu::from_wires(
|
||||
device,
|
||||
queue,
|
||||
&hover_blocks,
|
||||
depth_map,
|
||||
Some(WireModel::HOVER),
|
||||
@@ -2554,6 +2563,7 @@ impl Pipeline {
|
||||
pub fn upload_text_highlight(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[WireModel],
|
||||
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||
hovered: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||
@@ -2634,10 +2644,11 @@ impl Pipeline {
|
||||
}
|
||||
}
|
||||
self.text_highlight_vcount = out.len() as u32;
|
||||
self.text_highlight_vbuf = text_gpu::upload_vertices(device, &out);
|
||||
self.text_highlight_vbuf = text_gpu::upload_vertices(device, queue, &out);
|
||||
let mut block_gpu = if let Some(tint) = selected_tint {
|
||||
text_gpu::upload_block_vertex_refs(
|
||||
device,
|
||||
queue,
|
||||
&selected_blocks,
|
||||
depth_map,
|
||||
Some(tint),
|
||||
@@ -2647,6 +2658,7 @@ impl Pipeline {
|
||||
};
|
||||
block_gpu.extend(text_gpu::upload_block_vertex_refs(
|
||||
device,
|
||||
queue,
|
||||
&hover_blocks,
|
||||
depth_map,
|
||||
Some(WireModel::HOVER),
|
||||
@@ -2670,13 +2682,14 @@ impl Pipeline {
|
||||
pub fn upload_preview_wires(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) {
|
||||
self.gpu_preview_wires = if wires.is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
WireGpu::from_run(device, wires, depth_map, false, self.wire_const_bgl.as_ref())
|
||||
WireGpu::from_run(device, queue, wires, depth_map, false, self.wire_const_bgl.as_ref())
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2709,7 +2722,7 @@ impl Pipeline {
|
||||
atlas.clear_dirty();
|
||||
}
|
||||
}
|
||||
self.text_preview_vbuf = text_gpu::upload_vertices(device, verts);
|
||||
self.text_preview_vbuf = text_gpu::upload_vertices(device, queue, verts);
|
||||
self.text_preview_vcount = verts.len() as u32;
|
||||
}
|
||||
|
||||
@@ -2720,6 +2733,7 @@ impl Pipeline {
|
||||
pub fn upload_silhouettes(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
sets: &[crate::scene::model::mesh_model::MeshLodSet],
|
||||
content_id: u64,
|
||||
view_dir: glam::Vec3,
|
||||
@@ -2728,7 +2742,6 @@ impl Pipeline {
|
||||
let view = glam::DVec3::new(view_dir.x as f64, view_dir.y as f64, view_dir.z as f64)
|
||||
.normalize_or(glam::DVec3::NEG_Z);
|
||||
use crate::scene::pipeline::mesh_gpu::{SilhouetteInstance, SilhouetteVertex};
|
||||
use wgpu::util::DeviceExt;
|
||||
let chunk_bytes = gpu_budget::buffer_budget(device);
|
||||
let max_vertices = chunk_bytes / std::mem::size_of::<SilhouetteVertex>() / 2 * 2;
|
||||
if max_vertices < 2 {
|
||||
@@ -2804,12 +2817,12 @@ impl Pipeline {
|
||||
.chunks(max_instances.max(1))
|
||||
.map(|instances| {
|
||||
(
|
||||
device.create_buffer_init(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("mesh.silhouette.instances"),
|
||||
contents: bytemuck::cast_slice(instances),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
},
|
||||
gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"mesh.silhouette.instances",
|
||||
instances,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
),
|
||||
instances.len() as u32,
|
||||
)
|
||||
@@ -2911,12 +2924,12 @@ impl Pipeline {
|
||||
if vertex_count == 0 {
|
||||
return;
|
||||
}
|
||||
let vertex_buffer = device.create_buffer_init(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("mesh.silhouette.vbuf"),
|
||||
contents: bytemuck::cast_slice(&verts[..vertex_count]),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
},
|
||||
let vertex_buffer = gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"mesh.silhouette.vbuf",
|
||||
&verts[..vertex_count],
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
);
|
||||
for (instance_buffer, instance_count) in &group.instance_buffers {
|
||||
chunks.push(SilhouetteChunk {
|
||||
@@ -3033,6 +3046,7 @@ impl Pipeline {
|
||||
pub fn upload_face3d(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
face3d_wires: &[WireModel],
|
||||
all_wires: &[WireModel],
|
||||
wireframe_only: bool,
|
||||
@@ -3044,7 +3058,7 @@ impl Pipeline {
|
||||
// Edge buffer is always built from `face3d_wires`, so 3DFACE
|
||||
// outlines stay on the screen regardless of mode.
|
||||
self.gpu_face3d_edges =
|
||||
WireGpu::from_batch(device, face3d_wires, depth_map, self.wire_const_bgl.as_ref());
|
||||
WireGpu::from_batch(device, queue, face3d_wires, depth_map, self.wire_const_bgl.as_ref());
|
||||
// Fill buffer split: 3D quads + PolyfaceMesh / PolygonMesh face
|
||||
// tris go to `chunks_3d` (gated by `keep_3d_mesh_fills`);
|
||||
// 2D fills (text-LOD greek, MultiLeader background, dimension arrows) go to
|
||||
@@ -3067,6 +3081,7 @@ impl Pipeline {
|
||||
} else {
|
||||
self.gpu_face3d_fill = Some(Face3DGpu::from_wires(
|
||||
device,
|
||||
queue,
|
||||
face3d_wires,
|
||||
all_wires,
|
||||
keep_3d_mesh_fills,
|
||||
@@ -3404,9 +3419,9 @@ impl Pipeline {
|
||||
atlas.clear_dirty();
|
||||
}
|
||||
}
|
||||
self.text_vbuf = text_gpu::upload_vertices(device, verts);
|
||||
self.text_vbuf = text_gpu::upload_vertices(device, queue, verts);
|
||||
self.text_vcount = verts.len() as u32;
|
||||
self.block_text_gpu = text_gpu::upload_block_vertices(device, wires, depth_map);
|
||||
self.block_text_gpu = text_gpu::upload_block_vertices(device, queue, wires, depth_map);
|
||||
if let Some(started) = perf_started {
|
||||
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
|
||||
if elapsed_ms >= 1.0 {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use iced::wgpu;
|
||||
use iced::wgpu::util::DeviceExt;
|
||||
|
||||
use crate::scene::text::glyph_quads::GlyphQuad;
|
||||
use crate::scene::text::sdf_atlas::GlyphAtlas;
|
||||
@@ -462,15 +461,17 @@ pub fn create_pipelines(
|
||||
|
||||
pub fn upload_block_vertices(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[crate::scene::model::wire_model::WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) -> Vec<BlockTextGpu> {
|
||||
let refs: Vec<&crate::scene::model::wire_model::WireModel> = wires.iter().collect();
|
||||
upload_block_vertex_refs(device, &refs, depth_map, None)
|
||||
upload_block_vertex_refs(device, queue, &refs, depth_map, None)
|
||||
}
|
||||
|
||||
pub fn upload_block_vertex_refs(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[&crate::scene::model::wire_model::WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
tint: Option<[f32; 4]>,
|
||||
@@ -516,7 +517,7 @@ pub fn upload_block_vertex_refs(
|
||||
} else {
|
||||
source.text_verts.as_slice()
|
||||
};
|
||||
let Some(vertex_buffer) = upload_vertices(device, vertices) else {
|
||||
let Some(vertex_buffer) = upload_vertices(device, queue, vertices) else {
|
||||
continue;
|
||||
};
|
||||
let instances: Vec<BlockTextInstance> = group
|
||||
@@ -544,11 +545,13 @@ pub fn upload_block_vertex_refs(
|
||||
let max_instances =
|
||||
super::gpu_budget::max_elements::<BlockTextInstance>(device);
|
||||
for chunk in instances.chunks(max_instances) {
|
||||
let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("block_text.instances"),
|
||||
contents: bytemuck::cast_slice(chunk),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
let instance_buffer = super::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"block_text.instances",
|
||||
chunk,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
);
|
||||
out.push(BlockTextGpu {
|
||||
vertex_buffer: vertex_buffer.clone(),
|
||||
instance_buffer,
|
||||
@@ -561,15 +564,21 @@ pub fn upload_block_vertex_refs(
|
||||
}
|
||||
|
||||
/// Upload a finished vertex list; `None` if empty.
|
||||
pub fn upload_vertices(device: &wgpu::Device, verts: &[TextVertex]) -> Option<wgpu::Buffer> {
|
||||
pub fn upload_vertices(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
verts: &[TextVertex],
|
||||
) -> Option<wgpu::Buffer> {
|
||||
if verts.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("text.vbuf"),
|
||||
contents: bytemuck::cast_slice(verts),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
}))
|
||||
Some(super::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"text.vbuf",
|
||||
verts,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
))
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -255,48 +255,34 @@ fn make_const_bg(
|
||||
|
||||
fn alloc_inst_initialized(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
cap: u64,
|
||||
data: &[WireInstance],
|
||||
) -> wgpu::Buffer {
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("wire_arena.ibuf"),
|
||||
size: cap * std::mem::size_of::<WireInstance>() as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: true,
|
||||
});
|
||||
if !data.is_empty() {
|
||||
let bytes = bytemuck::cast_slice(data);
|
||||
let mut mapped = buffer
|
||||
.slice(..bytes.len() as u64)
|
||||
.get_mapped_range_mut();
|
||||
mapped.copy_from_slice(bytes);
|
||||
drop(mapped);
|
||||
}
|
||||
buffer.unmap();
|
||||
buffer
|
||||
super::gpu_upload::alloc_with_prefix(
|
||||
device,
|
||||
queue,
|
||||
"wire_arena.ibuf",
|
||||
cap * std::mem::size_of::<WireInstance>() as u64,
|
||||
data,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
)
|
||||
}
|
||||
|
||||
fn alloc_const_initialized(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
cap: u64,
|
||||
data: &[WireConst],
|
||||
) -> wgpu::Buffer {
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("wire_arena.cbuf"),
|
||||
size: cap * std::mem::size_of::<WireConst>() as u64,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: true,
|
||||
});
|
||||
if !data.is_empty() {
|
||||
let bytes = bytemuck::cast_slice(data);
|
||||
let mut mapped = buffer
|
||||
.slice(..bytes.len() as u64)
|
||||
.get_mapped_range_mut();
|
||||
mapped.copy_from_slice(bytes);
|
||||
drop(mapped);
|
||||
}
|
||||
buffer.unmap();
|
||||
buffer
|
||||
super::gpu_upload::alloc_with_prefix(
|
||||
device,
|
||||
queue,
|
||||
"wire_arena.cbuf",
|
||||
cap * std::mem::size_of::<WireConst>() as u64,
|
||||
data,
|
||||
wgpu::BufferUsages::STORAGE,
|
||||
)
|
||||
}
|
||||
|
||||
fn blank_const() -> WireConst {
|
||||
@@ -328,7 +314,7 @@ impl WireArena {
|
||||
/// path).
|
||||
pub fn build(
|
||||
device: &wgpu::Device,
|
||||
_queue: &wgpu::Queue,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[&WireModel],
|
||||
depth_map: &FxHashMap<u64, [f32; 2]>,
|
||||
const_bgl: &wgpu::BindGroupLayout,
|
||||
@@ -468,8 +454,8 @@ impl WireArena {
|
||||
.max(MIN_CONST_CAP)
|
||||
.min(MAX_CONSTS)) as u32;
|
||||
let upload_started = iced::time::Instant::now();
|
||||
let inst_buf = alloc_inst_initialized(device, inst_cap as u64, &instances);
|
||||
let const_buf = alloc_const_initialized(device, const_cap as u64, &consts_cpu);
|
||||
let inst_buf = alloc_inst_initialized(device, queue, inst_cap as u64, &instances);
|
||||
let const_buf = alloc_const_initialized(device, queue, const_cap as u64, &consts_cpu);
|
||||
let upload_ms = upload_started.elapsed().as_secs_f64() * 1000.0;
|
||||
let const_bind_group = make_const_bg(device, const_bgl, &const_buf);
|
||||
if perf {
|
||||
@@ -934,25 +920,18 @@ fn can_resize_packed_terminal_slab(
|
||||
|
||||
fn alloc_packed_initialized(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
cap: u64,
|
||||
data: &[PackedWireInstance],
|
||||
) -> wgpu::Buffer {
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("wire_arena.packed.ibuf"),
|
||||
size: cap * std::mem::size_of::<PackedWireInstance>() as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: true,
|
||||
});
|
||||
if !data.is_empty() {
|
||||
let bytes = bytemuck::cast_slice(data);
|
||||
let mut mapped = buffer
|
||||
.slice(..bytes.len() as u64)
|
||||
.get_mapped_range_mut();
|
||||
mapped.copy_from_slice(bytes);
|
||||
drop(mapped);
|
||||
}
|
||||
buffer.unmap();
|
||||
buffer
|
||||
super::gpu_upload::alloc_with_prefix(
|
||||
device,
|
||||
queue,
|
||||
"wire_arena.packed.ibuf",
|
||||
cap * std::mem::size_of::<PackedWireInstance>() as u64,
|
||||
data,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
)
|
||||
}
|
||||
|
||||
fn visible_ranges(
|
||||
@@ -998,6 +977,7 @@ fn visible_ranges(
|
||||
impl PackedWireArena {
|
||||
fn build(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[&WireModel],
|
||||
depth_map: &FxHashMap<u64, [f32; 2]>,
|
||||
mesh_edge: bool,
|
||||
@@ -1078,7 +1058,7 @@ impl PackedWireArena {
|
||||
let inst_cap = ((inst_tail as u64 * HEADROOM_NUM / HEADROOM_DEN)
|
||||
.max(MIN_INST_CAP)
|
||||
.min(MAX_PACKED_INSTANCES)) as u32;
|
||||
let inst_buf = alloc_packed_initialized(device, inst_cap as u64, &instances);
|
||||
let inst_buf = alloc_packed_initialized(device, queue, inst_cap as u64, &instances);
|
||||
|
||||
Some(Self {
|
||||
inst_buf,
|
||||
@@ -1351,7 +1331,7 @@ impl PersistentWireArena {
|
||||
)?)
|
||||
} else {
|
||||
PersistentWireArenaKind::Packed(PackedWireArena::build(
|
||||
device, wires, depth_map, mesh_edge,
|
||||
device, queue, wires, depth_map, mesh_edge,
|
||||
)?)
|
||||
};
|
||||
Some(Self { inner })
|
||||
|
||||
@@ -28,34 +28,20 @@
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
use iced::wgpu;
|
||||
|
||||
/// 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 instance_buffer_mapped<T: bytemuck::Pod>(
|
||||
/// Allocate a VERTEX buffer and fill it through the queue.
|
||||
///
|
||||
/// This used to map at creation to skip the staging copy. Mapping panics on
|
||||
/// the invalid buffer an out-of-memory device returns, and that panic aborts
|
||||
/// the process from inside iced's redraw, so the staging copy is the price of
|
||||
/// a session that survives a failed allocation. Chunking keeps the transient
|
||||
/// staging cost bounded. See `super::gpu_upload`.
|
||||
fn instance_buffer<T: bytemuck::Pod>(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
label: &str,
|
||||
data: &[T],
|
||||
) -> wgpu::Buffer {
|
||||
let bytes: &[u8] = bytemuck::cast_slice(data);
|
||||
// wgpu rejects size-0 buffers; the renderer already guards `instance_count`
|
||||
// before issuing a draw, so a placeholder allocation is fine here.
|
||||
let size = bytes.len().max(std::mem::size_of::<T>()) 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();
|
||||
if !bytes.is_empty() {
|
||||
view.slice(..bytes.len()).copy_from_slice(bytes);
|
||||
}
|
||||
}
|
||||
buf.unmap();
|
||||
buf
|
||||
super::gpu_upload::upload_buffer(device, queue, label, data, wgpu::BufferUsages::VERTEX)
|
||||
}
|
||||
|
||||
// ── Instance layout ───────────────────────────────────────────────────────
|
||||
@@ -684,6 +670,7 @@ pub(crate) fn wire_draw_depth(
|
||||
impl BlockWireGpu {
|
||||
pub fn from_wires(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[&WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
color_override: Option<[f32; 4]>,
|
||||
@@ -753,11 +740,13 @@ impl BlockWireGpu {
|
||||
.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,
|
||||
}),
|
||||
super::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"block_wire.vertices",
|
||||
chunk,
|
||||
wgpu::BufferUsages::VERTEX,
|
||||
),
|
||||
chunk.len() as u32,
|
||||
)
|
||||
})
|
||||
@@ -812,7 +801,7 @@ impl BlockWireGpu {
|
||||
.chunks(max_instances)
|
||||
.map(|chunk| {
|
||||
(
|
||||
instance_buffer_mapped(device, "block_wire.instances", chunk),
|
||||
instance_buffer(device, queue, "block_wire.instances", chunk),
|
||||
chunk.len() as u32,
|
||||
)
|
||||
})
|
||||
@@ -840,18 +829,20 @@ impl BlockWireGpu {
|
||||
/// each chunk.
|
||||
fn build_const_bind_group(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
bgl: &wgpu::BindGroupLayout,
|
||||
consts: &[WireConst],
|
||||
) -> std::sync::Arc<wgpu::BindGroup> {
|
||||
use wgpu::util::DeviceExt;
|
||||
// wgpu rejects zero-sized buffers; pad with one zeroed record when empty.
|
||||
let one = [<WireConst as bytemuck::Zeroable>::zeroed()];
|
||||
let data: &[WireConst] = if consts.is_empty() { &one } else { consts };
|
||||
let buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("wire_const.buf"),
|
||||
contents: bytemuck::cast_slice(data),
|
||||
usage: wgpu::BufferUsages::STORAGE,
|
||||
});
|
||||
let buf = super::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
"wire_const.buf",
|
||||
data,
|
||||
wgpu::BufferUsages::STORAGE,
|
||||
);
|
||||
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("wire_const.bg"),
|
||||
layout: bgl,
|
||||
@@ -869,6 +860,7 @@ impl WireGpu {
|
||||
/// of a large selected polyline or block before packing the overlay.
|
||||
pub fn from_highlight_refs(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[&WireModel],
|
||||
color: [f32; 4],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
@@ -895,12 +887,13 @@ impl WireGpu {
|
||||
if instances.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let bind_group = build_const_bind_group(device, const_bgl, &consts);
|
||||
let bind_group = build_const_bind_group(device, queue, const_bgl, &consts);
|
||||
return instances
|
||||
.chunks(MAX_INSTANCES)
|
||||
.map(|chunk| Self {
|
||||
instance_buffer: instance_buffer_mapped(
|
||||
instance_buffer: instance_buffer(
|
||||
device,
|
||||
queue,
|
||||
"wire.highlight.ibuf",
|
||||
chunk,
|
||||
),
|
||||
@@ -928,8 +921,9 @@ impl WireGpu {
|
||||
instances
|
||||
.chunks(MAX_PACKED_INSTANCES)
|
||||
.map(|chunk| Self {
|
||||
instance_buffer: instance_buffer_mapped(
|
||||
instance_buffer: instance_buffer(
|
||||
device,
|
||||
queue,
|
||||
"wire.highlight.compat.ibuf",
|
||||
chunk,
|
||||
),
|
||||
@@ -947,6 +941,7 @@ impl WireGpu {
|
||||
/// uses chunked resident buffers.
|
||||
pub fn from_run_refs(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[&WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
mesh_edge: bool,
|
||||
@@ -975,8 +970,9 @@ impl WireGpu {
|
||||
return instances
|
||||
.chunks(MAX_INSTANCES)
|
||||
.map(|chunk| Self {
|
||||
instance_buffer: instance_buffer_mapped(
|
||||
instance_buffer: instance_buffer(
|
||||
device,
|
||||
queue,
|
||||
"wire.run.hybrid.compat.ibuf",
|
||||
chunk,
|
||||
),
|
||||
@@ -1012,11 +1008,11 @@ impl WireGpu {
|
||||
if instances.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let bind_group = build_const_bind_group(device, const_bgl, &consts);
|
||||
let bind_group = build_const_bind_group(device, queue, const_bgl, &consts);
|
||||
instances
|
||||
.chunks(MAX_INSTANCES)
|
||||
.map(|chunk| Self {
|
||||
instance_buffer: instance_buffer_mapped(device, "wire.run.hybrid.ibuf", chunk),
|
||||
instance_buffer: instance_buffer(device, queue, "wire.run.hybrid.ibuf", chunk),
|
||||
first_instance: 0,
|
||||
instance_count: chunk.len() as u32,
|
||||
is_3d_mesh_edge: mesh_edge,
|
||||
@@ -1037,6 +1033,7 @@ impl WireGpu {
|
||||
/// image for transparent / coincident wires.
|
||||
pub fn from_run(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
mesh_edge: bool,
|
||||
@@ -1071,11 +1068,11 @@ impl WireGpu {
|
||||
if instances.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
let bg = build_const_bind_group(device, const_bgl, &consts);
|
||||
let bg = build_const_bind_group(device, queue, const_bgl, &consts);
|
||||
return instances
|
||||
.chunks(MAX_INSTANCES)
|
||||
.map(|chunk| {
|
||||
let buf = instance_buffer_mapped(device, "wire.run.ibuf", chunk);
|
||||
let buf = instance_buffer(device, queue, "wire.run.ibuf", chunk);
|
||||
Self {
|
||||
instance_buffer: buf,
|
||||
first_instance: 0,
|
||||
@@ -1108,7 +1105,7 @@ impl WireGpu {
|
||||
instances
|
||||
.chunks(MAX_PACKED_INSTANCES)
|
||||
.map(|chunk| {
|
||||
let buf = instance_buffer_mapped(device, "wire.run.compat.ibuf", chunk);
|
||||
let buf = instance_buffer(device, queue, "wire.run.compat.ibuf", chunk);
|
||||
Self {
|
||||
instance_buffer: buf,
|
||||
first_instance: 0,
|
||||
@@ -1125,6 +1122,7 @@ impl WireGpu {
|
||||
/// per-instance.
|
||||
pub fn from_batch(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
const_bgl: Option<&wgpu::BindGroupLayout>,
|
||||
@@ -1158,13 +1156,13 @@ impl WireGpu {
|
||||
if instances.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
let bg = build_const_bind_group(device, const_bgl, &consts);
|
||||
let bg = build_const_bind_group(device, queue, const_bgl, &consts);
|
||||
return instances
|
||||
.chunks(MAX_INSTANCES)
|
||||
.enumerate()
|
||||
.map(|(i, chunk)| {
|
||||
let label = format!("wire.batch.ibuf.{i}");
|
||||
let instance_buffer = instance_buffer_mapped(device, &label, chunk);
|
||||
let instance_buffer = instance_buffer(device, queue, &label, chunk);
|
||||
Self {
|
||||
instance_buffer,
|
||||
first_instance: 0,
|
||||
@@ -1191,7 +1189,7 @@ impl WireGpu {
|
||||
.enumerate()
|
||||
.map(|(i, chunk)| {
|
||||
let label = format!("wire.batch.compat.ibuf.{i}");
|
||||
let instance_buffer = instance_buffer_mapped(device, &label, chunk);
|
||||
let instance_buffer = instance_buffer(device, queue, &label, chunk);
|
||||
Self {
|
||||
instance_buffer,
|
||||
first_instance: 0,
|
||||
|
||||
@@ -608,6 +608,7 @@ impl shader::Primitive for Primitive {
|
||||
{
|
||||
inner.upload_face3d(
|
||||
device,
|
||||
queue,
|
||||
&vp.face3d_wires[..],
|
||||
&vp_wires[..],
|
||||
!face3d_fill_active,
|
||||
@@ -785,6 +786,7 @@ impl shader::Primitive for Primitive {
|
||||
inner.wire_arena_fallback = std::sync::Arc::new(
|
||||
crate::scene::pipeline::WireGpu::from_run_refs(
|
||||
device,
|
||||
queue,
|
||||
®ular,
|
||||
&draw_depths,
|
||||
false,
|
||||
@@ -821,6 +823,7 @@ impl shader::Primitive for Primitive {
|
||||
inner.wire_arena_fallback = std::sync::Arc::new(
|
||||
crate::scene::pipeline::WireGpu::from_run_refs(
|
||||
device,
|
||||
queue,
|
||||
&mesh,
|
||||
&draw_depths,
|
||||
true,
|
||||
@@ -918,7 +921,7 @@ impl shader::Primitive for Primitive {
|
||||
Some(entry) => entry,
|
||||
None => {
|
||||
let entry =
|
||||
inner.build_wire_buffers(device, &vp_wires[..], &draw_depths);
|
||||
inner.build_wire_buffers(device, queue, &vp_wires[..], &draw_depths);
|
||||
pipeline
|
||||
.wire_buffer_cache
|
||||
.insert(vp.wire_content_id, entry.clone());
|
||||
@@ -1000,6 +1003,7 @@ impl shader::Primitive for Primitive {
|
||||
};
|
||||
inner.upload_selected_wires(
|
||||
device,
|
||||
queue,
|
||||
&vp_wires[..],
|
||||
&vp.selected_handles,
|
||||
&vp.hover_handles,
|
||||
@@ -1012,6 +1016,7 @@ impl shader::Primitive for Primitive {
|
||||
// base text buffer.
|
||||
inner.upload_text_highlight(
|
||||
device,
|
||||
queue,
|
||||
&vp_wires[..],
|
||||
&vp.selected_handles,
|
||||
&vp.hover_handles,
|
||||
@@ -1066,7 +1071,7 @@ impl shader::Primitive for Primitive {
|
||||
// Live overlay (command preview / interim / grip drag) — small and
|
||||
// refreshed every frame it's present, so a drag never re-uploads
|
||||
// the resident base wire buffer.
|
||||
inner.upload_preview_wires(device, &vp.preview_wires[..], &draw_depths);
|
||||
inner.upload_preview_wires(device, queue, &vp.preview_wires[..], &draw_depths);
|
||||
inner.upload_preview_text(device, queue, &vp.preview_text_verts[..]);
|
||||
// Cull / scissor / LOD project AABBs relative-to-eye (matching the
|
||||
// GPU's RTE path) so the math stays precise at UTM-scale coords.
|
||||
@@ -1087,6 +1092,7 @@ impl shader::Primitive for Primitive {
|
||||
if inner.silhouette_key != silhouette_key {
|
||||
inner.upload_silhouettes(
|
||||
device,
|
||||
queue,
|
||||
if silhouette_enabled { &vp.meshes[..] } else { &[] },
|
||||
vp.wire_content_id,
|
||||
vp.view_dir,
|
||||
|
||||
Reference in new issue
Block a user