forked from 1b-agent/opencadstudio-upstream
perf(render): keep solid meshes resident
Keep all solid geometry GPU-resident during navigation. Use vertex-rate instancing, compact untextured vertices, vertex-cache ordering, and cached silhouette sources without culling geometry.
This commit is contained in:
1 parent
15ab027d70
commit
5e8221f8f1
9 files changed
+756
-1012
No files matched your search
Generated
+38
-25
@@ -31,6 +31,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"lyon_tessellation",
|
"lyon_tessellation",
|
||||||
|
"meshopt",
|
||||||
"naga 27.0.3",
|
"naga 27.0.3",
|
||||||
"ocs_plugin_api",
|
"ocs_plugin_api",
|
||||||
"open",
|
"open",
|
||||||
@@ -152,7 +153,7 @@ version = "1.1.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1071,7 +1072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1694,6 +1695,15 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "float_next_after"
|
name = "float_next_after"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -1741,7 +1751,7 @@ version = "0.12.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198"
|
checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1927,7 +1937,7 @@ dependencies = [
|
|||||||
"futures-macro",
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
@@ -3055,15 +3065,6 @@ dependencies = [
|
|||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.3"
|
version = "2.8.3"
|
||||||
@@ -3088,6 +3089,18 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "meshopt"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e01e77ead21976b3a9f01ec1724f766923da74f0726364e2b0f425658935d71a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
"cc",
|
||||||
|
"float-cmp 0.10.0",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
@@ -3254,7 +3267,7 @@ version = "0.9.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003"
|
checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3263,7 +3276,7 @@ version = "7.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3273,7 +3286,7 @@ version = "8.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3283,7 +3296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d"
|
checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytecount",
|
"bytecount",
|
||||||
"memchr 1.0.2",
|
"memchr",
|
||||||
"nom 8.0.0",
|
"nom 8.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4225,7 +4238,7 @@ checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
"getopts",
|
"getopts",
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
"pulldown-cmark-escape",
|
"pulldown-cmark-escape",
|
||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
@@ -4286,7 +4299,7 @@ version = "0.39.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
|
checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4532,7 +4545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
@@ -4544,7 +4557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
|
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4963,7 +4976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"zmij",
|
"zmij",
|
||||||
@@ -5260,7 +5273,7 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"float-cmp",
|
"float-cmp 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5629,7 +5642,7 @@ version = "3.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
|
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
"nom 8.0.0",
|
"nom 8.0.0",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
]
|
]
|
||||||
@@ -6699,7 +6712,7 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.8.3",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0
|
|||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
ocs_plugin_api = { path = "crates/ocs_plugin_api", features = ["host"] }
|
ocs_plugin_api = { path = "crates/ocs_plugin_api", features = ["host"] }
|
||||||
|
meshopt = "0.6.2"
|
||||||
i18n-embed = { version = "0.16", features = ["desktop-requester"] }
|
i18n-embed = { version = "0.16", features = ["desktop-requester"] }
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
open = "5"
|
open = "5"
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ impl DeviceCapabilities {
|
|||||||
/// instances, boundaries, families, dashes, and visibility.
|
/// instances, boundaries, families, dashes, and visibility.
|
||||||
const HATCH_STORAGE_BINDINGS: u32 = 5;
|
const HATCH_STORAGE_BINDINGS: u32 = 5;
|
||||||
|
|
||||||
/// Mesh compute culling reads one item buffer and writes four indirect
|
|
||||||
/// command buffers.
|
|
||||||
const MESH_CULL_BINDINGS: u32 = 5;
|
|
||||||
|
|
||||||
pub fn detect(device: &wgpu::Device) -> Self {
|
pub fn detect(device: &wgpu::Device) -> Self {
|
||||||
Self::from_limits(&device.limits())
|
Self::from_limits(&device.limits())
|
||||||
}
|
}
|
||||||
@@ -36,11 +32,6 @@ impl DeviceCapabilities {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mesh instancing needs one read-only storage buffer.
|
|
||||||
pub fn supports_mesh_storage_instancing(self) -> bool {
|
|
||||||
self.max_storage_buffers_per_shader_stage >= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn supports_wire_storage(self) -> bool {
|
pub fn supports_wire_storage(self) -> bool {
|
||||||
self.max_storage_buffers_per_shader_stage >= Self::WIRE_STORAGE_BINDINGS
|
self.max_storage_buffers_per_shader_stage >= Self::WIRE_STORAGE_BINDINGS
|
||||||
}
|
}
|
||||||
@@ -49,10 +40,6 @@ impl DeviceCapabilities {
|
|||||||
self.max_storage_buffers_per_shader_stage >= Self::HATCH_STORAGE_BINDINGS
|
self.max_storage_buffers_per_shader_stage >= Self::HATCH_STORAGE_BINDINGS
|
||||||
}
|
}
|
||||||
|
|
||||||
/// WebGL2 reports zero storage bindings and stays on CPU mesh culling.
|
|
||||||
pub fn supports_mesh_compute_culling(self) -> bool {
|
|
||||||
self.max_storage_buffers_per_shader_stage >= Self::MESH_CULL_BINDINGS
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -63,19 +50,15 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn webgl_limits_select_compatibility_paths() {
|
fn webgl_limits_select_compatibility_paths() {
|
||||||
let caps = DeviceCapabilities::from_limits(&wgpu::Limits::downlevel_webgl2_defaults());
|
let caps = DeviceCapabilities::from_limits(&wgpu::Limits::downlevel_webgl2_defaults());
|
||||||
assert!(!caps.supports_mesh_storage_instancing());
|
|
||||||
assert!(!caps.supports_wire_storage());
|
assert!(!caps.supports_wire_storage());
|
||||||
assert!(!caps.supports_batched_hatch());
|
assert!(!caps.supports_batched_hatch());
|
||||||
assert!(!caps.supports_mesh_compute_culling());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_limits_select_storage_paths() {
|
fn default_limits_select_storage_paths() {
|
||||||
let caps = DeviceCapabilities::from_limits(&wgpu::Limits::default());
|
let caps = DeviceCapabilities::from_limits(&wgpu::Limits::default());
|
||||||
assert!(caps.supports_mesh_storage_instancing());
|
|
||||||
assert!(caps.supports_wire_storage());
|
assert!(caps.supports_wire_storage());
|
||||||
assert!(caps.supports_batched_hatch());
|
assert!(caps.supports_batched_hatch());
|
||||||
assert!(caps.supports_mesh_compute_culling());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -86,8 +69,6 @@ mod tests {
|
|||||||
max_vertex_attributes: 16,
|
max_vertex_attributes: 16,
|
||||||
};
|
};
|
||||||
assert!(caps.supports_wire_storage());
|
assert!(caps.supports_wire_storage());
|
||||||
assert!(caps.supports_mesh_storage_instancing());
|
|
||||||
assert!(!caps.supports_batched_hatch());
|
assert!(!caps.supports_batched_hatch());
|
||||||
assert!(!caps.supports_mesh_compute_culling());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+150
-130
@@ -94,6 +94,41 @@ impl MeshVertex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct MeshPlainVertex {
|
||||||
|
pub position: [f32; 3],
|
||||||
|
pub normal: [f32; 3],
|
||||||
|
pub position_low: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshPlainVertex {
|
||||||
|
pub fn layout<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||||
|
const ATTRS: &[wgpu::VertexAttribute] = &[
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: std::mem::offset_of!(MeshPlainVertex, position) as u64,
|
||||||
|
shader_location: 0,
|
||||||
|
format: wgpu::VertexFormat::Float32x3,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: std::mem::offset_of!(MeshPlainVertex, normal) as u64,
|
||||||
|
shader_location: 1,
|
||||||
|
format: wgpu::VertexFormat::Float32x3,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: std::mem::offset_of!(MeshPlainVertex, position_low) as u64,
|
||||||
|
shader_location: 3,
|
||||||
|
format: wgpu::VertexFormat::Float32x3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
wgpu::VertexBufferLayout {
|
||||||
|
array_stride: std::mem::size_of::<Self>() as u64,
|
||||||
|
step_mode: wgpu::VertexStepMode::Vertex,
|
||||||
|
attributes: ATTRS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct MeshEdgeVertex {
|
pub struct MeshEdgeVertex {
|
||||||
@@ -240,6 +275,51 @@ impl MeshInstanceGpu {
|
|||||||
normal_row_2: [n[2] as f32, n[5] as f32, n[8] as f32, 0.0],
|
normal_row_2: [n[2] as f32, n[5] as f32, n[8] as f32, 0.0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn layout<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||||
|
const ATTRS: &[wgpu::VertexAttribute] = &[
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: std::mem::offset_of!(MeshInstanceGpu, model_row_0) as u64,
|
||||||
|
shader_location: 4,
|
||||||
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: std::mem::offset_of!(MeshInstanceGpu, model_row_1) as u64,
|
||||||
|
shader_location: 5,
|
||||||
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: std::mem::offset_of!(MeshInstanceGpu, model_row_2) as u64,
|
||||||
|
shader_location: 7,
|
||||||
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: std::mem::offset_of!(MeshInstanceGpu, translation_low) as u64,
|
||||||
|
shader_location: 8,
|
||||||
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: std::mem::offset_of!(MeshInstanceGpu, normal_row_0) as u64,
|
||||||
|
shader_location: 9,
|
||||||
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: std::mem::offset_of!(MeshInstanceGpu, normal_row_1) as u64,
|
||||||
|
shader_location: 13,
|
||||||
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: std::mem::offset_of!(MeshInstanceGpu, normal_row_2) as u64,
|
||||||
|
shader_location: 14,
|
||||||
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
wgpu::VertexBufferLayout {
|
||||||
|
array_stride: std::mem::size_of::<Self>() as u64,
|
||||||
|
step_mode: wgpu::VertexStepMode::Instance,
|
||||||
|
attributes: ATTRS,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Batched mesh buffers ──────────────────────────────────────────────────
|
// ── Batched mesh buffers ──────────────────────────────────────────────────
|
||||||
@@ -254,6 +334,7 @@ impl MeshInstanceGpu {
|
|||||||
|
|
||||||
pub struct MeshBatchChunk {
|
pub struct MeshBatchChunk {
|
||||||
pub vertex_buffer: wgpu::Buffer,
|
pub vertex_buffer: wgpu::Buffer,
|
||||||
|
pub compact_vertices: bool,
|
||||||
/// Opaque triangle indices (mesh colour alpha ≈ 1). Drawn with depth write.
|
/// Opaque triangle indices (mesh colour alpha ≈ 1). Drawn with depth write.
|
||||||
pub index_buffer: wgpu::Buffer,
|
pub index_buffer: wgpu::Buffer,
|
||||||
pub index_count: u32,
|
pub index_count: u32,
|
||||||
@@ -274,8 +355,6 @@ pub struct MeshBatchChunk {
|
|||||||
pub instance_count: u32,
|
pub instance_count: u32,
|
||||||
pub highlight_ranges: Vec<MeshBatchRange>,
|
pub highlight_ranges: Vec<MeshBatchRange>,
|
||||||
pub handles: rustc_hash::FxHashSet<acadrust::Handle>,
|
pub handles: rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
pub world_aabb: [f32; 6],
|
|
||||||
pub visible: bool,
|
|
||||||
pub material: Option<crate::scene::model::material_model::MeshMaterial>,
|
pub material: Option<crate::scene::model::material_model::MeshMaterial>,
|
||||||
pub face_color: [f32; 4],
|
pub face_color: [f32; 4],
|
||||||
pub material_bind_group: Option<wgpu::BindGroup>,
|
pub material_bind_group: Option<wgpu::BindGroup>,
|
||||||
@@ -326,7 +405,7 @@ fn make_chunk(
|
|||||||
highlight_ranges: &[MeshBatchRange],
|
highlight_ranges: &[MeshBatchRange],
|
||||||
instances: &[MeshInstanceGpu],
|
instances: &[MeshInstanceGpu],
|
||||||
handles: &rustc_hash::FxHashSet<acadrust::Handle>,
|
handles: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
bounds_override: Option<[f32; 6]>,
|
_bounds_override: Option<[f32; 6]>,
|
||||||
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
||||||
face_color: [f32; 4],
|
face_color: [f32; 4],
|
||||||
vertex_buffer_override: Option<wgpu::Buffer>,
|
vertex_buffer_override: Option<wgpu::Buffer>,
|
||||||
@@ -358,6 +437,28 @@ fn make_chunk(
|
|||||||
queue.write_buffer(&buffer, 0, bytemuck::cast_slice(data));
|
queue.write_buffer(&buffer, 0, bytemuck::cast_slice(data));
|
||||||
buffer
|
buffer
|
||||||
};
|
};
|
||||||
|
let compact_vertices = !material_has_textures(material) && wire_indices.is_empty();
|
||||||
|
let mk_plain_vertex = |data: &[MeshVertex]| {
|
||||||
|
if data.is_empty() {
|
||||||
|
return stubs.vertex.clone();
|
||||||
|
}
|
||||||
|
let compact: Vec<MeshPlainVertex> = data
|
||||||
|
.iter()
|
||||||
|
.map(|vertex| MeshPlainVertex {
|
||||||
|
position: vertex.position,
|
||||||
|
normal: vertex.normal,
|
||||||
|
position_low: vertex.position_low,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("mesh.batch.plain_vbuf"),
|
||||||
|
size: std::mem::size_of_val(compact.as_slice()) as u64,
|
||||||
|
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
queue.write_buffer(&buffer, 0, bytemuck::cast_slice(&compact));
|
||||||
|
buffer
|
||||||
|
};
|
||||||
let mk_edge_vertex = |data: &[MeshEdgeVertex], label: &'static str| {
|
let mk_edge_vertex = |data: &[MeshEdgeVertex], label: &'static str| {
|
||||||
if data.is_empty() {
|
if data.is_empty() {
|
||||||
return stubs.vertex.clone();
|
return stubs.vertex.clone();
|
||||||
@@ -377,46 +478,15 @@ fn make_chunk(
|
|||||||
} else {
|
} else {
|
||||||
instances
|
instances
|
||||||
};
|
};
|
||||||
let instance_usage =
|
|
||||||
if device.limits().max_storage_buffers_per_shader_stage > 0 {
|
|
||||||
wgpu::BufferUsages::STORAGE
|
|
||||||
} else {
|
|
||||||
wgpu::BufferUsages::UNIFORM
|
|
||||||
};
|
|
||||||
let mut computed_aabb = [
|
|
||||||
f32::INFINITY,
|
|
||||||
f32::INFINITY,
|
|
||||||
f32::INFINITY,
|
|
||||||
f32::NEG_INFINITY,
|
|
||||||
f32::NEG_INFINITY,
|
|
||||||
f32::NEG_INFINITY,
|
|
||||||
];
|
|
||||||
for vertex in verts {
|
|
||||||
let point = [
|
|
||||||
vertex.position[0] + vertex.position_low[0],
|
|
||||||
vertex.position[1] + vertex.position_low[1],
|
|
||||||
vertex.position[2] + vertex.position_low[2],
|
|
||||||
];
|
|
||||||
for axis in 0..3 {
|
|
||||||
computed_aabb[axis] = computed_aabb[axis].min(point[axis]);
|
|
||||||
computed_aabb[axis + 3] = computed_aabb[axis + 3].max(point[axis]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for vertex in edge_verts {
|
|
||||||
let point = [
|
|
||||||
vertex.position[0] + vertex.position_low[0],
|
|
||||||
vertex.position[1] + vertex.position_low[1],
|
|
||||||
vertex.position[2] + vertex.position_low[2],
|
|
||||||
];
|
|
||||||
for axis in 0..3 {
|
|
||||||
computed_aabb[axis] = computed_aabb[axis].min(point[axis]);
|
|
||||||
computed_aabb[axis + 3] = computed_aabb[axis + 3].max(point[axis]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let world_aabb = bounds_override.unwrap_or(computed_aabb);
|
|
||||||
MeshBatchChunk {
|
MeshBatchChunk {
|
||||||
vertex_buffer: vertex_buffer_override
|
vertex_buffer: vertex_buffer_override.unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| mk_vertex(verts, "mesh.batch.vbuf")),
|
if compact_vertices {
|
||||||
|
mk_plain_vertex(verts)
|
||||||
|
} else {
|
||||||
|
mk_vertex(verts, "mesh.batch.vbuf")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
compact_vertices,
|
||||||
index_buffer: mk_index(indices, "mesh.batch.ibuf"),
|
index_buffer: mk_index(indices, "mesh.batch.ibuf"),
|
||||||
index_count: indices.len() as u32,
|
index_count: indices.len() as u32,
|
||||||
transp_index_buffer: mk_index(transp_indices, "mesh.batch.transp_ibuf"),
|
transp_index_buffer: mk_index(transp_indices, "mesh.batch.transp_ibuf"),
|
||||||
@@ -428,9 +498,9 @@ fn make_chunk(
|
|||||||
edge_vertex_count: edge_verts.len() as u32,
|
edge_vertex_count: edge_verts.len() as u32,
|
||||||
instance_buffer: {
|
instance_buffer: {
|
||||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
label: Some("mesh.batch.instances"),
|
label: Some("mesh.batch.instances"),
|
||||||
size: std::mem::size_of_val(instance_data) as u64,
|
size: std::mem::size_of_val(instance_data) as u64,
|
||||||
usage: instance_usage | wgpu::BufferUsages::COPY_DST,
|
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||||
mapped_at_creation: false,
|
mapped_at_creation: false,
|
||||||
});
|
});
|
||||||
queue.write_buffer(&buffer, 0, bytemuck::cast_slice(instance_data));
|
queue.write_buffer(&buffer, 0, bytemuck::cast_slice(instance_data));
|
||||||
@@ -439,8 +509,6 @@ fn make_chunk(
|
|||||||
instance_count: instance_data.len() as u32,
|
instance_count: instance_data.len() as u32,
|
||||||
highlight_ranges: highlight_ranges.to_vec(),
|
highlight_ranges: highlight_ranges.to_vec(),
|
||||||
handles: handles.clone(),
|
handles: handles.clone(),
|
||||||
world_aabb,
|
|
||||||
visible: true,
|
|
||||||
material: material.cloned(),
|
material: material.cloned(),
|
||||||
face_color,
|
face_color,
|
||||||
material_bind_group: None,
|
material_bind_group: None,
|
||||||
@@ -727,29 +795,9 @@ pub fn create_material_bind_group_from_resources(
|
|||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
layout: &wgpu::BindGroupLayout,
|
layout: &wgpu::BindGroupLayout,
|
||||||
resources: &MeshMaterialResources,
|
resources: &MeshMaterialResources,
|
||||||
instance_buffer: Option<&wgpu::Buffer>,
|
|
||||||
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
||||||
face_color: [f32; 4],
|
face_color: [f32; 4],
|
||||||
) -> wgpu::BindGroup {
|
) -> wgpu::BindGroup {
|
||||||
let fallback_instances;
|
|
||||||
let instance_buffer = match instance_buffer {
|
|
||||||
Some(buffer) => buffer,
|
|
||||||
None => {
|
|
||||||
let usage =
|
|
||||||
if device.limits().max_storage_buffers_per_shader_stage > 0 {
|
|
||||||
wgpu::BufferUsages::STORAGE
|
|
||||||
} else {
|
|
||||||
wgpu::BufferUsages::UNIFORM
|
|
||||||
};
|
|
||||||
fallback_instances =
|
|
||||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
|
||||||
label: Some("mesh.instances.identity"),
|
|
||||||
contents: bytemuck::bytes_of(&MeshInstanceGpu::identity()),
|
|
||||||
usage,
|
|
||||||
});
|
|
||||||
&fallback_instances
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let (material_params, specular, ambient, advanced, flags) =
|
let (material_params, specular, ambient, advanced, flags) =
|
||||||
material_vertex_params(material);
|
material_vertex_params(material);
|
||||||
let surface = MeshSurfaceParams {
|
let surface = MeshSurfaceParams {
|
||||||
@@ -829,10 +877,6 @@ pub fn create_material_bind_group_from_resources(
|
|||||||
binding: 14,
|
binding: 14,
|
||||||
resource: resources.params_buffer.as_entire_binding(),
|
resource: resources.params_buffer.as_entire_binding(),
|
||||||
},
|
},
|
||||||
wgpu::BindGroupEntry {
|
|
||||||
binding: 15,
|
|
||||||
resource: instance_buffer.as_entire_binding(),
|
|
||||||
},
|
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
binding: 16,
|
binding: 16,
|
||||||
resource: surface_buffer.as_entire_binding(),
|
resource: surface_buffer.as_entire_binding(),
|
||||||
@@ -846,7 +890,6 @@ pub fn create_material_bind_group(
|
|||||||
queue: &wgpu::Queue,
|
queue: &wgpu::Queue,
|
||||||
layout: &wgpu::BindGroupLayout,
|
layout: &wgpu::BindGroupLayout,
|
||||||
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
material: Option<&crate::scene::model::material_model::MeshMaterial>,
|
||||||
instance_buffer: Option<&wgpu::Buffer>,
|
|
||||||
) -> wgpu::BindGroup {
|
) -> wgpu::BindGroup {
|
||||||
let resources = create_material_resources(device, queue, material);
|
let resources = create_material_resources(device, queue, material);
|
||||||
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);
|
||||||
@@ -854,7 +897,6 @@ pub fn create_material_bind_group(
|
|||||||
device,
|
device,
|
||||||
layout,
|
layout,
|
||||||
&resources,
|
&resources,
|
||||||
instance_buffer,
|
|
||||||
material,
|
material,
|
||||||
color,
|
color,
|
||||||
)
|
)
|
||||||
@@ -879,7 +921,6 @@ pub fn upload_chunk_material_bind_groups(
|
|||||||
device,
|
device,
|
||||||
layout,
|
layout,
|
||||||
&resources,
|
&resources,
|
||||||
Some(&chunk.instance_buffer),
|
|
||||||
chunk.material.as_ref(),
|
chunk.material.as_ref(),
|
||||||
chunk.face_color,
|
chunk.face_color,
|
||||||
));
|
));
|
||||||
@@ -894,7 +935,6 @@ pub fn upload_chunk_material_bind_groups(
|
|||||||
device,
|
device,
|
||||||
layout,
|
layout,
|
||||||
resources,
|
resources,
|
||||||
Some(&chunk.instance_buffer),
|
|
||||||
chunk.material.as_ref(),
|
chunk.material.as_ref(),
|
||||||
chunk.face_color,
|
chunk.face_color,
|
||||||
));
|
));
|
||||||
@@ -1025,6 +1065,18 @@ fn index_hash(indices: &[u32]) -> u64 {
|
|||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn optimize_triangle_indices(indices: &[u32], vertex_count: usize) -> std::sync::Arc<[u32]> {
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
return meshopt::optimize::optimize_vertex_cache(indices, vertex_count).into();
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
let _ = vertex_count;
|
||||||
|
indices.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn visual_style_partition_key(
|
fn visual_style_partition_key(
|
||||||
style: Option<&crate::scene::model::visual_style_model::MeshVisualStyle>,
|
style: Option<&crate::scene::model::visual_style_model::MeshVisualStyle>,
|
||||||
) -> [u64; 12] {
|
) -> [u64; 12] {
|
||||||
@@ -1061,6 +1113,7 @@ struct InstancedBuildProfile {
|
|||||||
struct InstancedVertexKey {
|
struct InstancedVertexKey {
|
||||||
source: u64,
|
source: u64,
|
||||||
material: u64,
|
material: u64,
|
||||||
|
compact: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -1102,6 +1155,8 @@ fn build_instanced_chunk(
|
|||||||
let material = first.material;
|
let material = first.material;
|
||||||
let color = first.color;
|
let color = first.color;
|
||||||
let source_handle = source.handle.value();
|
let source_handle = source.handle.value();
|
||||||
|
let compact_vertices = !material_has_textures(material)
|
||||||
|
&& (!first.include_edges || !source.edge_verts.is_empty());
|
||||||
let material_identity = if material_has_textures(material) {
|
let material_identity = if material_has_textures(material) {
|
||||||
material.map_or(0, |material| {
|
material.map_or(0, |material| {
|
||||||
material
|
material
|
||||||
@@ -1114,6 +1169,7 @@ fn build_instanced_chunk(
|
|||||||
let vertex_key = InstancedVertexKey {
|
let vertex_key = InstancedVertexKey {
|
||||||
source: source_handle,
|
source: source_handle,
|
||||||
material: material_identity,
|
material: material_identity,
|
||||||
|
compact: compact_vertices,
|
||||||
};
|
};
|
||||||
let shared_vertex_buffer = buffers.vertices.get(&vertex_key).cloned();
|
let shared_vertex_buffer = buffers.vertices.get(&vertex_key).cloned();
|
||||||
let has_normals = mesh.normals.len() == mesh.verts.len();
|
let has_normals = mesh.normals.len() == mesh.verts.len();
|
||||||
@@ -1574,7 +1630,7 @@ pub fn build_mesh_batch_filtered(
|
|||||||
let (shared_indices, shared_hash) = source_indices
|
let (shared_indices, shared_hash) = source_indices
|
||||||
.entry(source_identity)
|
.entry(source_identity)
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
let indices = std::sync::Arc::<[u32]>::from(mesh.indices.as_slice());
|
let indices = optimize_triangle_indices(&mesh.indices, mesh.verts.len());
|
||||||
let hash = index_hash(indices.as_ref());
|
let hash = index_hash(indices.as_ref());
|
||||||
(indices, hash)
|
(indices, hash)
|
||||||
})
|
})
|
||||||
@@ -1655,7 +1711,7 @@ pub fn build_mesh_batch_filtered(
|
|||||||
let parts: Vec<_> = groups
|
let parts: Vec<_> = groups
|
||||||
.into_values()
|
.into_values()
|
||||||
.map(|(material, color, indices)| {
|
.map(|(material, color, indices)| {
|
||||||
let indices = std::sync::Arc::<[u32]>::from(indices);
|
let indices = optimize_triangle_indices(&indices, mesh.verts.len());
|
||||||
let index_hash = index_hash(indices.as_ref());
|
let index_hash = index_hash(indices.as_ref());
|
||||||
CachedFacePart {
|
CachedFacePart {
|
||||||
material,
|
material,
|
||||||
@@ -1711,7 +1767,6 @@ pub fn build_mesh_batch_filtered(
|
|||||||
mesh_spatial_key(part.set, spatial_bounds),
|
mesh_spatial_key(part.set, spatial_bounds),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let storage_instancing = device.limits().max_storage_buffers_per_shader_stage > 0;
|
|
||||||
let mut instance_groups: std::collections::BTreeMap<
|
let mut instance_groups: std::collections::BTreeMap<
|
||||||
InstanceGroupKey,
|
InstanceGroupKey,
|
||||||
Vec<MeshBatchPart<'_>>,
|
Vec<MeshBatchPart<'_>>,
|
||||||
@@ -2173,59 +2228,24 @@ pub fn build_mesh_batch_filtered(
|
|||||||
let mut instanced_profile = InstancedBuildProfile::default();
|
let mut instanced_profile = InstancedBuildProfile::default();
|
||||||
let mut instanced_buffers = InstancedBufferCache::default();
|
let mut instanced_buffers = InstancedBufferCache::default();
|
||||||
for parts in instance_groups.values() {
|
for parts in instance_groups.values() {
|
||||||
let Some(first) = parts.first() else {
|
if let Some((chunk, triangles, profile)) = build_instanced_chunk(
|
||||||
continue;
|
device,
|
||||||
};
|
queue,
|
||||||
let source_bytes = first
|
&stubs,
|
||||||
.mesh
|
&mut instanced_buffers,
|
||||||
.verts
|
parts,
|
||||||
.len()
|
perf_started.is_some(),
|
||||||
.saturating_mul(std::mem::size_of::<MeshVertex>())
|
) {
|
||||||
.saturating_add(first.indices.len().saturating_mul(12))
|
total_tris += triangles;
|
||||||
.saturating_add(
|
chunks.push(chunk);
|
||||||
first
|
instanced_profile.vertices += profile.vertices;
|
||||||
.set
|
instanced_profile.edges += profile.edges;
|
||||||
.instance_source
|
instanced_profile.instances += profile.instances;
|
||||||
.as_ref()
|
instanced_profile.upload += profile.upload;
|
||||||
.map_or(0, |source| {
|
instanced_profile.vertex_count += profile.vertex_count;
|
||||||
source
|
instanced_profile.edge_count += profile.edge_count;
|
||||||
.edge_verts
|
instanced_profile.index_count += profile.index_count;
|
||||||
.len()
|
instanced_profile.chunk_count += profile.chunk_count;
|
||||||
.saturating_mul(std::mem::size_of::<MeshEdgeVertex>())
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.max(1);
|
|
||||||
// Keep repeated geometry bounded while retaining useful culling
|
|
||||||
// granularity. Small block definitions get spatial clusters of roughly
|
|
||||||
// 64–256 INSERTs; a huge source is duplicated only a few times.
|
|
||||||
let max_clusters = ((64 * 1024 * 1024) / source_bytes).clamp(1, 64);
|
|
||||||
let cluster_len = if storage_instancing {
|
|
||||||
parts.len().div_ceil(max_clusters).max(64)
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
};
|
|
||||||
for cluster in parts.chunks(cluster_len) {
|
|
||||||
if let Some((chunk, triangles, profile)) =
|
|
||||||
build_instanced_chunk(
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
&stubs,
|
|
||||||
&mut instanced_buffers,
|
|
||||||
cluster,
|
|
||||||
perf_started.is_some(),
|
|
||||||
)
|
|
||||||
{
|
|
||||||
total_tris += triangles;
|
|
||||||
chunks.push(chunk);
|
|
||||||
instanced_profile.vertices += profile.vertices;
|
|
||||||
instanced_profile.edges += profile.edges;
|
|
||||||
instanced_profile.instances += profile.instances;
|
|
||||||
instanced_profile.upload += profile.upload;
|
|
||||||
instanced_profile.vertex_count += profile.vertex_count;
|
|
||||||
instanced_profile.edge_count += profile.edge_count;
|
|
||||||
instanced_profile.index_count += profile.index_count;
|
|
||||||
instanced_profile.chunk_count += profile.chunk_count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(started) = perf_started {
|
if let Some(started) = perf_started {
|
||||||
|
|||||||
+496
-730
File diff suppressed because it is too large.
Load diff
@@ -390,8 +390,7 @@ impl shader::Primitive for Primitive {
|
|||||||
inner.wire_cull_key = (u64::MAX, u64::MAX, 0, 0);
|
inner.wire_cull_key = (u64::MAX, u64::MAX, 0, 0);
|
||||||
inner.hatch_lod_key = (usize::MAX, u64::MAX, 0, 0, false);
|
inner.hatch_lod_key = (usize::MAX, u64::MAX, 0, 0, false);
|
||||||
inner.wipeout_lod_key = (usize::MAX, u64::MAX, 0, 0, false);
|
inner.wipeout_lod_key = (usize::MAX, u64::MAX, 0, 0, false);
|
||||||
inner.mesh_lod_key = (usize::MAX, u64::MAX, 0, 0);
|
inner.silhouette_key = (usize::MAX, u64::MAX, [u32::MAX; 3], false);
|
||||||
inner.silhouette_key = (usize::MAX, u64::MAX, u64::MAX, false);
|
|
||||||
inner.render_sig = u64::MAX;
|
inner.render_sig = u64::MAX;
|
||||||
}
|
}
|
||||||
// The MSAA / depth / resolve textures are always sized to the
|
// The MSAA / depth / resolve textures are always sized to the
|
||||||
@@ -1039,13 +1038,14 @@ impl shader::Primitive for Primitive {
|
|||||||
let silhouette_key = (
|
let silhouette_key = (
|
||||||
Arc::as_ptr(&vp.meshes) as usize,
|
Arc::as_ptr(&vp.meshes) as usize,
|
||||||
vp.wire_content_id,
|
vp.wire_content_id,
|
||||||
vp.camera_generation,
|
vp.view_dir.to_array().map(f32::to_bits),
|
||||||
silhouette_enabled,
|
silhouette_enabled,
|
||||||
);
|
);
|
||||||
if inner.silhouette_key != silhouette_key {
|
if inner.silhouette_key != silhouette_key {
|
||||||
inner.upload_silhouettes(
|
inner.upload_silhouettes(
|
||||||
device,
|
device,
|
||||||
if silhouette_enabled { &vp.meshes[..] } else { &[] },
|
if silhouette_enabled { &vp.meshes[..] } else { &[] },
|
||||||
|
vp.wire_content_id,
|
||||||
vp.view_dir,
|
vp.view_dir,
|
||||||
);
|
);
|
||||||
inner.silhouette_key = silhouette_key;
|
inner.silhouette_key = silhouette_key;
|
||||||
@@ -1073,22 +1073,6 @@ impl shader::Primitive for Primitive {
|
|||||||
inner.compute_wipeout_lod(view_rot, eye, clip_size.width, clip_size.height);
|
inner.compute_wipeout_lod(view_rot, eye, clip_size.width, clip_size.height);
|
||||||
inner.wipeout_lod_key = wipeout_lod_key;
|
inner.wipeout_lod_key = wipeout_lod_key;
|
||||||
}
|
}
|
||||||
let mesh_lod_key = (
|
|
||||||
Arc::as_ptr(&vp.meshes) as usize,
|
|
||||||
vp.camera_generation,
|
|
||||||
clip_size.width,
|
|
||||||
clip_size.height,
|
|
||||||
);
|
|
||||||
if inner.mesh_lod_key != mesh_lod_key {
|
|
||||||
inner.compute_mesh_lod(
|
|
||||||
queue,
|
|
||||||
view_rot,
|
|
||||||
eye,
|
|
||||||
clip_size.width,
|
|
||||||
clip_size.height,
|
|
||||||
);
|
|
||||||
inner.mesh_lod_key = mesh_lod_key;
|
|
||||||
}
|
|
||||||
let cull_key = (
|
let cull_key = (
|
||||||
vp.wire_content_id,
|
vp.wire_content_id,
|
||||||
vp.camera_generation,
|
vp.camera_generation,
|
||||||
@@ -1298,6 +1282,7 @@ fn render_signature(vp: &ViewportData, clip_w: u32, clip_h: u32) -> u64 {
|
|||||||
vp.show_2d_solid_fills.hash(&mut h);
|
vp.show_2d_solid_fills.hash(&mut h);
|
||||||
vp.mesh_fill.hash(&mut h);
|
vp.mesh_fill.hash(&mut h);
|
||||||
vp.show_3d_edges.hash(&mut h);
|
vp.show_3d_edges.hash(&mut h);
|
||||||
|
vp.display_silhouette.hash(&mut h);
|
||||||
vp.hidden_line.hash(&mut h);
|
vp.hidden_line.hash(&mut h);
|
||||||
// ViewCube visibility is excluded from the *scene* signature elsewhere only
|
// ViewCube visibility is excluded from the *scene* signature elsewhere only
|
||||||
// for the live-hover pass; here it MUST invalidate the cache so toggling the
|
// for the live-hover pass; here it MUST invalidate the cache so toggling the
|
||||||
@@ -3701,6 +3686,7 @@ impl Scene {
|
|||||||
} else {
|
} else {
|
||||||
Arc::new(vec![])
|
Arc::new(vec![])
|
||||||
};
|
};
|
||||||
|
let navigating = !inst.paper_sheet && self.navigating_lod();
|
||||||
Some(ViewportData {
|
Some(ViewportData {
|
||||||
instance_id,
|
instance_id,
|
||||||
force_rasterize,
|
force_rasterize,
|
||||||
@@ -3744,7 +3730,7 @@ impl Scene {
|
|||||||
// actively moving; the scene-render cache holds the full-quality
|
// actively moving; the scene-render cache holds the full-quality
|
||||||
// (hatched) frame once it settles. Only applied to the on-screen
|
// (hatched) frame once it settles. Only applied to the on-screen
|
||||||
// Model / paper content — the paper *sheet* keeps its fills.
|
// Model / paper content — the paper *sheet* keeps its fills.
|
||||||
skip_hatch: self.hatch_lod_enabled() && !inst.paper_sheet && self.navigating_lod(),
|
skip_hatch: self.hatch_lod_enabled() && navigating,
|
||||||
skip_background: !inst.paper_sheet && self.current_layout != "Model",
|
skip_background: !inst.paper_sheet && self.current_layout != "Model",
|
||||||
geometry_epoch: self.geometry_epoch,
|
geometry_epoch: self.geometry_epoch,
|
||||||
camera_generation: self.camera_generation,
|
camera_generation: self.camera_generation,
|
||||||
|
|||||||
+57
-7
@@ -98,9 +98,6 @@ struct MeshInstance {
|
|||||||
normal_row_2: vec4<f32>,
|
normal_row_2: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
@group(1) @binding(15)
|
|
||||||
var<storage, read> mesh_instances: array<MeshInstance>;
|
|
||||||
|
|
||||||
struct VertexIn {
|
struct VertexIn {
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
@location(1) normal: vec3<f32>,
|
@location(1) normal: vec3<f32>,
|
||||||
@@ -111,6 +108,22 @@ struct VertexIn {
|
|||||||
@location(12) uv_refraction_normal: vec4<f32>,
|
@location(12) uv_refraction_normal: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PlainVertexIn {
|
||||||
|
@location(0) position: vec3<f32>,
|
||||||
|
@location(1) normal: vec3<f32>,
|
||||||
|
@location(3) position_low: vec3<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstanceIn {
|
||||||
|
@location(4) model_row_0: vec4<f32>,
|
||||||
|
@location(5) model_row_1: vec4<f32>,
|
||||||
|
@location(7) model_row_2: vec4<f32>,
|
||||||
|
@location(8) translation_low: vec4<f32>,
|
||||||
|
@location(9) normal_row_0: vec4<f32>,
|
||||||
|
@location(13) normal_row_1: vec4<f32>,
|
||||||
|
@location(14) normal_row_2: vec4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
struct VertexOut {
|
struct VertexOut {
|
||||||
@builtin(position) clip_pos: vec4<f32>,
|
@builtin(position) clip_pos: vec4<f32>,
|
||||||
@location(0) normal: vec3<f32>,
|
@location(0) normal: vec3<f32>,
|
||||||
@@ -153,13 +166,25 @@ fn relative_position(
|
|||||||
return (world_high - u.eye_high) + (world_low - u.eye_low);
|
return (world_high - u.eye_high) + (world_low - u.eye_low);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mesh_instance(input: InstanceIn) -> MeshInstance {
|
||||||
|
return MeshInstance(
|
||||||
|
input.model_row_0,
|
||||||
|
input.model_row_1,
|
||||||
|
input.model_row_2,
|
||||||
|
input.translation_low,
|
||||||
|
input.normal_row_0,
|
||||||
|
input.normal_row_1,
|
||||||
|
input.normal_row_2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_main(
|
fn vs_main(
|
||||||
v: VertexIn,
|
v: VertexIn,
|
||||||
@builtin(instance_index) instance_index: u32,
|
i: InstanceIn,
|
||||||
) -> VertexOut {
|
) -> VertexOut {
|
||||||
var out: VertexOut;
|
var out: VertexOut;
|
||||||
let instance = mesh_instances[instance_index];
|
let instance = mesh_instance(i);
|
||||||
let rel = relative_position(v.position, v.position_low, instance);
|
let rel = relative_position(v.position, v.position_low, instance);
|
||||||
out.clip_pos = u.view_rot * vec4<f32>(rel, 1.0);
|
out.clip_pos = u.view_rot * vec4<f32>(rel, 1.0);
|
||||||
out.normal = normalize(vec3<f32>(
|
out.normal = normalize(vec3<f32>(
|
||||||
@@ -178,13 +203,38 @@ fn vs_main(
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main_plain(
|
||||||
|
v: PlainVertexIn,
|
||||||
|
i: InstanceIn,
|
||||||
|
) -> VertexOut {
|
||||||
|
var out: VertexOut;
|
||||||
|
let instance = mesh_instance(i);
|
||||||
|
let rel = relative_position(v.position, v.position_low, instance);
|
||||||
|
out.clip_pos = u.view_rot * vec4<f32>(rel, 1.0);
|
||||||
|
out.normal = normalize(vec3<f32>(
|
||||||
|
dot(instance.normal_row_0.xyz, v.normal),
|
||||||
|
dot(instance.normal_row_1.xyz, v.normal),
|
||||||
|
dot(instance.normal_row_2.xyz, v.normal),
|
||||||
|
));
|
||||||
|
out.world_pos = rel;
|
||||||
|
out.uv_diffuse = vec2<f32>(0.0);
|
||||||
|
out.uv_specular = vec2<f32>(0.0);
|
||||||
|
out.uv_reflection = vec2<f32>(0.0);
|
||||||
|
out.uv_opacity = vec2<f32>(0.0);
|
||||||
|
out.uv_bump = vec2<f32>(0.0);
|
||||||
|
out.uv_refraction = vec2<f32>(0.0);
|
||||||
|
out.uv_normal = vec2<f32>(0.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_edge(
|
fn vs_edge(
|
||||||
v: EdgeVertexIn,
|
v: EdgeVertexIn,
|
||||||
@builtin(instance_index) instance_index: u32,
|
i: InstanceIn,
|
||||||
) -> EdgeVertexOut {
|
) -> EdgeVertexOut {
|
||||||
var out: EdgeVertexOut;
|
var out: EdgeVertexOut;
|
||||||
let instance = mesh_instances[instance_index];
|
let instance = mesh_instance(i);
|
||||||
let rel = relative_position(v.position, v.position_low, instance);
|
let rel = relative_position(v.position, v.position_low, instance);
|
||||||
out.clip_pos = u.view_rot * vec4<f32>(rel, 1.0);
|
out.clip_pos = u.view_rot * vec4<f32>(rel, 1.0);
|
||||||
out.color = v.color;
|
out.color = v.color;
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
struct CullUniform {
|
|
||||||
view_rot: mat4x4<f32>,
|
|
||||||
eye: vec4<f32>,
|
|
||||||
count: vec4<u32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CullItem {
|
|
||||||
min: vec4<f32>,
|
|
||||||
max: vec4<f32>,
|
|
||||||
counts: vec4<u32>,
|
|
||||||
info: vec4<u32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DrawIndexed {
|
|
||||||
index_count: u32,
|
|
||||||
instance_count: u32,
|
|
||||||
first_index: u32,
|
|
||||||
base_vertex: u32,
|
|
||||||
first_instance: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Draw {
|
|
||||||
vertex_count: u32,
|
|
||||||
instance_count: u32,
|
|
||||||
first_vertex: u32,
|
|
||||||
first_instance: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> u: CullUniform;
|
|
||||||
@group(0) @binding(1) var<storage, read> items: array<CullItem>;
|
|
||||||
@group(0) @binding(2) var<storage, read_write> opaque: array<DrawIndexed>;
|
|
||||||
@group(0) @binding(3) var<storage, read_write> transparent: array<DrawIndexed>;
|
|
||||||
@group(0) @binding(4) var<storage, read_write> wire: array<DrawIndexed>;
|
|
||||||
@group(0) @binding(5) var<storage, read_write> edge: array<Draw>;
|
|
||||||
|
|
||||||
fn chunk_visible(item: CullItem) -> bool {
|
|
||||||
if (item.info.y == 0u) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var min_ndc = vec2<f32>(1e30, 1e30);
|
|
||||||
var max_ndc = vec2<f32>(-1e30, -1e30);
|
|
||||||
for (var corner = 0u; corner < 8u; corner++) {
|
|
||||||
let point = vec3<f32>(
|
|
||||||
select(item.min.x, item.max.x, (corner & 1u) != 0u),
|
|
||||||
select(item.min.y, item.max.y, (corner & 2u) != 0u),
|
|
||||||
select(item.min.z, item.max.z, (corner & 4u) != 0u),
|
|
||||||
);
|
|
||||||
let clip = u.view_rot * vec4<f32>(point - u.eye.xyz, 1.0);
|
|
||||||
// A box touching or crossing the eye plane is conservatively retained.
|
|
||||||
if (clip.w <= 1e-6) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
let ndc = clip.xy / clip.w;
|
|
||||||
min_ndc = min(min_ndc, ndc);
|
|
||||||
max_ndc = max(max_ndc, ndc);
|
|
||||||
}
|
|
||||||
// 25% viewport margin on each side is 0.5 in NDC.
|
|
||||||
return !(
|
|
||||||
max_ndc.x < -1.5
|
|
||||||
|| min_ndc.x > 1.5
|
|
||||||
|| max_ndc.y < -1.5
|
|
||||||
|| min_ndc.y > 1.5
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@compute @workgroup_size(64)
|
|
||||||
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
|
|
||||||
let index = id.x;
|
|
||||||
if (index >= u.count.x) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let item = items[index];
|
|
||||||
let instances = select(0u, item.info.x, chunk_visible(item));
|
|
||||||
opaque[index] = DrawIndexed(item.counts.x, instances, 0u, 0u, 0u);
|
|
||||||
transparent[index] = DrawIndexed(item.counts.y, instances, 0u, 0u, 0u);
|
|
||||||
wire[index] = DrawIndexed(item.counts.z, instances, 0u, 0u, 0u);
|
|
||||||
edge[index] = Draw(item.counts.w, instances, 0u, 0u);
|
|
||||||
}
|
|
||||||
@@ -51,19 +51,24 @@ struct MeshInstance {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> u: Uniforms;
|
@group(0) @binding(0) var<uniform> u: Uniforms;
|
||||||
@group(1) @binding(15) var<storage, read> mesh_instances: array<MeshInstance>;
|
|
||||||
|
|
||||||
struct VertexIn {
|
struct VertexIn {
|
||||||
@location(0) position: vec3<f32>,
|
@location(0) position: vec3<f32>,
|
||||||
@location(3) position_low: vec3<f32>,
|
@location(3) position_low: vec3<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct InstanceIn {
|
||||||
|
@location(4) model_row_0: vec4<f32>,
|
||||||
|
@location(5) model_row_1: vec4<f32>,
|
||||||
|
@location(7) model_row_2: vec4<f32>,
|
||||||
|
@location(8) translation_low: vec4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_main(
|
fn vs_main(
|
||||||
vertex: VertexIn,
|
vertex: VertexIn,
|
||||||
@builtin(instance_index) instance_index: u32,
|
instance: InstanceIn,
|
||||||
) -> @builtin(position) vec4<f32> {
|
) -> @builtin(position) vec4<f32> {
|
||||||
let instance = mesh_instances[instance_index];
|
|
||||||
let world_high = vec3<f32>(
|
let world_high = vec3<f32>(
|
||||||
dot(instance.model_row_0.xyz, vertex.position) + instance.model_row_0.w,
|
dot(instance.model_row_0.xyz, vertex.position) + instance.model_row_0.w,
|
||||||
dot(instance.model_row_1.xyz, vertex.position) + instance.model_row_1.w,
|
dot(instance.model_row_1.xyz, vertex.position) + instance.model_row_1.w,
|
||||||
|
|||||||
Reference in new issue
Block a user