refactor: make instance type metadata opaque
Amp-Thread-ID: https://ampcode.com/threads/T-019f9d91-77c1-7206-a60f-ed6554ce92ab Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
@@ -59,30 +59,13 @@ impl MaterialKey {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct InstanceType {
|
||||
pub words: [u32; 16],
|
||||
}
|
||||
|
||||
impl InstanceType {
|
||||
pub const VISIBLE_MASK: u32 = 1;
|
||||
pub const ZERO: Self = Self { words: [0; 16] };
|
||||
pub const VISIBLE: Self = Self {
|
||||
words: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
};
|
||||
|
||||
pub const fn is_visible(self) -> bool {
|
||||
self.words[0] & Self::VISIBLE_MASK != 0
|
||||
}
|
||||
pub fn set_visible(&mut self, visible: bool) {
|
||||
self.words[0] = (self.words[0] & !Self::VISIBLE_MASK) | visible as u32;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InstanceType {
|
||||
fn default() -> Self {
|
||||
Self::VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
const _: [(); 64] = [(); std::mem::size_of::<InstanceType>()];
|
||||
@@ -685,32 +668,6 @@ impl RenderData {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_mesh_visible(
|
||||
&mut self,
|
||||
handle: MeshHandle,
|
||||
visible: bool,
|
||||
) -> Result<(), RenderDataError> {
|
||||
if !self
|
||||
.meshes
|
||||
.slots
|
||||
.contains(handle.slot(), handle.generation())
|
||||
{
|
||||
return Err(RenderDataError::InvalidMeshHandle);
|
||||
}
|
||||
let owned: Vec<u32> = self
|
||||
.instances
|
||||
.slots
|
||||
.occupied()
|
||||
.filter_map(|(slot, _)| (self.instances.mesh_handle(slot) == handle).then_some(slot))
|
||||
.collect();
|
||||
let next_revision = self.next_revision()?;
|
||||
for slot in owned {
|
||||
self.instances.instance_types[slot as usize].set_visible(visible);
|
||||
}
|
||||
self.revision = next_revision;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_instance_type(
|
||||
&mut self,
|
||||
handle: InstanceHandle,
|
||||
@@ -729,24 +686,6 @@ impl RenderData {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_instance_visible(
|
||||
&mut self,
|
||||
handle: InstanceHandle,
|
||||
visible: bool,
|
||||
) -> Result<(), RenderDataError> {
|
||||
if !self
|
||||
.instances
|
||||
.slots
|
||||
.contains(handle.slot(), handle.generation())
|
||||
{
|
||||
return Err(RenderDataError::InvalidInstanceHandle);
|
||||
}
|
||||
let next_revision = self.next_revision()?;
|
||||
self.instances.instance_types[handle.slot() as usize].set_visible(visible);
|
||||
self.revision = next_revision;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_instance_transform(
|
||||
&mut self,
|
||||
handle: InstanceHandle,
|
||||
|
||||
@@ -104,7 +104,28 @@ fn default_instance_is_protected_and_preserves_its_type() {
|
||||
data.destroy_instance(created.default_instance),
|
||||
Err(RenderDataError::CannotDestroyDefaultInstance)
|
||||
);
|
||||
data.set_mesh_visible(created.mesh, false).unwrap();
|
||||
let replacement = InstanceType {
|
||||
words: [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
8,
|
||||
0x8000_0000,
|
||||
u32::MAX,
|
||||
17,
|
||||
31,
|
||||
63,
|
||||
127,
|
||||
255,
|
||||
511,
|
||||
1023,
|
||||
2047,
|
||||
4095,
|
||||
],
|
||||
};
|
||||
data.set_instance_type(created.default_instance, replacement)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
data.mesh(created.mesh).unwrap().default_instance_type,
|
||||
info().default_instance_type
|
||||
@@ -113,14 +134,16 @@ fn default_instance_is_protected_and_preserves_its_type() {
|
||||
data.instance(created.default_instance)
|
||||
.unwrap()
|
||||
.instance_type,
|
||||
{
|
||||
let mut expected = info().default_instance_type;
|
||||
expected.set_visible(false);
|
||||
expected
|
||||
}
|
||||
replacement
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instance_type_default_is_exactly_zero() {
|
||||
assert_eq!(InstanceType::default(), InstanceType::ZERO);
|
||||
assert_eq!(InstanceType::default().words, [0; 16]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_mesh_and_instance_handles_are_rejected_after_reuse() {
|
||||
let mut data = data();
|
||||
|
||||
@@ -30,9 +30,6 @@ fn encode_scene<'a, T: Scene>(
|
||||
pass.set_vertex_buffer(4, t.slice(..));
|
||||
pass.set_index_buffer(i.slice(..), wgpu::IndexFormat::Uint32);
|
||||
for draw in &gpu.draws {
|
||||
if !draw.instance_type.is_visible() {
|
||||
continue;
|
||||
}
|
||||
pass.set_pipeline(pipelines.get_pipeline(draw.pipeline));
|
||||
if pipelines.requires_material(draw.pipeline) {
|
||||
pass.set_bind_group(2, materials.group(draw.material), &[]);
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::mem::size_of;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use crate::{
|
||||
render_data::{InstanceType, MaterialKey, MeshHandle, PipelineKey},
|
||||
render_data::{MaterialKey, MeshHandle, PipelineKey},
|
||||
renderer::scene_frame::SceneFramePlan,
|
||||
};
|
||||
|
||||
@@ -24,7 +24,6 @@ pub struct DrawItem {
|
||||
pub indices: std::ops::Range<u32>,
|
||||
pub base_vertex: i32,
|
||||
pub instances: std::ops::Range<u32>,
|
||||
pub instance_type: InstanceType,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@@ -169,7 +168,6 @@ impl GpuScenePlan {
|
||||
.ok_or("draw range overflow")?,
|
||||
base_vertex,
|
||||
instances: instance_index..instance_index + 1,
|
||||
instance_type: occurrence.instance_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1511,22 +1511,31 @@ impl<T: Scene + 'static> Renderer<T> {
|
||||
let meshes = js_sys::Array::new();
|
||||
for h in installed.meshes {
|
||||
let item = js_sys::Object::new();
|
||||
let view = self.render_data.mesh(h).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&item,
|
||||
&"handle".into(),
|
||||
&js_sys::Array::of2(&h.slot().into(), &h.generation().into()),
|
||||
)
|
||||
.unwrap();
|
||||
let ty = self
|
||||
.render_data
|
||||
.mesh(h)
|
||||
.unwrap()
|
||||
.default_instance_type
|
||||
.words;
|
||||
js_sys::Reflect::set(
|
||||
&item,
|
||||
&"defaultInstance".into(),
|
||||
&js_sys::Array::of2(
|
||||
&view.default_instance.slot().into(),
|
||||
&view.default_instance.generation().into(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&item,
|
||||
&"defaultType".into(),
|
||||
&js_sys::Array::from_iter(ty.into_iter().map(JsValue::from)),
|
||||
&js_sys::Array::from_iter(
|
||||
view.default_instance_type
|
||||
.words
|
||||
.into_iter()
|
||||
.map(JsValue::from),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
meshes.push(&item);
|
||||
@@ -1534,19 +1543,6 @@ impl<T: Scene + 'static> Renderer<T> {
|
||||
js_sys::Reflect::set(&result, &"meshes".into(), &meshes).unwrap();
|
||||
Ok(result.into())
|
||||
}
|
||||
2 => {
|
||||
self.render_data
|
||||
.set_mesh_visible(
|
||||
MeshHandle::from_parts(words[2], words[3]),
|
||||
match words[4] {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => return Err("INVALID_VISIBILITY"),
|
||||
},
|
||||
)
|
||||
.map_err(|e| render_data_error_code(&e))?;
|
||||
Ok(JsValue::UNDEFINED)
|
||||
}
|
||||
3 => {
|
||||
let mesh = MeshHandle::from_parts(words[2], words[3]);
|
||||
let mut m = [[0.; 4]; 4];
|
||||
@@ -1565,19 +1561,6 @@ impl<T: Scene + 'static> Renderer<T> {
|
||||
.map_err(|e| render_data_error_code(&e))?;
|
||||
Ok(js_sys::Array::of2(&h.slot().into(), &h.generation().into()).into())
|
||||
}
|
||||
4 => {
|
||||
self.render_data
|
||||
.set_instance_visible(
|
||||
InstanceHandle::from_parts(words[2], words[3]),
|
||||
match words[4] {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => return Err("INVALID_VISIBILITY"),
|
||||
},
|
||||
)
|
||||
.map_err(|e| render_data_error_code(&e))?;
|
||||
Ok(JsValue::UNDEFINED)
|
||||
}
|
||||
5 => {
|
||||
let h = InstanceHandle::from_parts(words[2], words[3]);
|
||||
let mut m = [[0.; 4]; 4];
|
||||
|
||||
@@ -161,7 +161,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::render_data::{MeshCreateInfo, RenderDataConfig, IDENTITY_MODEL_TRANSFORM};
|
||||
|
||||
fn mesh(data: &mut RenderData, visible: bool) -> crate::render_data::CreatedMesh {
|
||||
fn mesh(data: &mut RenderData, instance_type: InstanceType) -> crate::render_data::CreatedMesh {
|
||||
data.create_mesh(MeshCreateInfo {
|
||||
positions: &[[0., 0., 0.], [2., 0., 0.], [0., 2., 0.]],
|
||||
normals: &[[0., 0., 1.]; 3],
|
||||
@@ -170,11 +170,7 @@ mod tests {
|
||||
indices: &[0, 1, 2],
|
||||
pipeline: PipelineKey::new(0),
|
||||
material: crate::render_data::MaterialKey::DEFAULT,
|
||||
default_instance_type: if visible {
|
||||
InstanceType::VISIBLE
|
||||
} else {
|
||||
InstanceType::ZERO
|
||||
},
|
||||
default_instance_type: instance_type,
|
||||
default_transform: IDENTITY_MODEL_TRANSFORM,
|
||||
})
|
||||
.unwrap()
|
||||
@@ -186,10 +182,11 @@ mod tests {
|
||||
let mut cache = SceneFrameCache::default();
|
||||
let first = cache.get_or_build(&data).unwrap() as *const _;
|
||||
assert_eq!(first, cache.get_or_build(&data).unwrap() as *const _);
|
||||
let created = mesh(&mut data, true);
|
||||
let created = mesh(&mut data, InstanceType::ZERO);
|
||||
let second = cache.get_or_build(&data).unwrap() as *const _;
|
||||
assert_ne!(first, second);
|
||||
data.set_mesh_visible(created.mesh, false).unwrap();
|
||||
data.set_instance_type(created.default_instance, InstanceType { words: [5; 16] })
|
||||
.unwrap();
|
||||
let third = cache.get_or_build(&data).unwrap() as *const _;
|
||||
assert_ne!(second, third);
|
||||
let mut moved = IDENTITY_MODEL_TRANSFORM;
|
||||
@@ -200,10 +197,15 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retains_hidden_entries_builds_adjacency_and_world_bounds() {
|
||||
fn retains_all_entries_and_preserves_opaque_types() {
|
||||
let mut data = RenderData::new(RenderDataConfig::default()).unwrap();
|
||||
let hidden = mesh(&mut data, false);
|
||||
let shown = mesh(&mut data, true);
|
||||
let zero = mesh(&mut data, InstanceType::ZERO);
|
||||
let marked = mesh(
|
||||
&mut data,
|
||||
InstanceType {
|
||||
words: [0, u32::MAX, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
|
||||
},
|
||||
);
|
||||
let mut translated = IDENTITY_MODEL_TRANSFORM;
|
||||
translated[0][0] = 2.;
|
||||
translated[1][1] = 3.;
|
||||
@@ -211,14 +213,14 @@ mod tests {
|
||||
translated[3][0] = 5.;
|
||||
translated[3][1] = -2.;
|
||||
let extra = data
|
||||
.create_instance(hidden.mesh, translated, InstanceType::ZERO)
|
||||
.create_instance(zero.mesh, translated, InstanceType::ZERO)
|
||||
.unwrap();
|
||||
let plan = SceneFramePlan::build(&data).unwrap();
|
||||
assert_eq!((plan.meshes.len(), plan.occurrences.len()), (2, 3));
|
||||
assert!(plan
|
||||
.occurrences
|
||||
.iter()
|
||||
.any(|o| o.handle == hidden.default_instance && o.is_default));
|
||||
.any(|o| o.handle == zero.default_instance && o.is_default));
|
||||
let occurrence = plan.occurrences.iter().find(|o| o.handle == extra).unwrap();
|
||||
assert_eq!(occurrence.world_aabb.min, [5., -2., 0.]);
|
||||
assert_eq!(occurrence.world_aabb.max, [9., 4., 0.]);
|
||||
@@ -228,10 +230,10 @@ mod tests {
|
||||
.all(|&i| plan.occurrences[i].mesh_index == mesh_index));
|
||||
}
|
||||
assert_eq!(
|
||||
shown.default_instance.slot(),
|
||||
marked.default_instance.slot(),
|
||||
plan.occurrences
|
||||
.iter()
|
||||
.find(|o| o.mesh == shown.mesh)
|
||||
.find(|o| o.mesh == marked.mesh)
|
||||
.unwrap()
|
||||
.handle
|
||||
.slot()
|
||||
@@ -241,26 +243,26 @@ mod tests {
|
||||
#[test]
|
||||
fn slot_reuse_preserves_dense_order_adjacency_and_ownership() {
|
||||
let mut data = RenderData::new(RenderDataConfig::default()).unwrap();
|
||||
let a = mesh(&mut data, true);
|
||||
let doomed = mesh(&mut data, true);
|
||||
let c = mesh(&mut data, true);
|
||||
let a = mesh(&mut data, InstanceType::ZERO);
|
||||
let doomed = mesh(&mut data, InstanceType::ZERO);
|
||||
let c = mesh(&mut data, InstanceType::ZERO);
|
||||
let a_extra = data
|
||||
.create_instance(a.mesh, IDENTITY_MODEL_TRANSFORM, InstanceType::VISIBLE)
|
||||
.create_instance(a.mesh, IDENTITY_MODEL_TRANSFORM, InstanceType::ZERO)
|
||||
.unwrap();
|
||||
let doomed_extra = data
|
||||
.create_instance(doomed.mesh, IDENTITY_MODEL_TRANSFORM, InstanceType::VISIBLE)
|
||||
.create_instance(doomed.mesh, IDENTITY_MODEL_TRANSFORM, InstanceType::ZERO)
|
||||
.unwrap();
|
||||
let c_extra = data
|
||||
.create_instance(c.mesh, IDENTITY_MODEL_TRANSFORM, InstanceType::VISIBLE)
|
||||
.create_instance(c.mesh, IDENTITY_MODEL_TRANSFORM, InstanceType::ZERO)
|
||||
.unwrap();
|
||||
data.destroy_instance(a_extra).unwrap();
|
||||
data.destroy_mesh(doomed.mesh).unwrap();
|
||||
let replacement = mesh(&mut data, true);
|
||||
let replacement = mesh(&mut data, InstanceType::ZERO);
|
||||
let replacement_extra = data
|
||||
.create_instance(
|
||||
replacement.mesh,
|
||||
IDENTITY_MODEL_TRANSFORM,
|
||||
InstanceType::VISIBLE,
|
||||
InstanceType::ZERO,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(replacement.mesh.slot(), doomed.mesh.slot());
|
||||
|
||||
Reference in New Issue
Block a user