forked from 1b-agent/opencadstudio-upstream
Merge PR #1001: GPU allocation stability and Greek locale
This commit is contained in:
commit
75f9205da4
37 files changed
+4399
-389
No files matched your search
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = إغلاق
|
.close = إغلاق
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Затвори
|
.close = Затвори
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Zavřít
|
.close = Zavřít
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Schließen
|
.close = Schließen
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Close
|
.close = Close
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Cerrar
|
.close = Cerrar
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Sulje
|
.close = Sulje
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Fermer
|
.close = Fermer
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = बंद करें
|
.close = बंद करें
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Bezárás
|
.close = Bezárás
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Chiudi
|
.close = Chiudi
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = 閉じる
|
.close = 閉じる
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = 닫기
|
.close = 닫기
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Sluiten
|
.close = Sluiten
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Zamknij
|
.close = Zamknij
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Fechar
|
.close = Fechar
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Замкнуть
|
.close = Замкнуть
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = Kapat
|
.close = Kapat
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = 关闭
|
.close = 关闭
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ language =
|
|||||||
.polish = Polski
|
.polish = Polski
|
||||||
.chinese-traditional = 繁體中文
|
.chinese-traditional = 繁體中文
|
||||||
.bulgarian = Български
|
.bulgarian = Български
|
||||||
|
.greek = Ελληνικά
|
||||||
|
|
||||||
action =
|
action =
|
||||||
.close = 關閉
|
.close = 關閉
|
||||||
|
|||||||
+6
-1
@@ -65,10 +65,12 @@ pub enum Language {
|
|||||||
PlPl,
|
PlPl,
|
||||||
#[serde(rename = "zh-TW")]
|
#[serde(rename = "zh-TW")]
|
||||||
ZhTw,
|
ZhTw,
|
||||||
|
#[serde(rename = "el-GR")]
|
||||||
|
ElGr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Language {
|
impl Language {
|
||||||
pub const ALL: [Language; 21] = [
|
pub const ALL: [Language; 22] = [
|
||||||
Language::System,
|
Language::System,
|
||||||
Language::EnUs,
|
Language::EnUs,
|
||||||
Language::BgBg,
|
Language::BgBg,
|
||||||
@@ -78,6 +80,7 @@ impl Language {
|
|||||||
Language::FrFr,
|
Language::FrFr,
|
||||||
Language::FiFi,
|
Language::FiFi,
|
||||||
Language::DeDe,
|
Language::DeDe,
|
||||||
|
Language::ElGr,
|
||||||
Language::HuHu,
|
Language::HuHu,
|
||||||
Language::ItIt,
|
Language::ItIt,
|
||||||
Language::JaJp,
|
Language::JaJp,
|
||||||
@@ -115,6 +118,7 @@ impl Language {
|
|||||||
Language::HuHu => vec!["hu-HU".parse().expect("valid locale")],
|
Language::HuHu => vec!["hu-HU".parse().expect("valid locale")],
|
||||||
Language::PlPl => vec!["pl-PL".parse().expect("valid locale")],
|
Language::PlPl => vec!["pl-PL".parse().expect("valid locale")],
|
||||||
Language::ZhTw => vec!["zh-TW".parse().expect("valid locale")],
|
Language::ZhTw => vec!["zh-TW".parse().expect("valid locale")],
|
||||||
|
Language::ElGr => vec!["el-GR".parse().expect("valid locale")],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +145,7 @@ impl Language {
|
|||||||
Language::HuHu => crate::tr!("language", "hungarian"),
|
Language::HuHu => crate::tr!("language", "hungarian"),
|
||||||
Language::PlPl => crate::tr!("language", "polish"),
|
Language::PlPl => crate::tr!("language", "polish"),
|
||||||
Language::ZhTw => crate::tr!("language", "chinese-traditional"),
|
Language::ZhTw => crate::tr!("language", "chinese-traditional"),
|
||||||
|
Language::ElGr => crate::tr!("language", "greek"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
use crate::scene::model::wire_model::WireModel;
|
use crate::scene::model::wire_model::WireModel;
|
||||||
use iced::wgpu;
|
use iced::wgpu;
|
||||||
use iced::wgpu::util::DeviceExt;
|
|
||||||
|
|
||||||
pub(crate) fn planar_solid_faces_view(wire: &WireModel, view_dir: glam::Vec3) -> bool {
|
pub(crate) fn planar_solid_faces_view(wire: &WireModel, view_dir: glam::Vec3) -> bool {
|
||||||
if !wire.fill_is_2d_solid || wire.fill_is_3d {
|
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.
|
/// legacy planar SOLID interior while keeping other 2-D overlays.
|
||||||
pub fn from_wires(
|
pub fn from_wires(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
face3d_wires: &[WireModel],
|
face3d_wires: &[WireModel],
|
||||||
all_wires: &[WireModel],
|
all_wires: &[WireModel],
|
||||||
keep_3d_mesh_fills: bool,
|
keep_3d_mesh_fills: bool,
|
||||||
@@ -302,6 +302,7 @@ impl Face3DGpu {
|
|||||||
|
|
||||||
let (block_chunks_3d, block_chunks_2d) = upload_block_chunks(
|
let (block_chunks_3d, block_chunks_2d) = upload_block_chunks(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
all_wires,
|
all_wires,
|
||||||
keep_3d_mesh_fills,
|
keep_3d_mesh_fills,
|
||||||
show_2d_solid_fills,
|
show_2d_solid_fills,
|
||||||
@@ -309,8 +310,8 @@ impl Face3DGpu {
|
|||||||
depth_map,
|
depth_map,
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
chunks_3d: upload_chunks(device, &verts_3d, "face3d.vbuf.3d"),
|
chunks_3d: upload_chunks(device, queue, &verts_3d, "face3d.vbuf.3d"),
|
||||||
chunks_2d: upload_chunks(device, &verts_2d, "face3d.vbuf.2d"),
|
chunks_2d: upload_chunks(device, queue, &verts_2d, "face3d.vbuf.2d"),
|
||||||
block_chunks_3d,
|
block_chunks_3d,
|
||||||
block_chunks_2d,
|
block_chunks_2d,
|
||||||
}
|
}
|
||||||
@@ -319,6 +320,7 @@ impl Face3DGpu {
|
|||||||
|
|
||||||
fn upload_block_chunks(
|
fn upload_block_chunks(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[WireModel],
|
wires: &[WireModel],
|
||||||
keep_3d_mesh_fills: bool,
|
keep_3d_mesh_fills: bool,
|
||||||
show_2d_solid_fills: bool,
|
show_2d_solid_fills: bool,
|
||||||
@@ -376,11 +378,9 @@ fn upload_block_chunks(
|
|||||||
if vertices.is_empty() {
|
if vertices.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let budget = (device.limits().max_buffer_size as usize / 10) * 9;
|
// Triangle lists: chunks must stay a multiple of 3.
|
||||||
let max_vertices = ((budget / std::mem::size_of::<Face3DVertex>()).max(3) / 3) * 3;
|
let max_vertices = super::gpu_budget::max_elements_grouped::<Face3DVertex>(device, 3);
|
||||||
let max_instances = ((device.limits().max_buffer_size as usize / 10) * 9
|
let max_instances = super::gpu_budget::max_elements::<Face3DInstance>(device);
|
||||||
/ std::mem::size_of::<Face3DInstance>())
|
|
||||||
.max(1);
|
|
||||||
let instances: Vec<Face3DInstance> = group
|
let instances: Vec<Face3DInstance> = group
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|wire| {
|
.filter_map(|wire| {
|
||||||
@@ -417,21 +417,25 @@ fn upload_block_chunks(
|
|||||||
.chunks(max_instances)
|
.chunks(max_instances)
|
||||||
.map(|chunk| {
|
.map(|chunk| {
|
||||||
(
|
(
|
||||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
super::gpu_upload::upload_buffer(
|
||||||
label: Some("face3d.block.instances"),
|
device,
|
||||||
contents: bytemuck::cast_slice(chunk),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"face3d.block.instances",
|
||||||
}),
|
chunk,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
),
|
||||||
chunk.len() as u32,
|
chunk.len() as u32,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
for vertex_chunk in vertices.chunks(max_vertices) {
|
for vertex_chunk in vertices.chunks(max_vertices) {
|
||||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let vertex_buffer = super::gpu_upload::upload_buffer(
|
||||||
label: Some("face3d.block.vertices"),
|
device,
|
||||||
contents: bytemuck::cast_slice(vertex_chunk),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"face3d.block.vertices",
|
||||||
});
|
vertex_chunk,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
);
|
||||||
for (instance_buffer, instance_count) in &instance_buffers {
|
for (instance_buffer, instance_count) in &instance_buffers {
|
||||||
let chunk = BlockFace3DChunk {
|
let chunk = BlockFace3DChunk {
|
||||||
vertex_buffer: vertex_buffer.clone(),
|
vertex_buffer: vertex_buffer.clone(),
|
||||||
@@ -456,21 +460,22 @@ fn upload_block_chunks(
|
|||||||
/// range never truncates.
|
/// range never truncates.
|
||||||
fn upload_chunks(
|
fn upload_chunks(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
verts: &[Face3DVertex],
|
verts: &[Face3DVertex],
|
||||||
label: &'static str,
|
label: &'static str,
|
||||||
) -> Vec<Face3DChunk> {
|
) -> Vec<Face3DChunk> {
|
||||||
let budget = (device.limits().max_buffer_size as usize / 10) * 9; // 10% headroom
|
|
||||||
let vsize = std::mem::size_of::<Face3DVertex>();
|
|
||||||
// Round down to a multiple of 3 so triangles never straddle chunks.
|
// Round down to a multiple of 3 so triangles never straddle chunks.
|
||||||
let max_verts = ((budget / vsize).max(3) / 3) * 3;
|
let max_verts = super::gpu_budget::max_elements_grouped::<Face3DVertex>(device, 3);
|
||||||
verts
|
verts
|
||||||
.chunks(max_verts)
|
.chunks(max_verts)
|
||||||
.map(|c| Face3DChunk {
|
.map(|c| Face3DChunk {
|
||||||
vertex_buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
vertex_buffer: super::gpu_upload::upload_buffer(
|
||||||
label: Some(label),
|
device,
|
||||||
contents: bytemuck::cast_slice(c),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
label,
|
||||||
}),
|
c,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
),
|
||||||
vertex_count: c.len() as u32,
|
vertex_count: c.len() as u32,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
//! One VRAM-aware budget for every GPU buffer this renderer allocates.
|
||||||
|
//!
|
||||||
|
//! `wgpu::Limits::max_buffer_size` is an **API validation limit**, not a
|
||||||
|
//! statement about physical memory: NVIDIA reports several GB of it on a 2 GB
|
||||||
|
//! card. Sizing a chunk from that limit therefore produces buffers larger than
|
||||||
|
//! the whole device, the allocation fails, and the frame dies — issue #203
|
||||||
|
//! (`mesh_gpu`), #358 (`face3d_gpu`) and the block-wire path were all the same
|
||||||
|
//! bug rediscovered.
|
||||||
|
//!
|
||||||
|
//! `mesh_gpu.rs` was the only site that clamped the result. This module makes
|
||||||
|
//! that clamp the single shared rule so a new upload path cannot reintroduce
|
||||||
|
//! the mistake by copying one of the unclamped expressions.
|
||||||
|
|
||||||
|
use iced::wgpu;
|
||||||
|
|
||||||
|
/// Upper bound on any single buffer, regardless of what the device claims it
|
||||||
|
/// would validate. Chosen to match the clamp `mesh_gpu` has used since #203:
|
||||||
|
/// large enough that chunk overhead stays negligible, small enough that a
|
||||||
|
/// failed allocation is recoverable on a low-VRAM card.
|
||||||
|
const HARD_CAP_BYTES: usize = 32 * 1024 * 1024;
|
||||||
|
|
||||||
|
/// 10% headroom below the device's validation limit, matching the `/ 10 * 9`
|
||||||
|
/// the individual sites used before they shared this helper.
|
||||||
|
fn device_ceiling(device: &wgpu::Device) -> usize {
|
||||||
|
(device.limits().max_buffer_size as usize / 10) * 9
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test/debug override, in MiB. Raising it above the real VRAM is the
|
||||||
|
/// cheapest way to force an out-of-memory path and check that the renderer
|
||||||
|
/// degrades the frame instead of aborting.
|
||||||
|
fn cap_bytes() -> usize {
|
||||||
|
static CAP: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
|
||||||
|
*CAP.get_or_init(|| {
|
||||||
|
std::env::var("OCS_GPU_CHUNK_MIB")
|
||||||
|
.ok()
|
||||||
|
.and_then(|value| value.trim().parse::<usize>().ok())
|
||||||
|
.filter(|mib| *mib > 0)
|
||||||
|
.map(|mib| mib * 1024 * 1024)
|
||||||
|
.unwrap_or(HARD_CAP_BYTES)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Largest byte size a single buffer built by this renderer may take.
|
||||||
|
pub fn buffer_budget(device: &wgpu::Device) -> usize {
|
||||||
|
device_ceiling(device).min(cap_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How many `T` fit in one buffer. Never returns 0, so callers can pass the
|
||||||
|
/// result straight to `slice::chunks`, which panics on a zero chunk size.
|
||||||
|
pub fn max_elements<T>(device: &wgpu::Device) -> usize {
|
||||||
|
(buffer_budget(device) / std::mem::size_of::<T>().max(1)).max(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `max_elements` rounded down to a whole number of `group` elements, for
|
||||||
|
/// vertex data whose primitives must not straddle a chunk boundary: 3 for a
|
||||||
|
/// triangle list, 6 for the quad expansion the wire shaders use.
|
||||||
|
///
|
||||||
|
/// Always returns at least one whole group.
|
||||||
|
pub fn max_elements_grouped<T>(device: &wgpu::Device, group: usize) -> usize {
|
||||||
|
let group = group.max(1);
|
||||||
|
(max_elements::<T>(device) / group).max(1) * group
|
||||||
|
}
|
||||||
@@ -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)
|
.filter(|hatch| hatch.boundary.len() >= 3)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
*resident = StorageHatchBatch::build(device, &self.bind_group_layout, &renderable);
|
*resident = StorageHatchBatch::build(device, queue, &self.bind_group_layout, &renderable);
|
||||||
}
|
}
|
||||||
HatchBackend::Texture { resident, .. } => {
|
HatchBackend::Texture { resident, .. } => {
|
||||||
let renderable: Vec<HatchModel> = hatches
|
let renderable: Vec<HatchModel> = hatches
|
||||||
@@ -198,7 +198,7 @@ impl HatchGpu {
|
|||||||
.filter(|hatch| hatch.boundary.len() >= 3)
|
.filter(|hatch| hatch.boundary.len() >= 3)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
*preview = StorageHatchBatch::build(device, &self.bind_group_layout, &renderable);
|
*preview = StorageHatchBatch::build(device, queue, &self.bind_group_layout, &renderable);
|
||||||
}
|
}
|
||||||
HatchBackend::Texture { preview, .. } => {
|
HatchBackend::Texture { preview, .. } => {
|
||||||
let renderable: Vec<HatchModel> = hatches
|
let renderable: Vec<HatchModel> = hatches
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
use crate::scene::model::hatch_model::{HatchModel, HatchPattern, PatFamily};
|
use crate::scene::model::hatch_model::{HatchModel, HatchPattern, PatFamily};
|
||||||
use iced::wgpu;
|
use iced::wgpu;
|
||||||
use iced::wgpu::util::DeviceExt;
|
|
||||||
|
|
||||||
// ── GPU structs ────────────────────────────────────────────────────────────
|
// ── GPU structs ────────────────────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
@@ -188,6 +187,7 @@ impl StorageHatchBatch {
|
|||||||
/// Builds whole-hatch chunks within the device's buffer limits.
|
/// Builds whole-hatch chunks within the device's buffer limits.
|
||||||
pub(super) fn build(
|
pub(super) fn build(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
bgl: &wgpu::BindGroupLayout,
|
bgl: &wgpu::BindGroupLayout,
|
||||||
hatches: &[HatchModel],
|
hatches: &[HatchModel],
|
||||||
) -> Vec<Self> {
|
) -> Vec<Self> {
|
||||||
@@ -196,9 +196,13 @@ impl StorageHatchBatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let limits = device.limits();
|
let limits = device.limits();
|
||||||
|
// Both device figures are API validation limits, not physical VRAM, so
|
||||||
|
// the shared budget caps them (`storage.rs` chunks are allocations, not
|
||||||
|
// just size checks — the `*_fits` predicates below are the checks).
|
||||||
let chunk_limit = limits
|
let chunk_limit = limits
|
||||||
.max_buffer_size
|
.max_buffer_size
|
||||||
.min(limits.max_storage_buffer_binding_size as u64);
|
.min(limits.max_storage_buffer_binding_size as u64)
|
||||||
|
.min(crate::scene::pipeline::gpu_budget::buffer_budget(device) as u64);
|
||||||
let mut ranges = Vec::new();
|
let mut ranges = Vec::new();
|
||||||
let mut start = 0;
|
let mut start = 0;
|
||||||
let mut used = [0u64; 7];
|
let mut used = [0u64; 7];
|
||||||
@@ -220,12 +224,13 @@ impl StorageHatchBatch {
|
|||||||
ranges.push(start..hatches.len());
|
ranges.push(start..hatches.len());
|
||||||
ranges
|
ranges
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|range| Self::build_chunk(device, bgl, &hatches[range]))
|
.filter_map(|range| Self::build_chunk(device, queue, bgl, &hatches[range]))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_chunk(
|
fn build_chunk(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
bgl: &wgpu::BindGroupLayout,
|
bgl: &wgpu::BindGroupLayout,
|
||||||
hatches: &[HatchModel],
|
hatches: &[HatchModel],
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
@@ -496,42 +501,56 @@ impl StorageHatchBatch {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let vertex_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||||
label: Some("hatch.vertex"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&verts),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"hatch.vertex",
|
||||||
});
|
&verts,
|
||||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
wgpu::BufferUsages::VERTEX,
|
||||||
label: Some("hatch.index"),
|
);
|
||||||
contents: bytemuck::cast_slice(&indices),
|
let index_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||||
usage: wgpu::BufferUsages::INDEX,
|
device,
|
||||||
});
|
queue,
|
||||||
let placement_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
"hatch.index",
|
||||||
label: Some("hatch.placements"),
|
&indices,
|
||||||
contents: bytemuck::cast_slice(&placements),
|
wgpu::BufferUsages::INDEX,
|
||||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
);
|
||||||
});
|
let placement_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||||
let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
device,
|
||||||
label: Some("hatch.instances"),
|
queue,
|
||||||
contents: bytemuck::cast_slice(&instances),
|
"hatch.placements",
|
||||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
&placements,
|
||||||
});
|
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||||
let family_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
);
|
||||||
label: Some("hatch.families"),
|
let instance_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||||
contents: bytemuck::cast_slice(&families),
|
device,
|
||||||
usage: wgpu::BufferUsages::STORAGE,
|
queue,
|
||||||
});
|
"hatch.instances",
|
||||||
let dash_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
&instances,
|
||||||
label: Some("hatch.dashes"),
|
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||||
contents: bytemuck::cast_slice(&dashes),
|
);
|
||||||
usage: wgpu::BufferUsages::STORAGE,
|
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 {
|
let visibility_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||||
label: Some("hatch.visibility"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&visibility),
|
queue,
|
||||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
"hatch.visibility",
|
||||||
});
|
&visibility,
|
||||||
|
wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||||
|
);
|
||||||
|
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some("hatch.bg"),
|
label: Some("hatch.bg"),
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
use crate::scene::model::hatch_model::{HatchModel, HatchPattern};
|
use crate::scene::model::hatch_model::{HatchModel, HatchPattern};
|
||||||
use iced::wgpu;
|
use iced::wgpu;
|
||||||
use iced::wgpu::util::DeviceExt;
|
|
||||||
|
|
||||||
/// Width (in texels) of the RGBA32F data texture. Height grows to fit; a hatch
|
/// Width (in texels) of the RGBA32F data texture. Height grows to fit; a hatch
|
||||||
/// with N total texels uses ceil(N / WIDTH) rows.
|
/// with N total texels uses ceil(N / WIDTH) rows.
|
||||||
@@ -199,16 +198,20 @@ impl TextureHatch {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|[x, y]| HatchVertex { pos: [x, y, 0.0], _pad: 0.0 })
|
.map(|[x, y]| HatchVertex { pos: [x, y, 0.0], _pad: 0.0 })
|
||||||
.collect();
|
.collect();
|
||||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let vertex_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||||
label: Some("hatch.texture.vbuf"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&vertices),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"hatch.texture.vbuf",
|
||||||
});
|
&vertices,
|
||||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
wgpu::BufferUsages::VERTEX,
|
||||||
label: Some("hatch.texture.ibuf"),
|
);
|
||||||
contents: bytemuck::cast_slice(&indices),
|
let index_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||||
usage: wgpu::BufferUsages::INDEX,
|
device,
|
||||||
});
|
queue,
|
||||||
|
"hatch.texture.ibuf",
|
||||||
|
&indices,
|
||||||
|
wgpu::BufferUsages::INDEX,
|
||||||
|
);
|
||||||
let base = model
|
let base = model
|
||||||
.render_instance
|
.render_instance
|
||||||
.map_or([0.0; 3], |instance| instance.translation);
|
.map_or([0.0; 3], |instance| instance.translation);
|
||||||
@@ -231,11 +234,13 @@ impl TextureHatch {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let placement_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let placement_buffer = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||||
label: Some("hatch.texture.placements"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&placements),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"hatch.texture.placements",
|
||||||
});
|
&placements,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
);
|
||||||
|
|
||||||
// ── Gradient projection range (snapped-local space) ───────────────
|
// ── Gradient projection range (snapped-local space) ───────────────
|
||||||
let base_mode = mode & 0xFF;
|
let base_mode = mode & 0xFF;
|
||||||
@@ -371,11 +376,13 @@ impl TextureHatch {
|
|||||||
dash_off,
|
dash_off,
|
||||||
tex_width: width,
|
tex_width: width,
|
||||||
};
|
};
|
||||||
let _uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let _uniform_buf = crate::scene::pipeline::gpu_upload::upload_buffer(
|
||||||
label: Some("hatch.texture.uniform"),
|
device,
|
||||||
contents: bytemuck::bytes_of(&uniform_data),
|
queue,
|
||||||
usage: wgpu::BufferUsages::UNIFORM,
|
"hatch.texture.uniform",
|
||||||
});
|
std::slice::from_ref(&uniform_data),
|
||||||
|
wgpu::BufferUsages::UNIFORM,
|
||||||
|
);
|
||||||
|
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some("hatch.texture.bind_group1"),
|
label: Some("hatch.texture.bind_group1"),
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
use crate::scene::model::image_model::ImageModel;
|
use crate::scene::model::image_model::ImageModel;
|
||||||
use iced::wgpu;
|
use iced::wgpu;
|
||||||
use iced::wgpu::util::DeviceExt;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
// ── Vertex ────────────────────────────────────────────────────────────────
|
// ── Vertex ────────────────────────────────────────────────────────────────
|
||||||
@@ -179,24 +178,24 @@ impl ImageGpu {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let instance_buffer = Arc::new(device.create_buffer_init(
|
let instance_buffer = Arc::new(super::gpu_upload::upload_buffer(
|
||||||
&wgpu::util::BufferInitDescriptor {
|
device,
|
||||||
label: Some("image.instances"),
|
queue,
|
||||||
contents: bytemuck::cast_slice(&instances),
|
"image.instances",
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
&instances,
|
||||||
},
|
wgpu::BufferUsages::VERTEX,
|
||||||
));
|
));
|
||||||
let params = ImageParams {
|
let params = ImageParams {
|
||||||
opacity: model.opacity.clamp(0.0, 1.0),
|
opacity: model.opacity.clamp(0.0, 1.0),
|
||||||
draw_depth: 0.0,
|
draw_depth: 0.0,
|
||||||
_pad: [0.0; 2],
|
_pad: [0.0; 2],
|
||||||
};
|
};
|
||||||
let params_buf = Arc::new(device.create_buffer_init(
|
let params_buf = Arc::new(super::gpu_upload::upload_buffer(
|
||||||
&wgpu::util::BufferInitDescriptor {
|
device,
|
||||||
label: Some("image.params"),
|
queue,
|
||||||
contents: bytemuck::bytes_of(¶ms),
|
"image.params",
|
||||||
usage: wgpu::BufferUsages::UNIFORM,
|
std::slice::from_ref(¶ms),
|
||||||
},
|
wgpu::BufferUsages::UNIFORM,
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut output = Vec::with_capacity(x_tiles.len() * y_tiles.len());
|
let mut output = Vec::with_capacity(x_tiles.len() * y_tiles.len());
|
||||||
@@ -264,12 +263,13 @@ impl ImageGpu {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
let vertex_buffer =
|
let vertex_buffer = super::gpu_upload::upload_buffer(
|
||||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
device,
|
||||||
label: Some("image.vbuf"),
|
queue,
|
||||||
contents: bytemuck::cast_slice(&verts),
|
"image.vbuf",
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
&verts,
|
||||||
});
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
);
|
||||||
output.push(Self {
|
output.push(Self {
|
||||||
vertex_buffer,
|
vertex_buffer,
|
||||||
instance_buffer: Arc::clone(&instance_buffer),
|
instance_buffer: Arc::clone(&instance_buffer),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
|
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
|
||||||
use iced::wgpu;
|
use iced::wgpu;
|
||||||
use iced::wgpu::util::DeviceExt;
|
|
||||||
|
|
||||||
// ── Vertex layout ─────────────────────────────────────────────────────────
|
// ── Vertex layout ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -377,13 +376,15 @@ struct MeshBatchStubs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MeshBatchStubs {
|
impl MeshBatchStubs {
|
||||||
fn new(device: &wgpu::Device) -> Self {
|
fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
|
||||||
Self {
|
Self {
|
||||||
index: device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
index: super::gpu_upload::upload_buffer(
|
||||||
label: Some("mesh.batch.empty_ibuf"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&[0u32]),
|
queue,
|
||||||
usage: wgpu::BufferUsages::INDEX,
|
"mesh.batch.empty_ibuf",
|
||||||
}),
|
&[0u32],
|
||||||
|
wgpu::BufferUsages::INDEX,
|
||||||
|
),
|
||||||
vertex: device.create_buffer(&wgpu::BufferDescriptor {
|
vertex: device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
label: Some("mesh.batch.empty_vbuf"),
|
label: Some("mesh.batch.empty_vbuf"),
|
||||||
size: std::mem::size_of::<MeshVertex>() as u64,
|
size: std::mem::size_of::<MeshVertex>() as u64,
|
||||||
@@ -784,11 +785,13 @@ pub fn create_material_resources(
|
|||||||
[material.color_bleed_scale, 0.0, 0.0, 0.0]
|
[material.color_bleed_scale, 0.0, 0.0, 0.0]
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
let params_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let params_buffer = super::gpu_upload::upload_buffer(
|
||||||
label: Some("mesh.material.params"),
|
device,
|
||||||
contents: bytemuck::bytes_of(¶ms),
|
queue,
|
||||||
usage: wgpu::BufferUsages::UNIFORM,
|
"mesh.material.params",
|
||||||
});
|
std::slice::from_ref(¶ms),
|
||||||
|
wgpu::BufferUsages::UNIFORM,
|
||||||
|
);
|
||||||
MeshMaterialResources {
|
MeshMaterialResources {
|
||||||
diffuse_view,
|
diffuse_view,
|
||||||
specular_view,
|
specular_view,
|
||||||
@@ -810,6 +813,7 @@ pub fn create_material_resources(
|
|||||||
|
|
||||||
pub fn create_material_bind_group_from_resources(
|
pub fn create_material_bind_group_from_resources(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
layout: &wgpu::BindGroupLayout,
|
layout: &wgpu::BindGroupLayout,
|
||||||
resources: &MeshMaterialResources,
|
resources: &MeshMaterialResources,
|
||||||
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
||||||
@@ -825,11 +829,13 @@ pub fn create_material_bind_group_from_resources(
|
|||||||
advanced,
|
advanced,
|
||||||
flags,
|
flags,
|
||||||
};
|
};
|
||||||
let surface_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let surface_buffer = super::gpu_upload::upload_buffer(
|
||||||
label: Some("mesh.material.surface"),
|
device,
|
||||||
contents: bytemuck::bytes_of(&surface),
|
queue,
|
||||||
usage: wgpu::BufferUsages::UNIFORM,
|
"mesh.material.surface",
|
||||||
});
|
std::slice::from_ref(&surface),
|
||||||
|
wgpu::BufferUsages::UNIFORM,
|
||||||
|
);
|
||||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some("mesh.material.bind_group"),
|
label: Some("mesh.material.bind_group"),
|
||||||
layout,
|
layout,
|
||||||
@@ -912,6 +918,7 @@ pub fn create_material_bind_group(
|
|||||||
let color = material.map_or([0.8, 0.8, 0.8, 1.0], |material| material.diffuse);
|
let color = material.map_or([0.8, 0.8, 0.8, 1.0], |material| material.diffuse);
|
||||||
create_material_bind_group_from_resources(
|
create_material_bind_group_from_resources(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
layout,
|
layout,
|
||||||
&resources,
|
&resources,
|
||||||
material,
|
material,
|
||||||
@@ -936,6 +943,7 @@ pub fn upload_chunk_material_bind_groups(
|
|||||||
let resources = create_material_resources(device, queue, Some(material));
|
let resources = create_material_resources(device, queue, Some(material));
|
||||||
chunk.material_bind_group = Some(create_material_bind_group_from_resources(
|
chunk.material_bind_group = Some(create_material_bind_group_from_resources(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
layout,
|
layout,
|
||||||
&resources,
|
&resources,
|
||||||
chunk.material.as_ref(),
|
chunk.material.as_ref(),
|
||||||
@@ -950,6 +958,7 @@ pub fn upload_chunk_material_bind_groups(
|
|||||||
};
|
};
|
||||||
chunk.material_bind_group = Some(create_material_bind_group_from_resources(
|
chunk.material_bind_group = Some(create_material_bind_group_from_resources(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
layout,
|
layout,
|
||||||
resources,
|
resources,
|
||||||
chunk.material.as_ref(),
|
chunk.material.as_ref(),
|
||||||
@@ -1580,15 +1589,13 @@ pub fn build_mesh_batch_filtered(
|
|||||||
// Derive the caps from the real device limit and vertex size. The previous
|
// Derive the caps from the real device limit and vertex size. The previous
|
||||||
// fixed 6 M-vertex cap assumed 40 B/vertex, but `position_low` (RTE) grew
|
// fixed 6 M-vertex cap assumed 40 B/vertex, but `position_low` (RTE) grew
|
||||||
// MeshVertex to 52 B, so 6 M × 52 B = 312 MB blew past the 256 MB cap.
|
// MeshVertex to 52 B, so 6 M × 52 B = 312 MB blew past the 256 MB cap.
|
||||||
let hard_budget = (device.limits().max_buffer_size as usize / 10) * 9;
|
let budget = super::gpu_budget::buffer_budget(device);
|
||||||
let budget = hard_budget.min(32 * 1024 * 1024);
|
let max_verts = super::gpu_budget::max_elements::<MeshVertex>(device).max(3);
|
||||||
let vsize = std::mem::size_of::<MeshVertex>();
|
|
||||||
let max_verts = (budget / vsize).max(3);
|
|
||||||
// A triangle contributes three line segments (six standalone edge
|
// A triangle contributes three line segments (six standalone edge
|
||||||
// vertices). Bound chunks by the largest buffer produced for wire meshes.
|
// vertices). Bound chunks by the largest buffer produced for wire meshes.
|
||||||
let max_tris =
|
let max_tris =
|
||||||
(budget / (6 * std::mem::size_of::<MeshEdgeVertex>())).max(1);
|
(budget / (6 * std::mem::size_of::<MeshEdgeVertex>())).max(1);
|
||||||
let stubs = MeshBatchStubs::new(device);
|
let stubs = MeshBatchStubs::new(device, queue);
|
||||||
|
|
||||||
let mut chunks = Vec::new();
|
let mut chunks = Vec::new();
|
||||||
let mut verts: Vec<MeshVertex> = Vec::new();
|
let mut verts: Vec<MeshVertex> = Vec::new();
|
||||||
@@ -1800,9 +1807,9 @@ pub fn build_mesh_batch_filtered(
|
|||||||
.verts
|
.verts
|
||||||
.len()
|
.len()
|
||||||
.saturating_mul(std::mem::size_of::<MeshVertex>())
|
.saturating_mul(std::mem::size_of::<MeshVertex>())
|
||||||
<= hard_budget
|
<= budget
|
||||||
&& part.indices.len().saturating_mul(std::mem::size_of::<u32>())
|
&& part.indices.len().saturating_mul(std::mem::size_of::<u32>())
|
||||||
<= hard_budget
|
<= budget
|
||||||
&& part
|
&& part
|
||||||
.set
|
.set
|
||||||
.instance_source
|
.instance_source
|
||||||
@@ -1812,7 +1819,7 @@ pub fn build_mesh_batch_filtered(
|
|||||||
.edge_verts
|
.edge_verts
|
||||||
.len()
|
.len()
|
||||||
.saturating_mul(std::mem::size_of::<MeshEdgeVertex>())
|
.saturating_mul(std::mem::size_of::<MeshEdgeVertex>())
|
||||||
<= hard_budget;
|
<= budget;
|
||||||
let triangle_edges_fit = !part.include_edges
|
let triangle_edges_fit = !part.include_edges
|
||||||
|| !source.edge_verts.is_empty()
|
|| !source.edge_verts.is_empty()
|
||||||
|| part
|
|| part
|
||||||
@@ -1820,7 +1827,7 @@ pub fn build_mesh_batch_filtered(
|
|||||||
.len()
|
.len()
|
||||||
.saturating_mul(2)
|
.saturating_mul(2)
|
||||||
.saturating_mul(std::mem::size_of::<MeshEdgeVertex>())
|
.saturating_mul(std::mem::size_of::<MeshEdgeVertex>())
|
||||||
<= hard_budget;
|
<= budget;
|
||||||
feature_edges_fit && triangle_edges_fit
|
feature_edges_fit && triangle_edges_fit
|
||||||
})
|
})
|
||||||
&& part
|
&& part
|
||||||
|
|||||||
+58
-39
@@ -1,5 +1,7 @@
|
|||||||
mod device_capabilities;
|
mod device_capabilities;
|
||||||
pub mod face3d_gpu;
|
pub mod face3d_gpu;
|
||||||
|
pub mod gpu_budget;
|
||||||
|
pub mod gpu_upload;
|
||||||
pub mod hatch_gpu;
|
pub mod hatch_gpu;
|
||||||
pub mod wipeout_gpu;
|
pub mod wipeout_gpu;
|
||||||
pub mod image_gpu;
|
pub mod image_gpu;
|
||||||
@@ -14,7 +16,6 @@ pub mod wire_arena;
|
|||||||
pub mod wire_gpu;
|
pub mod wire_gpu;
|
||||||
|
|
||||||
use iced::wgpu;
|
use iced::wgpu;
|
||||||
use iced::wgpu::util::DeviceExt;
|
|
||||||
use iced::{Rectangle, Size};
|
use iced::{Rectangle, Size};
|
||||||
|
|
||||||
pub use face3d_gpu::Face3DGpu;
|
pub use face3d_gpu::Face3DGpu;
|
||||||
@@ -2120,11 +2121,13 @@ impl Pipeline {
|
|||||||
// UV crop uniform: [uv_offset_x, uv_offset_y, uv_scale_x, uv_scale_y]
|
// 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
|
// padded to 16 bytes (std140 vec2 alignment). Defaulted to the
|
||||||
// identity crop (offset 0, scale 1) for the common on-canvas case.
|
// identity crop (offset 0, scale 1) for the common on-canvas case.
|
||||||
let blit_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let blit_uniform_buffer = gpu_upload::upload_buffer(
|
||||||
label: Some("blit.uniform_buffer"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&[0.0f32, 0.0, 1.0, 1.0]),
|
queue,
|
||||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
"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 {
|
let blit_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("blit.pipeline_layout"),
|
label: Some("blit.pipeline_layout"),
|
||||||
@@ -2340,6 +2343,7 @@ impl Pipeline {
|
|||||||
pub fn build_wire_buffers(
|
pub fn build_wire_buffers(
|
||||||
&self,
|
&self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[WireModel],
|
wires: &[WireModel],
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
) -> (
|
) -> (
|
||||||
@@ -2382,6 +2386,7 @@ impl Pipeline {
|
|||||||
let refs: Vec<&WireModel> = wires[i..j].iter().collect();
|
let refs: Vec<&WireModel> = wires[i..j].iter().collect();
|
||||||
batches.extend(WireGpu::from_run_refs(
|
batches.extend(WireGpu::from_run_refs(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&refs,
|
&refs,
|
||||||
depth_map,
|
depth_map,
|
||||||
mesh_edge,
|
mesh_edge,
|
||||||
@@ -2391,6 +2396,7 @@ impl Pipeline {
|
|||||||
}
|
}
|
||||||
let block_batches = BlockWireGpu::from_wires(
|
let block_batches = BlockWireGpu::from_wires(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&block_wires,
|
&block_wires,
|
||||||
depth_map,
|
depth_map,
|
||||||
None,
|
None,
|
||||||
@@ -2425,6 +2431,7 @@ impl Pipeline {
|
|||||||
pub fn upload_selected_wires(
|
pub fn upload_selected_wires(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[WireModel],
|
wires: &[WireModel],
|
||||||
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
hovered: &rustc_hash::FxHashSet<acadrust::Handle>,
|
hovered: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
@@ -2494,6 +2501,7 @@ impl Pipeline {
|
|||||||
let mut gpu = if let Some(tint) = selected_tint {
|
let mut gpu = if let Some(tint) = selected_tint {
|
||||||
WireGpu::from_highlight_refs(
|
WireGpu::from_highlight_refs(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&selected_regular,
|
&selected_regular,
|
||||||
tint,
|
tint,
|
||||||
depth_map,
|
depth_map,
|
||||||
@@ -2504,6 +2512,7 @@ impl Pipeline {
|
|||||||
};
|
};
|
||||||
gpu.extend(WireGpu::from_highlight_refs(
|
gpu.extend(WireGpu::from_highlight_refs(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&hover_regular,
|
&hover_regular,
|
||||||
WireModel::HOVER,
|
WireModel::HOVER,
|
||||||
depth_map,
|
depth_map,
|
||||||
@@ -2513,6 +2522,7 @@ impl Pipeline {
|
|||||||
let mut block_gpu = if let Some(tint) = selected_tint {
|
let mut block_gpu = if let Some(tint) = selected_tint {
|
||||||
BlockWireGpu::from_wires(
|
BlockWireGpu::from_wires(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&selected_blocks,
|
&selected_blocks,
|
||||||
depth_map,
|
depth_map,
|
||||||
Some(tint),
|
Some(tint),
|
||||||
@@ -2523,6 +2533,7 @@ impl Pipeline {
|
|||||||
};
|
};
|
||||||
block_gpu.extend(BlockWireGpu::from_wires(
|
block_gpu.extend(BlockWireGpu::from_wires(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&hover_blocks,
|
&hover_blocks,
|
||||||
depth_map,
|
depth_map,
|
||||||
Some(WireModel::HOVER),
|
Some(WireModel::HOVER),
|
||||||
@@ -2553,6 +2564,7 @@ impl Pipeline {
|
|||||||
pub fn upload_text_highlight(
|
pub fn upload_text_highlight(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[WireModel],
|
wires: &[WireModel],
|
||||||
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
hovered: &rustc_hash::FxHashSet<acadrust::Handle>,
|
hovered: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
@@ -2633,10 +2645,11 @@ impl Pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.text_highlight_vcount = out.len() as u32;
|
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 {
|
let mut block_gpu = if let Some(tint) = selected_tint {
|
||||||
text_gpu::upload_block_vertex_refs(
|
text_gpu::upload_block_vertex_refs(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&selected_blocks,
|
&selected_blocks,
|
||||||
depth_map,
|
depth_map,
|
||||||
Some(tint),
|
Some(tint),
|
||||||
@@ -2646,6 +2659,7 @@ impl Pipeline {
|
|||||||
};
|
};
|
||||||
block_gpu.extend(text_gpu::upload_block_vertex_refs(
|
block_gpu.extend(text_gpu::upload_block_vertex_refs(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&hover_blocks,
|
&hover_blocks,
|
||||||
depth_map,
|
depth_map,
|
||||||
Some(WireModel::HOVER),
|
Some(WireModel::HOVER),
|
||||||
@@ -2669,13 +2683,14 @@ impl Pipeline {
|
|||||||
pub fn upload_preview_wires(
|
pub fn upload_preview_wires(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[WireModel],
|
wires: &[WireModel],
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
) {
|
) {
|
||||||
self.gpu_preview_wires = if wires.is_empty() {
|
self.gpu_preview_wires = if wires.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} 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())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2708,7 +2723,7 @@ impl Pipeline {
|
|||||||
atlas.clear_dirty();
|
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;
|
self.text_preview_vcount = verts.len() as u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2719,6 +2734,7 @@ impl Pipeline {
|
|||||||
pub fn upload_silhouettes(
|
pub fn upload_silhouettes(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
sets: &[crate::scene::model::mesh_model::MeshLodSet],
|
sets: &[crate::scene::model::mesh_model::MeshLodSet],
|
||||||
content_id: u64,
|
content_id: u64,
|
||||||
view_dir: glam::Vec3,
|
view_dir: glam::Vec3,
|
||||||
@@ -2727,12 +2743,7 @@ impl Pipeline {
|
|||||||
let view = glam::DVec3::new(view_dir.x as f64, view_dir.y as f64, view_dir.z as f64)
|
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);
|
.normalize_or(glam::DVec3::NEG_Z);
|
||||||
use crate::scene::pipeline::mesh_gpu::{SilhouetteInstance, SilhouetteVertex};
|
use crate::scene::pipeline::mesh_gpu::{SilhouetteInstance, SilhouetteVertex};
|
||||||
use wgpu::util::DeviceExt;
|
let chunk_bytes = gpu_budget::buffer_budget(device);
|
||||||
const SILHOUETTE_CHUNK_BYTES: u64 = 64 * 1024 * 1024;
|
|
||||||
let chunk_bytes = device
|
|
||||||
.limits()
|
|
||||||
.max_buffer_size
|
|
||||||
.min(SILHOUETTE_CHUNK_BYTES) as usize;
|
|
||||||
let max_vertices = chunk_bytes / std::mem::size_of::<SilhouetteVertex>() / 2 * 2;
|
let max_vertices = chunk_bytes / std::mem::size_of::<SilhouetteVertex>() / 2 * 2;
|
||||||
if max_vertices < 2 {
|
if max_vertices < 2 {
|
||||||
self.silhouette_chunks.clear();
|
self.silhouette_chunks.clear();
|
||||||
@@ -2807,12 +2818,12 @@ impl Pipeline {
|
|||||||
.chunks(max_instances.max(1))
|
.chunks(max_instances.max(1))
|
||||||
.map(|instances| {
|
.map(|instances| {
|
||||||
(
|
(
|
||||||
device.create_buffer_init(
|
gpu_upload::upload_buffer(
|
||||||
&wgpu::util::BufferInitDescriptor {
|
device,
|
||||||
label: Some("mesh.silhouette.instances"),
|
queue,
|
||||||
contents: bytemuck::cast_slice(instances),
|
"mesh.silhouette.instances",
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
instances,
|
||||||
},
|
wgpu::BufferUsages::VERTEX,
|
||||||
),
|
),
|
||||||
instances.len() as u32,
|
instances.len() as u32,
|
||||||
)
|
)
|
||||||
@@ -2914,12 +2925,12 @@ impl Pipeline {
|
|||||||
if vertex_count == 0 {
|
if vertex_count == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let vertex_buffer = device.create_buffer_init(
|
let vertex_buffer = gpu_upload::upload_buffer(
|
||||||
&wgpu::util::BufferInitDescriptor {
|
device,
|
||||||
label: Some("mesh.silhouette.vbuf"),
|
queue,
|
||||||
contents: bytemuck::cast_slice(&verts[..vertex_count]),
|
"mesh.silhouette.vbuf",
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
&verts[..vertex_count],
|
||||||
},
|
wgpu::BufferUsages::VERTEX,
|
||||||
);
|
);
|
||||||
for (instance_buffer, instance_count) in &group.instance_buffers {
|
for (instance_buffer, instance_count) in &group.instance_buffers {
|
||||||
chunks.push(SilhouetteChunk {
|
chunks.push(SilhouetteChunk {
|
||||||
@@ -2959,8 +2970,7 @@ impl Pipeline {
|
|||||||
/// so the mask pipeline stamps it straight into the stencil with `Invert`
|
/// so the mask pipeline stamps it straight into the stencil with `Invert`
|
||||||
/// (even-odd fill → interior marked, any convexity). Empty input clears the
|
/// (even-odd fill → interior marked, any convexity). Empty input clears the
|
||||||
/// boundary so the viewport renders unclipped (its render rectangle clips).
|
/// boundary so the viewport renders unclipped (its render rectangle clips).
|
||||||
pub fn upload_clip_boundary(&mut self, device: &wgpu::Device, boundary_ndc: &[[f32; 2]]) {
|
pub fn upload_clip_boundary(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, boundary_ndc: &[[f32; 2]]) {
|
||||||
use wgpu::util::DeviceExt;
|
|
||||||
if boundary_ndc.len() < 3 {
|
if boundary_ndc.len() < 3 {
|
||||||
self.clip_boundary = None;
|
self.clip_boundary = None;
|
||||||
return;
|
return;
|
||||||
@@ -2976,11 +2986,13 @@ impl Pipeline {
|
|||||||
self.clip_boundary = None;
|
self.clip_boundary = None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let vbuf = gpu_upload::upload_buffer(
|
||||||
label: Some("clip_boundary.vbuf"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&verts),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"clip_boundary.vbuf",
|
||||||
});
|
&verts,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
);
|
||||||
self.clip_boundary = Some((vbuf, (verts.len() / 2) as u32));
|
self.clip_boundary = Some((vbuf, (verts.len() / 2) as u32));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3036,6 +3048,7 @@ impl Pipeline {
|
|||||||
pub fn upload_face3d(
|
pub fn upload_face3d(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
face3d_wires: &[WireModel],
|
face3d_wires: &[WireModel],
|
||||||
all_wires: &[WireModel],
|
all_wires: &[WireModel],
|
||||||
wireframe_only: bool,
|
wireframe_only: bool,
|
||||||
@@ -3047,7 +3060,7 @@ impl Pipeline {
|
|||||||
// Edge buffer is always built from `face3d_wires`, so 3DFACE
|
// Edge buffer is always built from `face3d_wires`, so 3DFACE
|
||||||
// outlines stay on the screen regardless of mode.
|
// outlines stay on the screen regardless of mode.
|
||||||
self.gpu_face3d_edges =
|
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
|
// Fill buffer split: 3D quads + PolyfaceMesh / PolygonMesh face
|
||||||
// tris go to `chunks_3d` (gated by `keep_3d_mesh_fills`);
|
// tris go to `chunks_3d` (gated by `keep_3d_mesh_fills`);
|
||||||
// 2D fills (text-LOD greek, MultiLeader background, dimension arrows) go to
|
// 2D fills (text-LOD greek, MultiLeader background, dimension arrows) go to
|
||||||
@@ -3070,6 +3083,7 @@ impl Pipeline {
|
|||||||
} else {
|
} else {
|
||||||
self.gpu_face3d_fill = Some(Face3DGpu::from_wires(
|
self.gpu_face3d_fill = Some(Face3DGpu::from_wires(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
face3d_wires,
|
face3d_wires,
|
||||||
all_wires,
|
all_wires,
|
||||||
keep_3d_mesh_fills,
|
keep_3d_mesh_fills,
|
||||||
@@ -3366,13 +3380,18 @@ impl Pipeline {
|
|||||||
self.hatch_gpu.upload_preview(device, queue, hatches);
|
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
|
let renderable: Vec<HatchModel> = wipeouts
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|h| h.boundary.len() >= 3)
|
.filter(|h| h.boundary.len() >= 3)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.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(
|
pub fn upload_images(
|
||||||
@@ -3407,9 +3426,9 @@ impl Pipeline {
|
|||||||
atlas.clear_dirty();
|
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.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 {
|
if let Some(started) = perf_started {
|
||||||
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
|
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
|
||||||
if elapsed_ms >= 1.0 {
|
if elapsed_ms >= 1.0 {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use iced::wgpu;
|
use iced::wgpu;
|
||||||
use iced::wgpu::util::DeviceExt;
|
|
||||||
|
|
||||||
use crate::scene::text::glyph_quads::GlyphQuad;
|
use crate::scene::text::glyph_quads::GlyphQuad;
|
||||||
use crate::scene::text::sdf_atlas::GlyphAtlas;
|
use crate::scene::text::sdf_atlas::GlyphAtlas;
|
||||||
@@ -462,15 +461,17 @@ pub fn create_pipelines(
|
|||||||
|
|
||||||
pub fn upload_block_vertices(
|
pub fn upload_block_vertices(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[crate::scene::model::wire_model::WireModel],
|
wires: &[crate::scene::model::wire_model::WireModel],
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
) -> Vec<BlockTextGpu> {
|
) -> Vec<BlockTextGpu> {
|
||||||
let refs: Vec<&crate::scene::model::wire_model::WireModel> = wires.iter().collect();
|
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(
|
pub fn upload_block_vertex_refs(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[&crate::scene::model::wire_model::WireModel],
|
wires: &[&crate::scene::model::wire_model::WireModel],
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
tint: Option<[f32; 4]>,
|
tint: Option<[f32; 4]>,
|
||||||
@@ -516,7 +517,7 @@ pub fn upload_block_vertex_refs(
|
|||||||
} else {
|
} else {
|
||||||
source.text_verts.as_slice()
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
let instances: Vec<BlockTextInstance> = group
|
let instances: Vec<BlockTextInstance> = group
|
||||||
@@ -541,15 +542,16 @@ pub fn upload_block_vertex_refs(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let max_instances = ((device.limits().max_buffer_size as usize / 10) * 9
|
let max_instances =
|
||||||
/ std::mem::size_of::<BlockTextInstance>())
|
super::gpu_budget::max_elements::<BlockTextInstance>(device);
|
||||||
.max(1);
|
|
||||||
for chunk in instances.chunks(max_instances) {
|
for chunk in instances.chunks(max_instances) {
|
||||||
let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let instance_buffer = super::gpu_upload::upload_buffer(
|
||||||
label: Some("block_text.instances"),
|
device,
|
||||||
contents: bytemuck::cast_slice(chunk),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"block_text.instances",
|
||||||
});
|
chunk,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
);
|
||||||
out.push(BlockTextGpu {
|
out.push(BlockTextGpu {
|
||||||
vertex_buffer: vertex_buffer.clone(),
|
vertex_buffer: vertex_buffer.clone(),
|
||||||
instance_buffer,
|
instance_buffer,
|
||||||
@@ -562,15 +564,21 @@ pub fn upload_block_vertex_refs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Upload a finished vertex list; `None` if empty.
|
/// 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() {
|
if verts.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
Some(super::gpu_upload::upload_buffer(
|
||||||
label: Some("text.vbuf"),
|
device,
|
||||||
contents: bytemuck::cast_slice(verts),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"text.vbuf",
|
||||||
}))
|
verts,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Tests ─────────────────────────────────────────────────────────────────
|
// ── Tests ─────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1078,43 +1078,54 @@ pub struct ViewCubePipeline {
|
|||||||
|
|
||||||
impl ViewCubePipeline {
|
impl ViewCubePipeline {
|
||||||
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat) -> Self {
|
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat) -> Self {
|
||||||
use wgpu::util::DeviceExt;
|
|
||||||
let (verts, idxs) = build_geometry();
|
let (verts, idxs) = build_geometry();
|
||||||
let line_verts = surface_edge_lines(&verts, &idxs);
|
let line_verts = surface_edge_lines(&verts, &idxs);
|
||||||
let (ring_verts, ring_idxs) = build_ring_geometry();
|
let (ring_verts, ring_idxs) = build_ring_geometry();
|
||||||
let ring_line_verts = surface_edge_lines(&ring_verts, &ring_idxs);
|
let ring_line_verts = surface_edge_lines(&ring_verts, &ring_idxs);
|
||||||
let cube_px = VIEWCUBE_PX;
|
let cube_px = VIEWCUBE_PX;
|
||||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let vertex_buffer = super::gpu_upload::upload_buffer(
|
||||||
label: Some("vc.vb"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&verts),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"vc.vb",
|
||||||
});
|
&verts,
|
||||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
wgpu::BufferUsages::VERTEX,
|
||||||
label: Some("vc.ib"),
|
);
|
||||||
contents: bytemuck::cast_slice(&idxs),
|
let index_buffer = super::gpu_upload::upload_buffer(
|
||||||
usage: wgpu::BufferUsages::INDEX,
|
device,
|
||||||
});
|
queue,
|
||||||
let line_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
"vc.ib",
|
||||||
label: Some("vc.line_vb"),
|
&idxs,
|
||||||
contents: bytemuck::cast_slice(&line_verts),
|
wgpu::BufferUsages::INDEX,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
);
|
||||||
});
|
let line_vertex_buffer = super::gpu_upload::upload_buffer(
|
||||||
let ring_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
device,
|
||||||
label: Some("vc.ring_vb"),
|
queue,
|
||||||
contents: bytemuck::cast_slice(&ring_verts),
|
"vc.line_vb",
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
&line_verts,
|
||||||
});
|
wgpu::BufferUsages::VERTEX,
|
||||||
let ring_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
);
|
||||||
label: Some("vc.ring_ib"),
|
let ring_vertex_buffer = super::gpu_upload::upload_buffer(
|
||||||
contents: bytemuck::cast_slice(&ring_idxs),
|
device,
|
||||||
usage: wgpu::BufferUsages::INDEX,
|
queue,
|
||||||
});
|
"vc.ring_vb",
|
||||||
|
&ring_verts,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
);
|
||||||
|
let ring_index_buffer = super::gpu_upload::upload_buffer(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
"vc.ring_ib",
|
||||||
|
&ring_idxs,
|
||||||
|
wgpu::BufferUsages::INDEX,
|
||||||
|
);
|
||||||
let ring_line_vertex_buffer =
|
let ring_line_vertex_buffer =
|
||||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
super::gpu_upload::upload_buffer(
|
||||||
label: Some("vc.ring_line_vb"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&ring_line_verts),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"vc.ring_line_vb",
|
||||||
});
|
&ring_line_verts,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
);
|
||||||
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
label: Some("vc.ub"),
|
label: Some("vc.ub"),
|
||||||
size: std::mem::size_of::<CubeUniforms>() as u64,
|
size: std::mem::size_of::<CubeUniforms>() as u64,
|
||||||
@@ -1291,11 +1302,13 @@ impl ViewCubePipeline {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
let composite_uniform_buffer =
|
let composite_uniform_buffer =
|
||||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
super::gpu_upload::upload_buffer(
|
||||||
label: Some("vc.composite_uniform"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&[1.0f32, 1.0, 0.0, 0.0]),
|
queue,
|
||||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
"vc.composite_uniform",
|
||||||
});
|
&[1.0f32, 1.0, 0.0, 0.0],
|
||||||
|
wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||||
|
);
|
||||||
let composite_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
let composite_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
label: Some("vc.composite_sampler"),
|
label: Some("vc.composite_sampler"),
|
||||||
mag_filter: wgpu::FilterMode::Nearest,
|
mag_filter: wgpu::FilterMode::Nearest,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ use crate::scene::model::hatch_model::{
|
|||||||
FillPlane, HatchModel, HatchPattern, PatFamily, MAX_HATCH_BOUNDARY_VERTS,
|
FillPlane, HatchModel, HatchPattern, PatFamily, MAX_HATCH_BOUNDARY_VERTS,
|
||||||
};
|
};
|
||||||
use iced::wgpu;
|
use iced::wgpu;
|
||||||
use iced::wgpu::util::DeviceExt;
|
|
||||||
|
|
||||||
// ── Constants ──────────────────────────────────────────────────────────────
|
// ── Constants ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -157,6 +156,7 @@ pub struct WipeoutGpu {
|
|||||||
impl WipeoutGpu {
|
impl WipeoutGpu {
|
||||||
pub fn from_models(
|
pub fn from_models(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
models: &[HatchModel],
|
models: &[HatchModel],
|
||||||
bgl1: &wgpu::BindGroupLayout,
|
bgl1: &wgpu::BindGroupLayout,
|
||||||
) -> Vec<Self> {
|
) -> Vec<Self> {
|
||||||
@@ -176,11 +176,16 @@ impl WipeoutGpu {
|
|||||||
}
|
}
|
||||||
groups
|
groups
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|group| Self::new(device, &group, bgl1))
|
.map(|group| Self::new(device, queue, &group, bgl1))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(device: &wgpu::Device, models: &[&HatchModel], bgl1: &wgpu::BindGroupLayout) -> Self {
|
fn new(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
models: &[&HatchModel],
|
||||||
|
bgl1: &wgpu::BindGroupLayout,
|
||||||
|
) -> Self {
|
||||||
let model = models[0];
|
let model = models[0];
|
||||||
// ── Decode pattern mode ──────────────────────────────────────────
|
// ── Decode pattern mode ──────────────────────────────────────────
|
||||||
let (mode, color2, grad_cos, grad_sin) = match &model.pattern {
|
let (mode, color2, grad_cos, grad_sin) = match &model.pattern {
|
||||||
@@ -264,11 +269,13 @@ impl WipeoutGpu {
|
|||||||
_pad: 0.0,
|
_pad: 0.0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let vertex_buffer = super::gpu_upload::upload_buffer(
|
||||||
label: Some("hatch.vbuf"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&quad),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"hatch.vbuf",
|
||||||
});
|
&quad,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
);
|
||||||
|
|
||||||
// ── Gradient: projection range (in snapped-local space) ──────────
|
// ── Gradient: projection range (in snapped-local space) ──────────
|
||||||
let (grad_min, grad_range) = if mode == 2 {
|
let (grad_min, grad_range) = if mode == 2 {
|
||||||
@@ -354,21 +361,27 @@ impl WipeoutGpu {
|
|||||||
let family_batch = build_family_batch(&model.pattern);
|
let family_batch = build_family_batch(&model.pattern);
|
||||||
|
|
||||||
// ── GPU buffers ───────────────────────────────────────────────────
|
// ── GPU buffers ───────────────────────────────────────────────────
|
||||||
let _uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let _uniform_buf = super::gpu_upload::upload_buffer(
|
||||||
label: Some("hatch.uniforms"),
|
device,
|
||||||
contents: bytemuck::bytes_of(&uniform_data),
|
queue,
|
||||||
usage: wgpu::BufferUsages::UNIFORM,
|
"hatch.uniforms",
|
||||||
});
|
std::slice::from_ref(&uniform_data),
|
||||||
let _boundary_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
wgpu::BufferUsages::UNIFORM,
|
||||||
label: Some("hatch.boundary"),
|
);
|
||||||
contents: bytemuck::bytes_of(&boundary_data),
|
let _boundary_buf = super::gpu_upload::upload_buffer(
|
||||||
usage: wgpu::BufferUsages::UNIFORM,
|
device,
|
||||||
});
|
queue,
|
||||||
let _family_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
"hatch.boundary",
|
||||||
label: Some("hatch.families"),
|
std::slice::from_ref(&boundary_data),
|
||||||
contents: bytemuck::bytes_of(&family_batch),
|
wgpu::BufferUsages::UNIFORM,
|
||||||
usage: wgpu::BufferUsages::UNIFORM,
|
);
|
||||||
});
|
let _family_buf = super::gpu_upload::upload_buffer(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
"hatch.families",
|
||||||
|
std::slice::from_ref(&family_batch),
|
||||||
|
wgpu::BufferUsages::UNIFORM,
|
||||||
|
);
|
||||||
|
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some("hatch.bind_group1"),
|
label: Some("hatch.bind_group1"),
|
||||||
@@ -419,11 +432,13 @@ impl WipeoutGpu {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let instance_buffer = super::gpu_upload::upload_buffer(
|
||||||
label: Some("wipeout.instances"),
|
device,
|
||||||
contents: bytemuck::cast_slice(&placements),
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
"wipeout.instances",
|
||||||
});
|
&placements,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
);
|
||||||
let mut world_bounds = [
|
let mut world_bounds = [
|
||||||
f32::INFINITY,
|
f32::INFINITY,
|
||||||
f32::INFINITY,
|
f32::INFINITY,
|
||||||
|
|||||||
@@ -255,48 +255,34 @@ fn make_const_bg(
|
|||||||
|
|
||||||
fn alloc_inst_initialized(
|
fn alloc_inst_initialized(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
cap: u64,
|
cap: u64,
|
||||||
data: &[WireInstance],
|
data: &[WireInstance],
|
||||||
) -> wgpu::Buffer {
|
) -> wgpu::Buffer {
|
||||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
super::gpu_upload::alloc_with_prefix(
|
||||||
label: Some("wire_arena.ibuf"),
|
device,
|
||||||
size: cap * std::mem::size_of::<WireInstance>() as u64,
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
"wire_arena.ibuf",
|
||||||
mapped_at_creation: true,
|
cap * std::mem::size_of::<WireInstance>() as u64,
|
||||||
});
|
data,
|
||||||
if !data.is_empty() {
|
wgpu::BufferUsages::VERTEX,
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_const_initialized(
|
fn alloc_const_initialized(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
cap: u64,
|
cap: u64,
|
||||||
data: &[WireConst],
|
data: &[WireConst],
|
||||||
) -> wgpu::Buffer {
|
) -> wgpu::Buffer {
|
||||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
super::gpu_upload::alloc_with_prefix(
|
||||||
label: Some("wire_arena.cbuf"),
|
device,
|
||||||
size: cap * std::mem::size_of::<WireConst>() as u64,
|
queue,
|
||||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
"wire_arena.cbuf",
|
||||||
mapped_at_creation: true,
|
cap * std::mem::size_of::<WireConst>() as u64,
|
||||||
});
|
data,
|
||||||
if !data.is_empty() {
|
wgpu::BufferUsages::STORAGE,
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn blank_const() -> WireConst {
|
fn blank_const() -> WireConst {
|
||||||
@@ -328,7 +314,7 @@ impl WireArena {
|
|||||||
/// path).
|
/// path).
|
||||||
pub fn build(
|
pub fn build(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
_queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
wires: &[&WireModel],
|
wires: &[&WireModel],
|
||||||
depth_map: &FxHashMap<u64, [f32; 2]>,
|
depth_map: &FxHashMap<u64, [f32; 2]>,
|
||||||
const_bgl: &wgpu::BindGroupLayout,
|
const_bgl: &wgpu::BindGroupLayout,
|
||||||
@@ -468,8 +454,8 @@ impl WireArena {
|
|||||||
.max(MIN_CONST_CAP)
|
.max(MIN_CONST_CAP)
|
||||||
.min(MAX_CONSTS)) as u32;
|
.min(MAX_CONSTS)) as u32;
|
||||||
let upload_started = iced::time::Instant::now();
|
let upload_started = iced::time::Instant::now();
|
||||||
let inst_buf = alloc_inst_initialized(device, inst_cap as u64, &instances);
|
let inst_buf = alloc_inst_initialized(device, queue, inst_cap as u64, &instances);
|
||||||
let const_buf = alloc_const_initialized(device, const_cap as u64, &consts_cpu);
|
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 upload_ms = upload_started.elapsed().as_secs_f64() * 1000.0;
|
||||||
let const_bind_group = make_const_bg(device, const_bgl, &const_buf);
|
let const_bind_group = make_const_bg(device, const_bgl, &const_buf);
|
||||||
if perf {
|
if perf {
|
||||||
@@ -934,25 +920,18 @@ fn can_resize_packed_terminal_slab(
|
|||||||
|
|
||||||
fn alloc_packed_initialized(
|
fn alloc_packed_initialized(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
cap: u64,
|
cap: u64,
|
||||||
data: &[PackedWireInstance],
|
data: &[PackedWireInstance],
|
||||||
) -> wgpu::Buffer {
|
) -> wgpu::Buffer {
|
||||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
super::gpu_upload::alloc_with_prefix(
|
||||||
label: Some("wire_arena.packed.ibuf"),
|
device,
|
||||||
size: cap * std::mem::size_of::<PackedWireInstance>() as u64,
|
queue,
|
||||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
"wire_arena.packed.ibuf",
|
||||||
mapped_at_creation: true,
|
cap * std::mem::size_of::<PackedWireInstance>() as u64,
|
||||||
});
|
data,
|
||||||
if !data.is_empty() {
|
wgpu::BufferUsages::VERTEX,
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visible_ranges(
|
fn visible_ranges(
|
||||||
@@ -998,6 +977,7 @@ fn visible_ranges(
|
|||||||
impl PackedWireArena {
|
impl PackedWireArena {
|
||||||
fn build(
|
fn build(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[&WireModel],
|
wires: &[&WireModel],
|
||||||
depth_map: &FxHashMap<u64, [f32; 2]>,
|
depth_map: &FxHashMap<u64, [f32; 2]>,
|
||||||
mesh_edge: bool,
|
mesh_edge: bool,
|
||||||
@@ -1078,7 +1058,7 @@ impl PackedWireArena {
|
|||||||
let inst_cap = ((inst_tail as u64 * HEADROOM_NUM / HEADROOM_DEN)
|
let inst_cap = ((inst_tail as u64 * HEADROOM_NUM / HEADROOM_DEN)
|
||||||
.max(MIN_INST_CAP)
|
.max(MIN_INST_CAP)
|
||||||
.min(MAX_PACKED_INSTANCES)) as u32;
|
.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 {
|
Some(Self {
|
||||||
inst_buf,
|
inst_buf,
|
||||||
@@ -1351,7 +1331,7 @@ impl PersistentWireArena {
|
|||||||
)?)
|
)?)
|
||||||
} else {
|
} else {
|
||||||
PersistentWireArenaKind::Packed(PackedWireArena::build(
|
PersistentWireArenaKind::Packed(PackedWireArena::build(
|
||||||
device, wires, depth_map, mesh_edge,
|
device, queue, wires, depth_map, mesh_edge,
|
||||||
)?)
|
)?)
|
||||||
};
|
};
|
||||||
Some(Self { inner })
|
Some(Self { inner })
|
||||||
|
|||||||
@@ -28,34 +28,20 @@
|
|||||||
use crate::scene::model::wire_model::WireModel;
|
use crate::scene::model::wire_model::WireModel;
|
||||||
use iced::wgpu;
|
use iced::wgpu;
|
||||||
|
|
||||||
/// Allocate a VERTEX buffer with `mapped_at_creation` and write `data` directly
|
/// Allocate a VERTEX buffer and fill it through the queue.
|
||||||
/// into the mapped slice. Skips the intermediate staging copy that
|
///
|
||||||
/// `create_buffer_init` performs and avoids holding a second `Vec` worth of
|
/// This used to map at creation to skip the staging copy. Mapping panics on
|
||||||
/// memory during upload — meaningful on cold open where wire buffers can run
|
/// the invalid buffer an out-of-memory device returns, and that panic aborts
|
||||||
/// into the hundreds of MB.
|
/// the process from inside iced's redraw, so the staging copy is the price of
|
||||||
fn instance_buffer_mapped<T: bytemuck::Pod>(
|
/// 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,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
label: &str,
|
label: &str,
|
||||||
data: &[T],
|
data: &[T],
|
||||||
) -> wgpu::Buffer {
|
) -> wgpu::Buffer {
|
||||||
let bytes: &[u8] = bytemuck::cast_slice(data);
|
super::gpu_upload::upload_buffer(device, queue, label, data, wgpu::BufferUsages::VERTEX)
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Instance layout ───────────────────────────────────────────────────────
|
// ── Instance layout ───────────────────────────────────────────────────────
|
||||||
@@ -684,12 +670,12 @@ pub(crate) fn wire_draw_depth(
|
|||||||
impl BlockWireGpu {
|
impl BlockWireGpu {
|
||||||
pub fn from_wires(
|
pub fn from_wires(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[&WireModel],
|
wires: &[&WireModel],
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
color_override: Option<[f32; 4]>,
|
color_override: Option<[f32; 4]>,
|
||||||
const_bgl: &wgpu::BindGroupLayout,
|
const_bgl: &wgpu::BindGroupLayout,
|
||||||
) -> Vec<Self> {
|
) -> Vec<Self> {
|
||||||
use wgpu::util::DeviceExt;
|
|
||||||
|
|
||||||
let mut slots: rustc_hash::FxHashMap<(u64, bool), usize> =
|
let mut slots: rustc_hash::FxHashMap<(u64, bool), usize> =
|
||||||
rustc_hash::FxHashMap::default();
|
rustc_hash::FxHashMap::default();
|
||||||
@@ -740,16 +726,42 @@ impl BlockWireGpu {
|
|||||||
};
|
};
|
||||||
vertices.extend_from_slice(&[vertex; 6]);
|
vertices.extend_from_slice(&[vertex; 6]);
|
||||||
}
|
}
|
||||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
// Split vertex data into buffers small enough to allocate on
|
||||||
label: Some("block_wire.vertices"),
|
// low-VRAM devices. Same scheme as the solid-mesh chunking in
|
||||||
contents: bytemuck::cast_slice(&vertices),
|
// `mesh_gpu.rs` (#203) and the fill chunking in `face3d_gpu.rs`
|
||||||
usage: wgpu::BufferUsages::VERTEX,
|
// (#358); the block-wire path was never converted, so a
|
||||||
});
|
// block-heavy drawing produced one multi-hundred-MB buffer.
|
||||||
let const_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
// Round down to a multiple of 6: each segment expands to six
|
||||||
label: Some("block_wire.const"),
|
// vertices and must not straddle two chunks.
|
||||||
contents: bytemuck::bytes_of(&constant),
|
let max_verts =
|
||||||
usage: wgpu::BufferUsages::UNIFORM,
|
super::gpu_budget::max_elements_grouped::<BlockWireVertex>(device, 6);
|
||||||
});
|
let vertex_chunks: Vec<(wgpu::Buffer, u32)> = vertices
|
||||||
|
.chunks(max_verts)
|
||||||
|
.map(|chunk| {
|
||||||
|
(
|
||||||
|
super::gpu_upload::upload_buffer(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
"block_wire.vertices",
|
||||||
|
chunk,
|
||||||
|
wgpu::BufferUsages::VERTEX,
|
||||||
|
),
|
||||||
|
chunk.len() as u32,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.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.
|
||||||
|
let const_buffer = super::gpu_upload::upload_buffer(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
"block_wire.const",
|
||||||
|
std::slice::from_ref(&constant),
|
||||||
|
wgpu::BufferUsages::UNIFORM,
|
||||||
|
);
|
||||||
let const_bind_group = std::sync::Arc::new(device.create_bind_group(
|
let const_bind_group = std::sync::Arc::new(device.create_bind_group(
|
||||||
&wgpu::BindGroupDescriptor {
|
&wgpu::BindGroupDescriptor {
|
||||||
label: Some("block_wire.const.bg"),
|
label: Some("block_wire.const.bg"),
|
||||||
@@ -788,22 +800,29 @@ impl BlockWireGpu {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let max_instances = ((device.limits().max_buffer_size as usize / 10) * 9
|
let max_instances = super::gpu_budget::max_elements::<BlockWireInstance>(device);
|
||||||
/ std::mem::size_of::<BlockWireInstance>())
|
// Build each instance buffer once and reuse it across vertex
|
||||||
.max(1);
|
// chunks, rather than re-uploading it per chunk.
|
||||||
for chunk in instances.chunks(max_instances) {
|
let instance_chunks: Vec<(wgpu::Buffer, u32)> = instances
|
||||||
out.push(Self {
|
.chunks(max_instances)
|
||||||
vertex_buffer: vertex_buffer.clone(),
|
.map(|chunk| {
|
||||||
instance_buffer: instance_buffer_mapped(
|
(
|
||||||
device,
|
instance_buffer(device, queue, "block_wire.instances", chunk),
|
||||||
"block_wire.instances",
|
chunk.len() as u32,
|
||||||
chunk,
|
)
|
||||||
),
|
})
|
||||||
vertex_count: vertices.len() as u32,
|
.collect();
|
||||||
instance_count: chunk.len() as u32,
|
for (vertex_buffer, vertex_count) in &vertex_chunks {
|
||||||
is_3d_mesh_edge: mesh_edge,
|
for (instance_buffer, instance_count) in &instance_chunks {
|
||||||
const_bind_group: const_bind_group.clone(),
|
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(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
@@ -816,18 +835,20 @@ impl BlockWireGpu {
|
|||||||
/// each chunk.
|
/// each chunk.
|
||||||
fn build_const_bind_group(
|
fn build_const_bind_group(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
bgl: &wgpu::BindGroupLayout,
|
bgl: &wgpu::BindGroupLayout,
|
||||||
consts: &[WireConst],
|
consts: &[WireConst],
|
||||||
) -> std::sync::Arc<wgpu::BindGroup> {
|
) -> std::sync::Arc<wgpu::BindGroup> {
|
||||||
use wgpu::util::DeviceExt;
|
|
||||||
// wgpu rejects zero-sized buffers; pad with one zeroed record when empty.
|
// wgpu rejects zero-sized buffers; pad with one zeroed record when empty.
|
||||||
let one = [<WireConst as bytemuck::Zeroable>::zeroed()];
|
let one = [<WireConst as bytemuck::Zeroable>::zeroed()];
|
||||||
let data: &[WireConst] = if consts.is_empty() { &one } else { consts };
|
let data: &[WireConst] = if consts.is_empty() { &one } else { consts };
|
||||||
let buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let buf = super::gpu_upload::upload_buffer(
|
||||||
label: Some("wire_const.buf"),
|
device,
|
||||||
contents: bytemuck::cast_slice(data),
|
queue,
|
||||||
usage: wgpu::BufferUsages::STORAGE,
|
"wire_const.buf",
|
||||||
});
|
data,
|
||||||
|
wgpu::BufferUsages::STORAGE,
|
||||||
|
);
|
||||||
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some("wire_const.bg"),
|
label: Some("wire_const.bg"),
|
||||||
layout: bgl,
|
layout: bgl,
|
||||||
@@ -845,6 +866,7 @@ impl WireGpu {
|
|||||||
/// of a large selected polyline or block before packing the overlay.
|
/// of a large selected polyline or block before packing the overlay.
|
||||||
pub fn from_highlight_refs(
|
pub fn from_highlight_refs(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[&WireModel],
|
wires: &[&WireModel],
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
@@ -871,12 +893,13 @@ impl WireGpu {
|
|||||||
if instances.is_empty() {
|
if instances.is_empty() {
|
||||||
return Vec::new();
|
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
|
return instances
|
||||||
.chunks(MAX_INSTANCES)
|
.chunks(MAX_INSTANCES)
|
||||||
.map(|chunk| Self {
|
.map(|chunk| Self {
|
||||||
instance_buffer: instance_buffer_mapped(
|
instance_buffer: instance_buffer(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
"wire.highlight.ibuf",
|
"wire.highlight.ibuf",
|
||||||
chunk,
|
chunk,
|
||||||
),
|
),
|
||||||
@@ -904,8 +927,9 @@ impl WireGpu {
|
|||||||
instances
|
instances
|
||||||
.chunks(MAX_PACKED_INSTANCES)
|
.chunks(MAX_PACKED_INSTANCES)
|
||||||
.map(|chunk| Self {
|
.map(|chunk| Self {
|
||||||
instance_buffer: instance_buffer_mapped(
|
instance_buffer: instance_buffer(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
"wire.highlight.compat.ibuf",
|
"wire.highlight.compat.ibuf",
|
||||||
chunk,
|
chunk,
|
||||||
),
|
),
|
||||||
@@ -923,6 +947,7 @@ impl WireGpu {
|
|||||||
/// uses chunked resident buffers.
|
/// uses chunked resident buffers.
|
||||||
pub fn from_run_refs(
|
pub fn from_run_refs(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[&WireModel],
|
wires: &[&WireModel],
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
mesh_edge: bool,
|
mesh_edge: bool,
|
||||||
@@ -951,8 +976,9 @@ impl WireGpu {
|
|||||||
return instances
|
return instances
|
||||||
.chunks(MAX_INSTANCES)
|
.chunks(MAX_INSTANCES)
|
||||||
.map(|chunk| Self {
|
.map(|chunk| Self {
|
||||||
instance_buffer: instance_buffer_mapped(
|
instance_buffer: instance_buffer(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
"wire.run.hybrid.compat.ibuf",
|
"wire.run.hybrid.compat.ibuf",
|
||||||
chunk,
|
chunk,
|
||||||
),
|
),
|
||||||
@@ -988,11 +1014,11 @@ impl WireGpu {
|
|||||||
if instances.is_empty() {
|
if instances.is_empty() {
|
||||||
return Vec::new();
|
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
|
instances
|
||||||
.chunks(MAX_INSTANCES)
|
.chunks(MAX_INSTANCES)
|
||||||
.map(|chunk| Self {
|
.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,
|
first_instance: 0,
|
||||||
instance_count: chunk.len() as u32,
|
instance_count: chunk.len() as u32,
|
||||||
is_3d_mesh_edge: mesh_edge,
|
is_3d_mesh_edge: mesh_edge,
|
||||||
@@ -1013,6 +1039,7 @@ impl WireGpu {
|
|||||||
/// image for transparent / coincident wires.
|
/// image for transparent / coincident wires.
|
||||||
pub fn from_run(
|
pub fn from_run(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[WireModel],
|
wires: &[WireModel],
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
mesh_edge: bool,
|
mesh_edge: bool,
|
||||||
@@ -1047,11 +1074,11 @@ impl WireGpu {
|
|||||||
if instances.is_empty() {
|
if instances.is_empty() {
|
||||||
return vec![];
|
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
|
return instances
|
||||||
.chunks(MAX_INSTANCES)
|
.chunks(MAX_INSTANCES)
|
||||||
.map(|chunk| {
|
.map(|chunk| {
|
||||||
let buf = instance_buffer_mapped(device, "wire.run.ibuf", chunk);
|
let buf = instance_buffer(device, queue, "wire.run.ibuf", chunk);
|
||||||
Self {
|
Self {
|
||||||
instance_buffer: buf,
|
instance_buffer: buf,
|
||||||
first_instance: 0,
|
first_instance: 0,
|
||||||
@@ -1084,7 +1111,7 @@ impl WireGpu {
|
|||||||
instances
|
instances
|
||||||
.chunks(MAX_PACKED_INSTANCES)
|
.chunks(MAX_PACKED_INSTANCES)
|
||||||
.map(|chunk| {
|
.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 {
|
Self {
|
||||||
instance_buffer: buf,
|
instance_buffer: buf,
|
||||||
first_instance: 0,
|
first_instance: 0,
|
||||||
@@ -1101,6 +1128,7 @@ impl WireGpu {
|
|||||||
/// per-instance.
|
/// per-instance.
|
||||||
pub fn from_batch(
|
pub fn from_batch(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
wires: &[WireModel],
|
wires: &[WireModel],
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
const_bgl: Option<&wgpu::BindGroupLayout>,
|
const_bgl: Option<&wgpu::BindGroupLayout>,
|
||||||
@@ -1134,13 +1162,13 @@ impl WireGpu {
|
|||||||
if instances.is_empty() {
|
if instances.is_empty() {
|
||||||
return vec![];
|
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
|
return instances
|
||||||
.chunks(MAX_INSTANCES)
|
.chunks(MAX_INSTANCES)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, chunk)| {
|
.map(|(i, chunk)| {
|
||||||
let label = format!("wire.batch.ibuf.{i}");
|
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 {
|
Self {
|
||||||
instance_buffer,
|
instance_buffer,
|
||||||
first_instance: 0,
|
first_instance: 0,
|
||||||
@@ -1167,7 +1195,7 @@ impl WireGpu {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, chunk)| {
|
.map(|(i, chunk)| {
|
||||||
let label = format!("wire.batch.compat.ibuf.{i}");
|
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 {
|
Self {
|
||||||
instance_buffer,
|
instance_buffer,
|
||||||
first_instance: 0,
|
first_instance: 0,
|
||||||
|
|||||||
+39
-14
@@ -546,6 +546,7 @@ impl shader::Primitive for Primitive {
|
|||||||
if wipeout_changed || fill_changed {
|
if wipeout_changed || fill_changed {
|
||||||
inner.upload_wipeouts(
|
inner.upload_wipeouts(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
if fill_mode {
|
if fill_mode {
|
||||||
&vp.wipeout_hatches[..]
|
&vp.wipeout_hatches[..]
|
||||||
} else {
|
} else {
|
||||||
@@ -608,6 +609,7 @@ impl shader::Primitive for Primitive {
|
|||||||
{
|
{
|
||||||
inner.upload_face3d(
|
inner.upload_face3d(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&vp.face3d_wires[..],
|
&vp.face3d_wires[..],
|
||||||
&vp_wires[..],
|
&vp_wires[..],
|
||||||
!face3d_fill_active,
|
!face3d_fill_active,
|
||||||
@@ -785,6 +787,7 @@ impl shader::Primitive for Primitive {
|
|||||||
inner.wire_arena_fallback = std::sync::Arc::new(
|
inner.wire_arena_fallback = std::sync::Arc::new(
|
||||||
crate::scene::pipeline::WireGpu::from_run_refs(
|
crate::scene::pipeline::WireGpu::from_run_refs(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
®ular,
|
®ular,
|
||||||
&draw_depths,
|
&draw_depths,
|
||||||
false,
|
false,
|
||||||
@@ -821,6 +824,7 @@ impl shader::Primitive for Primitive {
|
|||||||
inner.wire_arena_fallback = std::sync::Arc::new(
|
inner.wire_arena_fallback = std::sync::Arc::new(
|
||||||
crate::scene::pipeline::WireGpu::from_run_refs(
|
crate::scene::pipeline::WireGpu::from_run_refs(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&mesh,
|
&mesh,
|
||||||
&draw_depths,
|
&draw_depths,
|
||||||
true,
|
true,
|
||||||
@@ -917,22 +921,40 @@ impl shader::Primitive for Primitive {
|
|||||||
let built = match cached {
|
let built = match cached {
|
||||||
Some(entry) => entry,
|
Some(entry) => entry,
|
||||||
None => {
|
None => {
|
||||||
|
// Free the superseded content BEFORE allocating its
|
||||||
|
// replacement. Every geometry edit mints a new content
|
||||||
|
// id, so a miss here means this slot's current buffers
|
||||||
|
// are already dead weight — and on a large drawing one
|
||||||
|
// set is most of the card (measured: 1.3 GB of a 2 GB
|
||||||
|
// GT 1030). Holding the old set while the new one is
|
||||||
|
// allocated is what exhausts VRAM after the first edit.
|
||||||
|
//
|
||||||
|
// Dropping this slot's handles first is what makes the
|
||||||
|
// retain below able to see the old entry as garbage; a
|
||||||
|
// pane still drawing that content keeps its own clone,
|
||||||
|
// so shared geometry is never pulled out from under it.
|
||||||
|
inner.gpu_wires = std::sync::Arc::new(Vec::new());
|
||||||
|
inner.gpu_block_wires = std::sync::Arc::new(Vec::new());
|
||||||
|
// Entries no slot still holds: only the cache references
|
||||||
|
// them. An entry drawn by any pane keeps a strong count
|
||||||
|
// ≥ 2, so this never drops live geometry.
|
||||||
|
let held_before = pipeline.wire_buffer_cache.len();
|
||||||
|
pipeline.wire_buffer_cache.retain(|_, (w, b, _)| {
|
||||||
|
std::sync::Arc::strong_count(w) > 1
|
||||||
|
|| std::sync::Arc::strong_count(b) > 1
|
||||||
|
});
|
||||||
|
if _perf {
|
||||||
|
crate::perf_record!(
|
||||||
|
"[perf] wire-cache evicted={} held={}",
|
||||||
|
held_before - pipeline.wire_buffer_cache.len(),
|
||||||
|
pipeline.wire_buffer_cache.len(),
|
||||||
|
);
|
||||||
|
}
|
||||||
let entry =
|
let entry =
|
||||||
inner.build_wire_buffers(device, &vp_wires[..], &draw_depths);
|
inner.build_wire_buffers(device, queue, &vp_wires[..], &draw_depths);
|
||||||
pipeline
|
pipeline
|
||||||
.wire_buffer_cache
|
.wire_buffer_cache
|
||||||
.insert(vp.wire_content_id, entry.clone());
|
.insert(vp.wire_content_id, entry.clone());
|
||||||
// Evict entries no slot still holds (only the cache
|
|
||||||
// references them). An entry drawn by any pane keeps a
|
|
||||||
// strong count ≥ 2, so this never drops live geometry.
|
|
||||||
if pipeline.wire_buffer_cache.len() > 16 {
|
|
||||||
pipeline
|
|
||||||
.wire_buffer_cache
|
|
||||||
.retain(|_, (w, b, _)| {
|
|
||||||
std::sync::Arc::strong_count(w) > 1
|
|
||||||
|| std::sync::Arc::strong_count(b) > 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
entry
|
entry
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1000,6 +1022,7 @@ impl shader::Primitive for Primitive {
|
|||||||
};
|
};
|
||||||
inner.upload_selected_wires(
|
inner.upload_selected_wires(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&vp_wires[..],
|
&vp_wires[..],
|
||||||
&vp.selected_handles,
|
&vp.selected_handles,
|
||||||
&vp.hover_handles,
|
&vp.hover_handles,
|
||||||
@@ -1012,6 +1035,7 @@ impl shader::Primitive for Primitive {
|
|||||||
// base text buffer.
|
// base text buffer.
|
||||||
inner.upload_text_highlight(
|
inner.upload_text_highlight(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
&vp_wires[..],
|
&vp_wires[..],
|
||||||
&vp.selected_handles,
|
&vp.selected_handles,
|
||||||
&vp.hover_handles,
|
&vp.hover_handles,
|
||||||
@@ -1066,7 +1090,7 @@ impl shader::Primitive for Primitive {
|
|||||||
// Live overlay (command preview / interim / grip drag) — small and
|
// Live overlay (command preview / interim / grip drag) — small and
|
||||||
// refreshed every frame it's present, so a drag never re-uploads
|
// refreshed every frame it's present, so a drag never re-uploads
|
||||||
// the resident base wire buffer.
|
// 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[..]);
|
inner.upload_preview_text(device, queue, &vp.preview_text_verts[..]);
|
||||||
// Cull / scissor / LOD project AABBs relative-to-eye (matching the
|
// Cull / scissor / LOD project AABBs relative-to-eye (matching the
|
||||||
// GPU's RTE path) so the math stays precise at UTM-scale coords.
|
// GPU's RTE path) so the math stays precise at UTM-scale coords.
|
||||||
@@ -1087,13 +1111,14 @@ impl shader::Primitive for Primitive {
|
|||||||
if inner.silhouette_key != silhouette_key {
|
if inner.silhouette_key != silhouette_key {
|
||||||
inner.upload_silhouettes(
|
inner.upload_silhouettes(
|
||||||
device,
|
device,
|
||||||
|
queue,
|
||||||
if silhouette_enabled { &vp.meshes[..] } else { &[] },
|
if silhouette_enabled { &vp.meshes[..] } else { &[] },
|
||||||
vp.wire_content_id,
|
vp.wire_content_id,
|
||||||
vp.view_dir,
|
vp.view_dir,
|
||||||
);
|
);
|
||||||
inner.silhouette_key = silhouette_key;
|
inner.silhouette_key = silhouette_key;
|
||||||
}
|
}
|
||||||
inner.upload_clip_boundary(device, &vp.clip_boundary_ndc);
|
inner.upload_clip_boundary(device, queue, &vp.clip_boundary_ndc);
|
||||||
let hatch_lod_key = (
|
let hatch_lod_key = (
|
||||||
Arc::as_ptr(&vp.hatches) as usize,
|
Arc::as_ptr(&vp.hatches) as usize,
|
||||||
vp.camera_generation,
|
vp.camera_generation,
|
||||||
|
|||||||
Reference in new issue
Block a user