fix: correct polyline arc direction, text precision, Face3D edge color, buffer chunking
- fix: polyline arc center sign was inverted (- → +) causing arcs to render on the wrong side in LwPolyline and Polyline2D - fix: store glyph strokes in local space with f64 origin so world_offset subtraction uses f64 precision; prevents blocky text at large UTM coordinates - fix: Face3D fill darkened to 45% so edge wires are visually distinct from fill - fix: wire batch vertex buffer chunked to 256 MB GPU limit (was panic on large files) - fix: gpu_face3d_edges changed from Option<WireGpu> to Vec<WireGpu> to support chunks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
02203dba93
commit
1a19a841e6
11 changed files with 136 additions and 113 deletions
|
|
@ -65,9 +65,10 @@ impl Face3DGpu {
|
|||
if wire.key_vertices.len() < 4 {
|
||||
continue;
|
||||
}
|
||||
let color = wire.color;
|
||||
let [r, g, b, a] = wire.color;
|
||||
let fill_color = [r * 0.45, g * 0.45, b * 0.45, a];
|
||||
let p = &wire.key_vertices;
|
||||
let v = |i: usize| Face3DVertex { position: p[i], color };
|
||||
let v = |i: usize| Face3DVertex { position: p[i], color: fill_color };
|
||||
|
||||
// Triangle 1: p0, p1, p2
|
||||
vertices.push(v(0));
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ pub struct Pipeline {
|
|||
gpu_meshes: Vec<MeshGpu>,
|
||||
/// Batched 3DFACE fill (all faces in one buffer) and edges (merged wire).
|
||||
gpu_face3d_fill: Option<Face3DGpu>,
|
||||
gpu_face3d_edges: Option<WireGpu>,
|
||||
gpu_face3d_edges: Vec<WireGpu>,
|
||||
pub viewcube: ViewCubePipeline,
|
||||
/// Last geometry epoch for which GPU buffers were uploaded.
|
||||
/// Initialized to u64::MAX so the first frame always uploads.
|
||||
|
|
@ -590,7 +590,7 @@ impl Pipeline {
|
|||
gpu_images: vec![],
|
||||
gpu_meshes: vec![],
|
||||
gpu_face3d_fill: None,
|
||||
gpu_face3d_edges: None,
|
||||
gpu_face3d_edges: vec![],
|
||||
viewcube,
|
||||
cached_epoch: u64::MAX,
|
||||
}
|
||||
|
|
@ -613,7 +613,7 @@ impl Pipeline {
|
|||
pub fn upload_face3d(&mut self, device: &wgpu::Device, face3d_wires: &[WireModel]) {
|
||||
if face3d_wires.is_empty() {
|
||||
self.gpu_face3d_fill = None;
|
||||
self.gpu_face3d_edges = None;
|
||||
self.gpu_face3d_edges = vec![];
|
||||
return;
|
||||
}
|
||||
self.gpu_face3d_fill = Some(Face3DGpu::from_wires(device, face3d_wires));
|
||||
|
|
@ -813,36 +813,38 @@ impl Pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Pass 5b: 3DFACE edges (batched) ──────────────────────────────
|
||||
if let Some(ref edges) = self.gpu_face3d_edges {
|
||||
if edges.vertex_count >= 6 {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("face3d_edges.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
// ── Pass 5b: 3DFACE edges (batched, possibly multiple chunks) ────
|
||||
if !self.gpu_face3d_edges.is_empty() {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("face3d_edges.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||
pass.set_pipeline(&self.wire_pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, edges.vertex_buffer.slice(..));
|
||||
pass.draw(0..edges.vertex_count, 0..1);
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||
pass.set_pipeline(&self.wire_pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
for edges in &self.gpu_face3d_edges {
|
||||
if edges.vertex_count >= 6 {
|
||||
pass.set_vertex_buffer(0, edges.vertex_buffer.slice(..));
|
||||
pass.draw(0..edges.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -125,13 +125,13 @@ impl WireGpu {
|
|||
Self::build(device, wire, [r, g, b, a * alpha])
|
||||
}
|
||||
|
||||
/// Merge multiple WireModels into a single GPU buffer (1 draw call for all).
|
||||
/// Merge multiple WireModels into GPU buffers chunked to fit the 256 MB GPU limit.
|
||||
/// Each wire keeps its own color and pattern — they're stored per-vertex.
|
||||
/// Returns None if the combined vertex list is empty.
|
||||
pub fn from_batch(device: &wgpu::Device, wires: &[WireModel]) -> Option<Self> {
|
||||
/// Returns an empty Vec if the combined vertex list is empty.
|
||||
pub fn from_batch(device: &wgpu::Device, wires: &[WireModel]) -> Vec<Self> {
|
||||
let total_segs: usize = wires.iter().map(|w| w.points.len().saturating_sub(1)).sum();
|
||||
if total_segs == 0 {
|
||||
return None;
|
||||
return vec![];
|
||||
}
|
||||
let mut vertices: Vec<WireVertex> = Vec::with_capacity(total_segs * 6);
|
||||
|
||||
|
|
@ -186,19 +186,27 @@ impl WireGpu {
|
|||
}
|
||||
|
||||
if vertices.is_empty() {
|
||||
return None;
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("wire.batch.vbuf"),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
// GPU max buffer size is 256 MB; chunk to stay within the limit.
|
||||
const MAX_VERTS: usize = 268_435_456 / std::mem::size_of::<WireVertex>();
|
||||
|
||||
Some(Self {
|
||||
vertex_buffer,
|
||||
vertex_count: vertices.len() as u32,
|
||||
})
|
||||
vertices
|
||||
.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,
|
||||
});
|
||||
Self {
|
||||
vertex_buffer,
|
||||
vertex_count: chunk.len() as u32,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build(device: &wgpu::Device, wire: &WireModel, color: [f32; 4]) -> Self {
|
||||
|
|
|
|||
Loading…
Reference in a new issue