Implement native SimpleRenderer reference pipeline (#76)
Some checks failed
Native code generation / deterministic (push) Failing after 2m7s
Imaging and meshing gate / native (push) Failing after 4m9s
JPEG 2000 feature / linux (push) Successful in 2m55s
Native Rust workspace compile / compile (push) Failing after 6m15s
Skia feature / linux (push) Successful in 30m59s
Some checks failed
Native code generation / deterministic (push) Failing after 2m7s
Imaging and meshing gate / native (push) Failing after 4m9s
JPEG 2000 feature / linux (push) Successful in 2m55s
Native Rust workspace compile / compile (push) Failing after 6m15s
Skia feature / linux (push) Successful in 30m59s
This commit is contained in:
@@ -1,8 +1,115 @@
|
||||
use libremetaverse::Primitive;
|
||||
use libremetaverse::rendering::{DetailLevel, SimpleMesh};
|
||||
#![allow(clippy::float_cmp, clippy::too_many_lines)]
|
||||
|
||||
use libremetaverse::rendering::{DetailLevel, IRendering, SimpleMesh, Vertex};
|
||||
use libremetaverse::{
|
||||
MappingType, Primitive, PrimitiveConstructionData, PrimitiveSculptData, PrimitiveTextureEntry,
|
||||
PrimitiveTextureEntryFace,
|
||||
};
|
||||
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
|
||||
use libremetaverse_prim_mesher::PrimMesh;
|
||||
use libremetaverse_rendering_mesh_foundry::MeshFoundry;
|
||||
use libremetaverse_rendering_simple::SimpleRenderer;
|
||||
use libremetaverse_types::{
|
||||
Error, HoleType, PCode, PathCurve, ProfileCurve, SculptType, UUID, Vector2, Vector3,
|
||||
};
|
||||
|
||||
fn uuid(value: &str) -> UUID {
|
||||
UUID::new_with_string(value.into()).expect("fixture UUID")
|
||||
}
|
||||
|
||||
fn primitive(profile: ProfileCurve, path: PathCurve, path_scale_y: f32) -> Primitive {
|
||||
let mut primitive = Primitive::new_with_constructor().expect("Primitive constructor");
|
||||
let mut data =
|
||||
PrimitiveConstructionData::new_with_constructor().expect("ConstructionData constructor");
|
||||
data.p_code = PCode::Prim;
|
||||
data.path_curve = path;
|
||||
data.set_profile_curve_with_property(profile);
|
||||
data.path_scale_x = 1.0;
|
||||
data.path_scale_y = path_scale_y;
|
||||
data.path_begin = 0.0;
|
||||
data.path_end = 1.0;
|
||||
data.profile_begin = 0.0;
|
||||
data.profile_end = 1.0;
|
||||
data.profile_hollow = 0.0;
|
||||
data.set_profile_hole(HoleType::Same);
|
||||
data.path_revolutions = 1.0;
|
||||
primitive.prim_data = data;
|
||||
primitive
|
||||
}
|
||||
|
||||
fn box_primitive() -> Primitive {
|
||||
primitive(ProfileCurve::Square, PathCurve::Line, 1.0)
|
||||
}
|
||||
|
||||
fn mesh_hash(mesh: &SimpleMesh) -> u64 {
|
||||
let mut hash = 0xcbf2_9ce4_8422_2325_u64;
|
||||
let mut mix = |value: u32| {
|
||||
hash ^= u64::from(value);
|
||||
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
|
||||
};
|
||||
mix(u32::try_from(mesh.vertices.len()).expect("fixture vertex count"));
|
||||
mix(u32::try_from(mesh.indices.len()).expect("fixture index count"));
|
||||
for vertex in &mesh.vertices {
|
||||
for value in [
|
||||
vertex.position.x,
|
||||
vertex.position.y,
|
||||
vertex.position.z,
|
||||
vertex.normal.x,
|
||||
vertex.normal.y,
|
||||
vertex.normal.z,
|
||||
vertex.tex_coord.x,
|
||||
vertex.tex_coord.y,
|
||||
] {
|
||||
mix(value.to_bits());
|
||||
}
|
||||
}
|
||||
for index in &mesh.indices {
|
||||
mix(u32::from(*index));
|
||||
}
|
||||
hash
|
||||
}
|
||||
|
||||
fn assert_valid_mesh(mesh: &SimpleMesh) {
|
||||
assert!(!mesh.vertices.is_empty());
|
||||
assert!(!mesh.indices.is_empty());
|
||||
assert_eq!(mesh.indices.len() % 3, 0);
|
||||
for index in &mesh.indices {
|
||||
assert!(usize::from(*index) < mesh.vertices.len());
|
||||
}
|
||||
for vertex in &mesh.vertices {
|
||||
for value in [
|
||||
vertex.position.x,
|
||||
vertex.position.y,
|
||||
vertex.position.z,
|
||||
vertex.normal.x,
|
||||
vertex.normal.y,
|
||||
vertex.normal.z,
|
||||
vertex.tex_coord.x,
|
||||
vertex.tex_coord.y,
|
||||
] {
|
||||
assert!(value.is_finite());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sculpt_image() -> ManagedImage {
|
||||
let mut image = ManagedImage::new(8, 8, ManagedImageImageChannels::COLOR)
|
||||
.expect("sculpt image constructor");
|
||||
for index in 0_u8..64 {
|
||||
image.red[usize::from(index)] = index.wrapping_mul(3);
|
||||
image.green[usize::from(index)] = index.wrapping_mul(5);
|
||||
image.blue[usize::from(index)] = index.wrapping_mul(7);
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
fn vertex(x: f32, y: f32, normal: Vector3, position: Vector3) -> Vertex {
|
||||
Vertex {
|
||||
position,
|
||||
normal,
|
||||
tex_coord: Vector2 { x, y },
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mesh_and_renderer_calls_compile_with_typed_elements() {
|
||||
@@ -23,17 +130,205 @@ fn mesh_and_renderer_calls_compile_with_typed_elements() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_mesher_and_remaining_shims_report_their_current_status() {
|
||||
let mesh = PrimMesh::new(4, 0.0, 1.0, 0.0, 4)
|
||||
.expect("native PrimMesh constructor should create a checked mesh");
|
||||
assert!(mesh.coords.is_empty());
|
||||
assert!(mesh.faces.is_empty());
|
||||
|
||||
let Err(renderer_error) = SimpleRenderer::new() else {
|
||||
panic!("failure-only renderer constructor unexpectedly returned a value");
|
||||
};
|
||||
fn simple_renderer_box_pipeline_is_deterministic_and_matches_golden_mesh() {
|
||||
let renderer = SimpleRenderer::new().expect("SimpleRenderer constructor");
|
||||
let faceted = renderer
|
||||
.generate_faceted_mesh(box_primitive(), DetailLevel::High)
|
||||
.expect("faceted box");
|
||||
assert_eq!(faceted.faces.len(), 6);
|
||||
assert_eq!(
|
||||
renderer_error.csharp_member(),
|
||||
Some("M:LibreMetaverse.Rendering.SimpleRenderer.#ctor")
|
||||
faceted
|
||||
.faces
|
||||
.iter()
|
||||
.map(|face| face.vertices.len())
|
||||
.sum::<usize>(),
|
||||
24
|
||||
);
|
||||
assert_eq!(
|
||||
faceted
|
||||
.faces
|
||||
.iter()
|
||||
.map(|face| face.indices.len())
|
||||
.sum::<usize>(),
|
||||
36
|
||||
);
|
||||
for face in &faceted.faces {
|
||||
assert!(face.min_extent.x <= face.max_extent.x);
|
||||
assert!(face.min_extent.y <= face.max_extent.y);
|
||||
assert!(face.min_extent.z <= face.max_extent.z);
|
||||
}
|
||||
|
||||
let first = renderer
|
||||
.generate_simple_mesh(box_primitive(), DetailLevel::High)
|
||||
.expect("first simple box");
|
||||
let second = renderer
|
||||
.generate_simple_mesh(box_primitive(), DetailLevel::High)
|
||||
.expect("second simple box");
|
||||
assert_valid_mesh(&first);
|
||||
assert_eq!(first.vertices.len(), 24);
|
||||
assert_eq!(first.indices.len(), 36);
|
||||
assert_eq!(mesh_hash(&first), mesh_hash(&second));
|
||||
assert_eq!(mesh_hash(&first), 0x3d2b_10e3_7da0_3d2e);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_renderer_maps_default_and_per_face_texture_metadata() {
|
||||
let default_texture = uuid("11111111-1111-1111-1111-111111111111");
|
||||
let override_texture = uuid("22222222-2222-2222-2222-222222222222");
|
||||
let material = uuid("33333333-3333-3333-3333-333333333333");
|
||||
let render_material = uuid("44444444-4444-4444-4444-444444444444");
|
||||
let mut textures =
|
||||
PrimitiveTextureEntry::new_with_uuid(default_texture).expect("texture entry constructor");
|
||||
let face = textures.create_face(0).expect("face override");
|
||||
face.set_texture_id(override_texture);
|
||||
face.set_material_id(material);
|
||||
face.set_render_material_id(render_material);
|
||||
let mut prim = box_primitive();
|
||||
prim.textures = Some(textures);
|
||||
|
||||
let mesh = SimpleRenderer::new()
|
||||
.expect("renderer")
|
||||
.generate_faceted_mesh(prim, DetailLevel::Medium)
|
||||
.expect("textured box");
|
||||
assert_eq!(mesh.faces[0].texture_face.texture_id(), override_texture);
|
||||
assert_eq!(mesh.faces[0].texture_face.material_id(), material);
|
||||
assert_eq!(
|
||||
mesh.faces[0].texture_face.render_material_id(),
|
||||
render_material
|
||||
);
|
||||
assert_eq!(mesh.faces[1].texture_face.texture_id(), default_texture);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_renderer_transforms_default_and_planar_texture_coordinates() {
|
||||
let renderer = SimpleRenderer::new().expect("renderer");
|
||||
let mut face = PrimitiveTextureEntryFace::new(None).expect("texture face");
|
||||
face.set_repeat_u(2.0);
|
||||
face.set_repeat_v(3.0);
|
||||
face.set_offset_u(0.1);
|
||||
face.set_offset_v(-0.2);
|
||||
face.set_rotation(0.0);
|
||||
face.set_tex_map_type(MappingType::Default);
|
||||
let mut vertices = vec![vertex(0.25, 0.75, Vector3::unit_z(), Vector3::zero())];
|
||||
renderer
|
||||
.transform_tex_coords(
|
||||
&mut vertices,
|
||||
Vector3::zero(),
|
||||
face,
|
||||
Vector3 {
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
},
|
||||
)
|
||||
.expect("default texture transform");
|
||||
assert!((vertices[0].tex_coord.x - 0.1).abs() < 1.0e-6);
|
||||
assert!((vertices[0].tex_coord.y - 1.05).abs() < 1.0e-6);
|
||||
|
||||
let mut planar = PrimitiveTextureEntryFace::new(None).expect("planar face");
|
||||
planar.set_tex_map_type(MappingType::Planar);
|
||||
let mut vertices = vec![vertex(
|
||||
99.0,
|
||||
99.0,
|
||||
Vector3::unit_z(),
|
||||
Vector3 {
|
||||
x: 0.25,
|
||||
y: -0.25,
|
||||
z: 0.5,
|
||||
},
|
||||
)];
|
||||
renderer
|
||||
.transform_tex_coords(
|
||||
&mut vertices,
|
||||
Vector3::zero(),
|
||||
planar,
|
||||
Vector3 {
|
||||
x: 2.0,
|
||||
y: 4.0,
|
||||
z: 1.0,
|
||||
},
|
||||
)
|
||||
.expect("planar texture transform");
|
||||
assert!((vertices[0].tex_coord.x - 1.5).abs() < 1.0e-6);
|
||||
assert!((vertices[0].tex_coord.y - -1.5).abs() < 1.0e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_renderer_generates_deterministic_sculpt_geometry() {
|
||||
let mut prim = box_primitive();
|
||||
let mut sculpt = PrimitiveSculptData::new_with_constructor().expect("sculpt metadata");
|
||||
sculpt.set_type_(SculptType::Plane);
|
||||
prim.sculpt = Some(sculpt);
|
||||
let renderer = SimpleRenderer::new().expect("renderer");
|
||||
let first = renderer
|
||||
.generate_simple_sculpt_mesh(prim.clone(), sculpt_image(), DetailLevel::Low)
|
||||
.expect("first sculpt mesh");
|
||||
let second = renderer
|
||||
.generate_simple_sculpt_mesh(prim, sculpt_image(), DetailLevel::Low)
|
||||
.expect("second sculpt mesh");
|
||||
assert_valid_mesh(&first);
|
||||
assert_eq!(mesh_hash(&first), mesh_hash(&second));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_renderer_trait_dispatch_returns_geometry() {
|
||||
let renderer = SimpleRenderer::new().expect("renderer");
|
||||
let mesh = IRendering::generate_simple_mesh(&renderer, box_primitive(), DetailLevel::Low)
|
||||
.expect("trait call")
|
||||
.expect("renderer returned geometry");
|
||||
assert_valid_mesh(&mesh);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_renderer_reports_source_context_for_invalid_and_mesh_sculpt_inputs() {
|
||||
let renderer = SimpleRenderer::new().expect("renderer");
|
||||
let source = uuid("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
|
||||
let mut invalid = box_primitive();
|
||||
invalid.id = source;
|
||||
invalid.prim_data.path_end = f32::NAN;
|
||||
assert!(matches!(
|
||||
renderer.generate_simple_mesh(invalid, DetailLevel::High),
|
||||
Err(Error::Rendering {
|
||||
source: actual,
|
||||
context: "non-finite primitive construction data"
|
||||
}) if actual == source
|
||||
));
|
||||
|
||||
let mut mesh_sculpt = box_primitive();
|
||||
mesh_sculpt.id = source;
|
||||
let mut sculpt = PrimitiveSculptData::new_with_constructor().expect("sculpt metadata");
|
||||
sculpt.set_type_(SculptType::Mesh);
|
||||
mesh_sculpt.sculpt = Some(sculpt);
|
||||
assert!(matches!(
|
||||
renderer.generate_simple_sculpt_mesh(
|
||||
mesh_sculpt,
|
||||
sculpt_image(),
|
||||
DetailLevel::High,
|
||||
),
|
||||
Err(Error::Rendering {
|
||||
source: actual,
|
||||
context: "mesh sculpt requires a decoded mesh asset"
|
||||
}) if actual == source
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_renderer_rejects_non_finite_texture_inputs() {
|
||||
let renderer = SimpleRenderer::new().expect("renderer");
|
||||
let mut face = PrimitiveTextureEntryFace::new(None).expect("texture face");
|
||||
face.set_repeat_u(f32::INFINITY);
|
||||
let mut vertices = vec![vertex(0.0, 0.0, Vector3::unit_z(), Vector3::zero())];
|
||||
assert_eq!(
|
||||
renderer.transform_tex_coords(
|
||||
&mut vertices,
|
||||
Vector3::zero(),
|
||||
face,
|
||||
Vector3 {
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0
|
||||
},
|
||||
),
|
||||
Err(Error::Argument)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user