forked from 1b-agent/opencadstudio-upstream
Merge pull request #1006 with block rendering fixes
Invalidate cached world-space block vertices when the base translation changes. Share the regular/mesh partition between full uploads and patches so instanced blocks are uploaded only once. Cover both paths with CPU and GPU regression checks.
This commit is contained in:
commit
af0cc68c5a
5 files changed
+317
-106
No files matched your search
@@ -3,6 +3,95 @@ use crate::scene::model::{mesh_model::MeshLodSet, mesh_model::MeshModel, wire_mo
|
||||
use acadrust::{types::Transform, Handle};
|
||||
use iced::futures::executor::block_on;
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires a GPU adapter"]
|
||||
fn block_edits_preserve_cache_coordinates_and_arena_partition() {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
|
||||
let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default()))
|
||||
.expect("GPU adapter");
|
||||
let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
required_limits: adapter.limits(),
|
||||
..Default::default()
|
||||
}))
|
||||
.expect("GPU device");
|
||||
let validation = device.push_error_scope(wgpu::ErrorFilter::Validation);
|
||||
let mut pipeline = Pipeline::new(&device, &queue, wgpu::TextureFormat::Bgra8UnormSrgb);
|
||||
let block = |handle: u64, x: f64| WireModel {
|
||||
name: handle.to_string(),
|
||||
points: vec![[x as f32, 0.0, 0.0], [x as f32 + 1.0, 0.0, 0.0]],
|
||||
render_instance: Some(crate::scene::model::instance_model::RenderInstance {
|
||||
source_id: 1,
|
||||
translation: [x, 0.0, 0.0],
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let first = block(1, 10.0);
|
||||
let second = block(2, 20.0);
|
||||
let depth = rustc_hash::FxHashMap::from_iter([(1, [0.1, 0.01]), (2, [0.2, 0.01])]);
|
||||
let original = pipeline.upload_block_wires(&device, &queue, &[&first, &second], &depth);
|
||||
let unchanged = pipeline.upload_block_wires(&device, &queue, &[&first, &second], &depth);
|
||||
assert_eq!(original[0].vertex_buffer, unchanged[0].vertex_buffer);
|
||||
assert_eq!(original[0].instance_count, 2);
|
||||
|
||||
let removed = pipeline.upload_block_wires(&device, &queue, &[&second], &depth);
|
||||
assert_ne!(
|
||||
original[0].vertex_buffer, removed[0].vertex_buffer,
|
||||
"removing the base instance must replace vertices baked at its old position"
|
||||
);
|
||||
let moved = block(2, 30.0);
|
||||
let moved_gpu = pipeline.upload_block_wires(&device, &queue, &[&moved], &depth);
|
||||
assert_ne!(removed[0].vertex_buffer, moved_gpu[0].vertex_buffer);
|
||||
let restored = pipeline.upload_block_wires(&device, &queue, &[&first, &second], &depth);
|
||||
assert_ne!(moved_gpu[0].vertex_buffer, restored[0].vertex_buffer);
|
||||
assert_eq!(restored[0].instance_count, 2);
|
||||
assert_eq!(
|
||||
pipeline.block_geometry.len(),
|
||||
1,
|
||||
"discard obsolete geometry"
|
||||
);
|
||||
|
||||
let line = WireModel {
|
||||
render_instance: None,
|
||||
..first.clone()
|
||||
};
|
||||
let changed = [second];
|
||||
let (regular, _) = wire_arena::split_wires(&changed);
|
||||
let runs = rustc_hash::FxHashMap::from_iter([(Handle::new(2), regular)]);
|
||||
for layout in [pipeline.wire_const_bgl.as_ref(), None] {
|
||||
let mut arena = wire_arena::PersistentWireArena::build(
|
||||
&device,
|
||||
&queue,
|
||||
&[&line],
|
||||
&depth,
|
||||
layout,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(arena.patch(
|
||||
&queue,
|
||||
&[(Handle::new(2), crate::scene::ChangeKind::Added)],
|
||||
&runs,
|
||||
true,
|
||||
&depth
|
||||
));
|
||||
assert_eq!(
|
||||
arena
|
||||
.wire_gpus()
|
||||
.iter()
|
||||
.map(|gpu| gpu.instance_count)
|
||||
.sum::<u32>(),
|
||||
1,
|
||||
"the block must not also enter the regular arena"
|
||||
);
|
||||
}
|
||||
queue.submit([]);
|
||||
device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
|
||||
assert!(
|
||||
block_on(validation.pop()).is_none(),
|
||||
"GPU validation failed"
|
||||
);
|
||||
}
|
||||
|
||||
// OCS_GPU_CHUNK_MIB=1 cargo test --lib bounded_gpu_uploads -- --ignored --nocapture
|
||||
#[test]
|
||||
#[ignore = "requires a GPU adapter and OCS_GPU_CHUNK_MIB=1"]
|
||||
|
||||
@@ -213,6 +213,9 @@ pub struct Pipeline {
|
||||
/// shared resident buffer.
|
||||
pub(crate) gpu_wires: std::sync::Arc<Vec<WireGpu>>,
|
||||
pub(crate) gpu_block_wires: std::sync::Arc<Vec<BlockWireGpu>>,
|
||||
/// Block geometry that survives an edit, keyed by the definition it
|
||||
/// expands. See `wire_gpu::BlockGeometryCache`.
|
||||
pub(crate) block_geometry: wire_gpu::BlockGeometryCache,
|
||||
/// Persistent per-entity wire instance arena (capability-selected format).
|
||||
/// When active, `gpu_wires` is a thin wrapper over this arena's buffers and an
|
||||
/// edit patches one entity's slab in place instead of rebuilding every wire.
|
||||
@@ -2279,6 +2282,7 @@ impl Pipeline {
|
||||
surface_format: format,
|
||||
gpu_wires: std::sync::Arc::new(vec![]),
|
||||
gpu_block_wires: std::sync::Arc::new(vec![]),
|
||||
block_geometry: Default::default(),
|
||||
wire_arena: None,
|
||||
wire_arena_mesh: None,
|
||||
wire_arena_fallback: std::sync::Arc::new(Vec::new()),
|
||||
@@ -2331,13 +2335,28 @@ impl Pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the resident wire batches + handle index for `wires`, wrapped in
|
||||
/// `Arc` so the caller can cache them by `wire_content_id` and share one
|
||||
/// copy across every slot that renders that content (see
|
||||
/// `MultiPipeline::wire_buffer_cache`). Takes `&self` (reads only the const
|
||||
/// bind-group layout) so the shared cache — not the slot — owns the result.
|
||||
/// Upload block placements while reusing unchanged geometry.
|
||||
pub fn upload_block_wires(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[&WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) -> Vec<BlockWireGpu> {
|
||||
BlockWireGpu::from_wires(
|
||||
device,
|
||||
queue,
|
||||
wires,
|
||||
depth_map,
|
||||
None,
|
||||
&self.block_wire_const_bgl,
|
||||
Some(&mut self.block_geometry),
|
||||
)
|
||||
}
|
||||
|
||||
/// Build resident batches and a handle index shared across viewport slots.
|
||||
pub fn build_wire_buffers(
|
||||
&self,
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[WireModel],
|
||||
@@ -2397,6 +2416,7 @@ impl Pipeline {
|
||||
depth_map,
|
||||
None,
|
||||
&self.block_wire_const_bgl,
|
||||
Some(&mut self.block_geometry),
|
||||
);
|
||||
|
||||
// Index handle → wire slots once, here, so the per-hover selection
|
||||
@@ -2523,6 +2543,7 @@ impl Pipeline {
|
||||
depth_map,
|
||||
Some(tint),
|
||||
&self.block_wire_const_bgl,
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
Vec::new()
|
||||
@@ -2534,6 +2555,7 @@ impl Pipeline {
|
||||
depth_map,
|
||||
Some(WireModel::HOVER),
|
||||
&self.block_wire_const_bgl,
|
||||
None,
|
||||
));
|
||||
self.gpu_selected_block_wires = block_gpu;
|
||||
if let Some(started) = perf_started {
|
||||
|
||||
@@ -120,9 +120,15 @@ fn handle_of(w: &WireModel) -> Option<Handle> {
|
||||
crate::scene::Scene::handle_from_wire_name(&w.name)
|
||||
}
|
||||
|
||||
/// True when `w` needs the shaded-mode edge treatment.
|
||||
pub fn is_mesh_edge(w: &WireModel, _mesh_names: &rustc_hash::FxHashSet<u64>) -> bool {
|
||||
!w.points.is_empty() && w.fill_is_3d
|
||||
/// Use the same regular/mesh partition for full uploads and changed runs.
|
||||
/// Instanced blocks are uploaded separately by `BlockWireGpu`.
|
||||
pub fn split_wires(wires: &[WireModel]) -> (Vec<&WireModel>, Vec<&WireModel>) {
|
||||
wires
|
||||
.iter()
|
||||
.filter(|wire| {
|
||||
wire.display_visible && !wire.points.is_empty() && wire.render_instance.is_none()
|
||||
})
|
||||
.partition(|wire| !wire.fill_is_3d)
|
||||
}
|
||||
|
||||
/// True when appending a new entity at the tail could change the image, so the
|
||||
@@ -1385,6 +1391,45 @@ impl PersistentWireArena {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn wire_partition_excludes_blocks_and_preserves_mixed_run_edges() {
|
||||
let line = WireModel {
|
||||
name: "line".into(),
|
||||
points: vec![[0.0; 3], [1.0, 0.0, 0.0]],
|
||||
..Default::default()
|
||||
};
|
||||
let mesh = WireModel {
|
||||
name: "mesh".into(),
|
||||
fill_is_3d: true,
|
||||
..line.clone()
|
||||
};
|
||||
let block = WireModel {
|
||||
render_instance: Some(crate::scene::model::instance_model::RenderInstance {
|
||||
source_id: 1,
|
||||
translation: [0.0; 3],
|
||||
}),
|
||||
..line.clone()
|
||||
};
|
||||
let block_mesh = WireModel {
|
||||
fill_is_3d: true,
|
||||
..block.clone()
|
||||
};
|
||||
let hidden = WireModel {
|
||||
display_visible: false,
|
||||
..line.clone()
|
||||
};
|
||||
let wires = [block, line, block_mesh, mesh, hidden, WireModel::default()];
|
||||
let (regular, edges) = split_wires(&wires);
|
||||
assert_eq!(
|
||||
regular.iter().map(|w| w.name.as_str()).collect::<Vec<_>>(),
|
||||
["line"]
|
||||
);
|
||||
assert_eq!(
|
||||
edges.iter().map(|w| w.name.as_str()).collect::<Vec<_>>(),
|
||||
["mesh"]
|
||||
);
|
||||
}
|
||||
|
||||
fn slab() -> Slab {
|
||||
Slab {
|
||||
inst_off: 100,
|
||||
|
||||
+134
-63
@@ -665,6 +665,17 @@ pub(crate) fn wire_draw_depth(
|
||||
}
|
||||
}
|
||||
|
||||
/// Cached block geometry keyed by source, edge mode, colour and base translation.
|
||||
/// Vertices use the first instance's world coordinates, so changing that base
|
||||
/// must invalidate the geometry as well as rebuild the relative placements.
|
||||
pub type BlockGeometryCache = rustc_hash::FxHashMap<
|
||||
(u64, bool, [u32; 4], [u64; 3]),
|
||||
(
|
||||
std::sync::Arc<Vec<(wgpu::Buffer, u32)>>,
|
||||
std::sync::Arc<wgpu::BindGroup>,
|
||||
),
|
||||
>;
|
||||
|
||||
impl BlockWireGpu {
|
||||
pub fn from_wires(
|
||||
device: &wgpu::Device,
|
||||
@@ -673,8 +684,8 @@ impl BlockWireGpu {
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
color_override: Option<[f32; 4]>,
|
||||
const_bgl: &wgpu::BindGroupLayout,
|
||||
mut cache: Option<&mut BlockGeometryCache>,
|
||||
) -> Vec<Self> {
|
||||
|
||||
let mut slots: rustc_hash::FxHashMap<(u64, bool), usize> =
|
||||
rustc_hash::FxHashMap::default();
|
||||
let mut groups: Vec<(bool, Vec<&WireModel>)> = Vec::new();
|
||||
@@ -699,6 +710,7 @@ impl BlockWireGpu {
|
||||
}
|
||||
|
||||
let mut out = Vec::new();
|
||||
let mut live_keys = rustc_hash::FxHashSet::default();
|
||||
for (mesh_edge, group) in groups {
|
||||
let Some(&source) = group.first() else {
|
||||
continue;
|
||||
@@ -707,6 +719,31 @@ impl BlockWireGpu {
|
||||
continue;
|
||||
};
|
||||
let color = color_override.unwrap_or(source.color);
|
||||
let cache_key = (
|
||||
base.source_id,
|
||||
mesh_edge,
|
||||
color.map(f32::to_bits),
|
||||
base.translation.map(f64::to_bits),
|
||||
);
|
||||
live_keys.insert(cache_key);
|
||||
// Reuse this definition's geometry when it is already on the GPU.
|
||||
if let Some((chunks, bind_group)) = cache
|
||||
.as_deref()
|
||||
.and_then(|c| c.get(&cache_key))
|
||||
.map(|(v, b)| (std::sync::Arc::clone(v), std::sync::Arc::clone(b)))
|
||||
{
|
||||
let instances = block_instances(&group, base, mesh_edge, depth_map);
|
||||
push_block_batches(
|
||||
&mut out,
|
||||
device,
|
||||
queue,
|
||||
&chunks,
|
||||
&instances,
|
||||
mesh_edge,
|
||||
&bind_group,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let (segments, mut constant) = emit_wire_native(source, 0, color, 0.0);
|
||||
if segments.is_empty() {
|
||||
continue;
|
||||
@@ -724,13 +761,7 @@ impl BlockWireGpu {
|
||||
};
|
||||
vertices.extend_from_slice(&[vertex; 6]);
|
||||
}
|
||||
// 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.
|
||||
// Round down to a multiple of 6: each segment expands to six
|
||||
// vertices and must not straddle two chunks.
|
||||
// Bound uploads without splitting a segment's six vertices.
|
||||
let max_verts =
|
||||
super::gpu_budget::max_elements_grouped::<BlockWireVertex>(device, 6);
|
||||
let vertex_chunks: Vec<(wgpu::Buffer, u32)> = vertices
|
||||
@@ -748,11 +779,7 @@ impl BlockWireGpu {
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
// Tiny, but size is irrelevant here: once the device is out of
|
||||
// memory every allocation fails, including a one-struct uniform,
|
||||
// and create_buffer_init would map the invalid buffer and panic.
|
||||
// This is the allocation that killed the process after the vertex
|
||||
// buffers had already degraded gracefully.
|
||||
// Queue uploads avoid mapping a failed allocation.
|
||||
let const_buffer = super::gpu_upload::upload_buffer(
|
||||
device,
|
||||
queue,
|
||||
@@ -770,63 +797,107 @@ impl BlockWireGpu {
|
||||
}],
|
||||
},
|
||||
));
|
||||
let instances: Vec<BlockWireInstance> = group
|
||||
.iter()
|
||||
.filter_map(|wire| {
|
||||
let instance = wire.render_instance?;
|
||||
let delta = [
|
||||
instance.translation[0] - base.translation[0],
|
||||
instance.translation[1] - base.translation[1],
|
||||
instance.translation[2] - base.translation[2],
|
||||
];
|
||||
let high = delta.map(|value| value as f32);
|
||||
Some(BlockWireInstance {
|
||||
translation: high,
|
||||
translation_low: [
|
||||
(delta[0] - high[0] as f64) as f32,
|
||||
(delta[1] - high[1] as f64) as f32,
|
||||
(delta[2] - high[2] as f64) as f32,
|
||||
],
|
||||
depth: [
|
||||
if mesh_edge {
|
||||
0.0
|
||||
} else {
|
||||
wire_draw_depth(wire, depth_map)
|
||||
},
|
||||
0.0,
|
||||
],
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let max_instances = super::gpu_budget::max_elements::<BlockWireInstance>(device);
|
||||
// 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| {
|
||||
let vertex_chunks = std::sync::Arc::new(vertex_chunks);
|
||||
if let Some(cache) = cache.as_deref_mut() {
|
||||
cache.insert(
|
||||
cache_key,
|
||||
(
|
||||
instance_buffer(device, queue, "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(),
|
||||
});
|
||||
}
|
||||
std::sync::Arc::clone(&vertex_chunks),
|
||||
std::sync::Arc::clone(&const_bind_group),
|
||||
),
|
||||
);
|
||||
}
|
||||
let instances = block_instances(&group, base, mesh_edge, depth_map);
|
||||
push_block_batches(
|
||||
&mut out,
|
||||
device,
|
||||
queue,
|
||||
&vertex_chunks,
|
||||
&instances,
|
||||
mesh_edge,
|
||||
&const_bind_group,
|
||||
);
|
||||
}
|
||||
// Release geometry that this build no longer uses.
|
||||
if let Some(cache) = cache {
|
||||
cache.retain(|key, _| live_keys.contains(key));
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
/// Placements and draw depths relative to the group's first instance.
|
||||
fn block_instances(
|
||||
group: &[&WireModel],
|
||||
base: crate::scene::model::instance_model::RenderInstance,
|
||||
mesh_edge: bool,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) -> Vec<BlockWireInstance> {
|
||||
group
|
||||
.iter()
|
||||
.filter_map(|wire| {
|
||||
let instance = wire.render_instance?;
|
||||
let delta = [
|
||||
instance.translation[0] - base.translation[0],
|
||||
instance.translation[1] - base.translation[1],
|
||||
instance.translation[2] - base.translation[2],
|
||||
];
|
||||
let high = delta.map(|value| value as f32);
|
||||
Some(BlockWireInstance {
|
||||
translation: high,
|
||||
translation_low: [
|
||||
(delta[0] - high[0] as f64) as f32,
|
||||
(delta[1] - high[1] as f64) as f32,
|
||||
(delta[2] - high[2] as f64) as f32,
|
||||
],
|
||||
depth: [
|
||||
if mesh_edge {
|
||||
0.0
|
||||
} else {
|
||||
wire_draw_depth(wire, depth_map)
|
||||
},
|
||||
0.0,
|
||||
],
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// One batch per (vertex chunk × instance chunk). Instance buffers are built
|
||||
/// once and shared across vertex chunks rather than re-uploaded per chunk.
|
||||
fn push_block_batches(
|
||||
out: &mut Vec<BlockWireGpu>,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
vertex_chunks: &[(wgpu::Buffer, u32)],
|
||||
instances: &[BlockWireInstance],
|
||||
mesh_edge: bool,
|
||||
const_bind_group: &std::sync::Arc<wgpu::BindGroup>,
|
||||
) {
|
||||
let max_instances = super::gpu_budget::max_elements::<BlockWireInstance>(device);
|
||||
let instance_chunks: Vec<(wgpu::Buffer, u32)> = instances
|
||||
.chunks(max_instances)
|
||||
.map(|chunk| {
|
||||
(
|
||||
instance_buffer(device, queue, "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(BlockWireGpu {
|
||||
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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Upload one constant chunk; its instances use chunk-local wire IDs.
|
||||
fn build_const_bind_group(
|
||||
device: &wgpu::Device,
|
||||
|
||||
+18
-34
@@ -654,8 +654,7 @@ impl shader::Primitive for Primitive {
|
||||
&& (inner.wire_const_bgl.is_some()
|
||||
|| ((vp.wire_patch.is_some()
|
||||
|| inner.wire_arena_id != u64::MAX)
|
||||
&& packed_arena_owner))
|
||||
&& !vp_wires.iter().any(|wire| wire.render_instance.is_some());
|
||||
&& packed_arena_owner));
|
||||
if use_wire_arena {
|
||||
use crate::scene::pipeline::wire_arena::{
|
||||
self, PersistentWireArena as WireArena,
|
||||
@@ -696,21 +695,9 @@ impl shader::Primitive for Primitive {
|
||||
.get(&handle)
|
||||
.map(|wires| wires.as_slice())
|
||||
.unwrap_or(&[]);
|
||||
let mesh_entity = run
|
||||
.iter()
|
||||
.any(|w| !w.fill_tris.is_empty() && !w.fill_tris_low.is_empty());
|
||||
regular_changed.insert(
|
||||
handle,
|
||||
run.iter()
|
||||
.filter(|w| !w.points.is_empty() && !mesh_entity)
|
||||
.collect(),
|
||||
);
|
||||
mesh_changed.insert(
|
||||
handle,
|
||||
run.iter()
|
||||
.filter(|w| !w.points.is_empty() && mesh_entity)
|
||||
.collect(),
|
||||
);
|
||||
let (regular, mesh) = wire_arena::split_wires(run);
|
||||
regular_changed.insert(handle, regular);
|
||||
mesh_changed.insert(handle, mesh);
|
||||
}
|
||||
}
|
||||
let fallback_touched = |mesh_edge: bool| {
|
||||
@@ -758,22 +745,7 @@ impl shader::Primitive for Primitive {
|
||||
if !reg_ok || !mesh_ok {
|
||||
// Initial upload or a patch that outgrew arena capacity:
|
||||
// only then pay the full regular/mesh split.
|
||||
let mesh_names: rustc_hash::FxHashSet<u64> = vp_wires
|
||||
.iter()
|
||||
.filter(|w| !w.fill_tris.is_empty() && !w.fill_tris_low.is_empty())
|
||||
.filter_map(|w| w.name.parse::<u64>().ok())
|
||||
.collect();
|
||||
let regular: Vec<&crate::scene::WireModel> = vp_wires
|
||||
.iter()
|
||||
.filter(|w| {
|
||||
!w.points.is_empty()
|
||||
&& !wire_arena::is_mesh_edge(w, &mesh_names)
|
||||
})
|
||||
.collect();
|
||||
let mesh: Vec<&crate::scene::WireModel> = vp_wires
|
||||
.iter()
|
||||
.filter(|w| wire_arena::is_mesh_edge(w, &mesh_names))
|
||||
.collect();
|
||||
let (regular, mesh) = wire_arena::split_wires(&vp_wires);
|
||||
if !reg_ok {
|
||||
inner.wire_arena = WireArena::build(
|
||||
device,
|
||||
@@ -874,7 +846,19 @@ impl shader::Primitive for Primitive {
|
||||
gpus.extend(arena.wire_gpus());
|
||||
}
|
||||
inner.gpu_wires = std::sync::Arc::new(gpus);
|
||||
inner.gpu_block_wires = std::sync::Arc::new(Vec::new());
|
||||
// Blocks keep their own cached geometry and placements.
|
||||
let instanced: Vec<&crate::scene::WireModel> = vp_wires
|
||||
.iter()
|
||||
.filter(|w| w.render_instance.is_some())
|
||||
.collect();
|
||||
inner.gpu_block_wires = std::sync::Arc::new(
|
||||
inner.upload_block_wires(
|
||||
device,
|
||||
queue,
|
||||
&instanced,
|
||||
&draw_depths,
|
||||
),
|
||||
);
|
||||
if _patched {
|
||||
wire_arena::patch_handle_index(
|
||||
&mut inner.wire_handle_index,
|
||||
|
||||
Reference in new issue
Block a user