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:
Amp
2026-07-29 06:13:25 +00:00
co-authored by heaust
parent 092b319d75
commit 0e866596c0
11 changed files with 110 additions and 166 deletions
+1 -62
View File
@@ -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,
+29 -6
View File
@@ -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), &[]);
+1 -3
View File
@@ -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,
});
}
}
+16 -33
View File
@@ -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];
+25 -23
View File
@@ -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());