Complete imaging and meshing integration gate (#44)
Some checks failed
Imaging and meshing gate / native (macos-latest) (push) Has been cancelled
Imaging and meshing gate / native (ubuntu-latest) (push) Has been cancelled
Imaging and meshing gate / native (windows-latest) (push) Has been cancelled
JPEG 2000 feature / linux (push) Has been cancelled
JPEG 2000 feature / macos (push) Has been cancelled
JPEG 2000 feature / windows (push) Has been cancelled
Skia feature / linux (push) Has been cancelled
Skia feature / macos (push) Has been cancelled
Skia feature / windows (push) Has been cancelled
Some checks failed
Imaging and meshing gate / native (macos-latest) (push) Has been cancelled
Imaging and meshing gate / native (ubuntu-latest) (push) Has been cancelled
Imaging and meshing gate / native (windows-latest) (push) Has been cancelled
JPEG 2000 feature / linux (push) Has been cancelled
JPEG 2000 feature / macos (push) Has been cancelled
JPEG 2000 feature / windows (push) Has been cancelled
Skia feature / linux (push) Has been cancelled
Skia feature / macos (push) Has been cancelled
Skia feature / windows (push) Has been cancelled
This commit is contained in:
@@ -15,5 +15,12 @@ jpeg2000 = ["dep:libremetaverse-openjpeg"]
|
||||
libremetaverse-types = { path = "../libremetaverse-types" }
|
||||
libremetaverse-openjpeg = { path = "../libremetaverse-openjpeg", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
stats_alloc = "0.1.10"
|
||||
|
||||
[[bench]]
|
||||
name = "image_pipeline"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
48
crates/libremetaverse-imaging/benches/image_pipeline.rs
Normal file
48
crates/libremetaverse-imaging/benches/image_pipeline.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::alloc::System;
|
||||
use std::hint::black_box;
|
||||
use std::time::Instant;
|
||||
|
||||
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
|
||||
use stats_alloc::{INSTRUMENTED_SYSTEM, Region, StatsAlloc};
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;
|
||||
|
||||
fn main() {
|
||||
const INPUT_SIDE: i32 = 2_048;
|
||||
const OUTPUT_SIDE: i32 = 1_024;
|
||||
|
||||
let channels = ManagedImageImageChannels(
|
||||
ManagedImageImageChannels::COLOR.0 | ManagedImageImageChannels::ALPHA.0,
|
||||
);
|
||||
let mut source = ManagedImage::new(INPUT_SIDE, INPUT_SIDE, channels)
|
||||
.expect("large benchmark image must fit the documented pixel bound");
|
||||
for (index, red) in source.red.iter_mut().enumerate() {
|
||||
*red = u8::try_from(index & 0xff).expect("masked sample");
|
||||
}
|
||||
source.green.fill(97);
|
||||
source.blue.fill(193);
|
||||
source.alpha.fill(255);
|
||||
|
||||
// Warm the validation path before measuring the owned clone, resize, and export pipeline.
|
||||
source.validate().expect("valid benchmark image");
|
||||
let allocation_region = Region::new(ALLOCATOR);
|
||||
let started = Instant::now();
|
||||
let mut resized = source.clone().expect("bounded image clone");
|
||||
resized
|
||||
.resize_bilinear(OUTPUT_SIDE, OUTPUT_SIDE)
|
||||
.expect("bounded bilinear resize");
|
||||
let rgba = resized.export_raw().expect("bounded RGBA export");
|
||||
let elapsed = started.elapsed();
|
||||
let allocation_stats = allocation_region.change();
|
||||
|
||||
assert_eq!((resized.width, resized.height), (OUTPUT_SIDE, OUTPUT_SIDE));
|
||||
assert_eq!(rgba.len(), 4 * OUTPUT_SIDE as usize * OUTPUT_SIDE as usize);
|
||||
black_box((resized, rgba));
|
||||
println!(
|
||||
"image pipeline: {INPUT_SIDE}x{INPUT_SIDE} -> {OUTPUT_SIDE}x{OUTPUT_SIDE} in {elapsed:?}, {} allocations, {} reallocations, {} bytes allocated",
|
||||
allocation_stats.allocations,
|
||||
allocation_stats.reallocations,
|
||||
allocation_stats.bytes_allocated,
|
||||
);
|
||||
}
|
||||
@@ -11,5 +11,12 @@ description = "Primitive meshing shims for the MetaCrate LibreMetaverse rewrite"
|
||||
libremetaverse-imaging = { path = "../libremetaverse-imaging" }
|
||||
libremetaverse-types = { path = "../libremetaverse-types" }
|
||||
|
||||
[dev-dependencies]
|
||||
stats_alloc = "0.1.10"
|
||||
|
||||
[[bench]]
|
||||
name = "meshing"
|
||||
harness = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
54
crates/libremetaverse-prim-mesher/benches/meshing.rs
Normal file
54
crates/libremetaverse-prim-mesher/benches/meshing.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use std::alloc::System;
|
||||
use std::hint::black_box;
|
||||
use std::time::Instant;
|
||||
|
||||
use libremetaverse_prim_mesher::{PathType, PrimMesh};
|
||||
use stats_alloc::{INSTRUMENTED_SYSTEM, Region, StatsAlloc};
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;
|
||||
|
||||
fn main() {
|
||||
let mut mesh = PrimMesh::new(128, 0.03, 0.97, 0.35, 64)
|
||||
.expect("large benchmark profile must fit the documented bounds");
|
||||
mesh.viewer_mode = true;
|
||||
mesh.calc_vertex_normals = true;
|
||||
mesh.path_cut_begin = 0.02;
|
||||
mesh.path_cut_end = 0.98;
|
||||
mesh.hole_size_x = 0.7;
|
||||
mesh.hole_size_y = 0.3;
|
||||
mesh.twist_begin = -45;
|
||||
mesh.twist_end = 270;
|
||||
mesh.taper_x = 0.15;
|
||||
mesh.taper_y = -0.1;
|
||||
mesh.skew = 0.2;
|
||||
mesh.radius = 0.1;
|
||||
mesh.revolutions = 1.5;
|
||||
mesh.steps_per_revolution = 96;
|
||||
|
||||
let allocation_region = Region::new(ALLOCATOR);
|
||||
let started = Instant::now();
|
||||
mesh.extrude(PathType::Circular)
|
||||
.expect("bounded circular extrusion");
|
||||
let indexer = mesh
|
||||
.get_vertex_indexer()
|
||||
.expect("checked viewer indexing")
|
||||
.expect("viewer-mode indexer");
|
||||
let elapsed = started.elapsed();
|
||||
let allocation_stats = allocation_region.change();
|
||||
|
||||
assert!(!mesh.coords.is_empty());
|
||||
assert!(!mesh.faces.is_empty());
|
||||
assert!(!mesh.viewer_faces.is_empty());
|
||||
assert!(mesh.viewer_faces.len() <= mesh.faces.len());
|
||||
black_box((&mesh, &indexer));
|
||||
println!(
|
||||
"meshing pipeline: {} coordinates, {} triangles, {} prim faces in {elapsed:?}, {} allocations, {} reallocations, {} bytes allocated",
|
||||
mesh.coords.len(),
|
||||
mesh.faces.len(),
|
||||
indexer.num_prim_faces,
|
||||
allocation_stats.allocations,
|
||||
allocation_stats.reallocations,
|
||||
allocation_stats.bytes_allocated,
|
||||
);
|
||||
}
|
||||
111
crates/libremetaverse-prim-mesher/tests/rendering_contract.rs
Normal file
111
crates/libremetaverse-prim-mesher/tests/rendering_contract.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! Integration contracts for the data consumed by the future rendering pipeline.
|
||||
|
||||
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
|
||||
use libremetaverse_prim_mesher::{
|
||||
PathType, PrimMesh, SculptMesh, SculptMeshSculptType, ViewerPolygon, ViewerVertex,
|
||||
};
|
||||
|
||||
fn assert_rendering_vertices(vertices: &[ViewerVertex], polygons: &[ViewerPolygon]) {
|
||||
assert!(vertices.len() <= usize::from(u16::MAX) + 1);
|
||||
for vertex in vertices {
|
||||
assert!(vertex.v.x.is_finite() && vertex.v.y.is_finite() && vertex.v.z.is_finite());
|
||||
assert!(vertex.n.x.is_finite() && vertex.n.y.is_finite() && vertex.n.z.is_finite());
|
||||
assert!(vertex.uv.u.is_finite() && vertex.uv.v.is_finite());
|
||||
}
|
||||
for polygon in polygons {
|
||||
for index in [polygon.v1, polygon.v2, polygon.v3] {
|
||||
let index = usize::try_from(index).expect("non-negative viewer index");
|
||||
assert!(index < vertices.len());
|
||||
u16::try_from(index).expect("MeshFoundry-facing index fits u16");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prim_mesh_viewer_groups_are_rendering_ready() {
|
||||
let mut mesh = PrimMesh::new(24, 0.05, 0.95, 0.25, 12).expect("checked profile");
|
||||
mesh.viewer_mode = true;
|
||||
mesh.calc_vertex_normals = true;
|
||||
mesh.path_cut_begin = 0.03;
|
||||
mesh.path_cut_end = 0.96;
|
||||
mesh.twist_begin = -30;
|
||||
mesh.twist_end = 120;
|
||||
mesh.taper_x = 0.1;
|
||||
mesh.skew = 0.15;
|
||||
mesh.revolutions = 1.25;
|
||||
mesh.steps_per_revolution = 48;
|
||||
mesh.extrude(PathType::Circular)
|
||||
.expect("checked viewer extrusion");
|
||||
|
||||
let indexer = mesh
|
||||
.get_vertex_indexer()
|
||||
.expect("checked viewer indexing")
|
||||
.expect("viewer mode produces an indexer");
|
||||
assert_eq!(indexer.num_prim_faces, mesh.num_prim_faces);
|
||||
assert_eq!(indexer.viewer_vertices.len(), indexer.viewer_polygons.len());
|
||||
for (vertices, polygons) in indexer
|
||||
.viewer_vertices
|
||||
.iter()
|
||||
.zip(indexer.viewer_polygons.iter())
|
||||
{
|
||||
assert_rendering_vertices(
|
||||
vertices,
|
||||
polygons
|
||||
.as_deref()
|
||||
.expect("allocated prim-face polygon group"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sculpt_lods_and_topologies_keep_rendering_indices_and_attributes_aligned() {
|
||||
let mut image = ManagedImage::new(128, 128, ManagedImageImageChannels::COLOR)
|
||||
.expect("bounded sculpt fixture");
|
||||
for index in 0..image.red.len() {
|
||||
image.red[index] = u8::try_from(index & 0xff).expect("masked sample");
|
||||
image.green[index] = u8::try_from((index / 128) & 0xff).expect("masked sample");
|
||||
image.blue[index] = u8::try_from((index * 17) & 0xff).expect("masked sample");
|
||||
}
|
||||
|
||||
for lod in [8, 16, 32] {
|
||||
for topology in [
|
||||
SculptMeshSculptType::Plane,
|
||||
SculptMeshSculptType::Sphere,
|
||||
SculptMeshSculptType::Torus,
|
||||
SculptMeshSculptType::Cylinder,
|
||||
] {
|
||||
let mesh =
|
||||
SculptMesh::new_with_managed_image_sculpt_type_int32_boolean_boolean_boolean(
|
||||
image.clone().expect("independent image fixture"),
|
||||
topology,
|
||||
lod,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
.expect("checked sculpt mesh");
|
||||
assert_eq!(mesh.coords.len(), mesh.normals.len());
|
||||
assert_eq!(mesh.coords.len(), mesh.uvs.len());
|
||||
assert_eq!(mesh.faces.len(), mesh.viewer_faces.len());
|
||||
assert!(mesh.coords.len() <= usize::from(u16::MAX) + 1);
|
||||
for face in &mesh.faces {
|
||||
for index in [face.v1, face.v2, face.v3] {
|
||||
let index = usize::try_from(index).expect("non-negative sculpt index");
|
||||
assert!(index < mesh.coords.len());
|
||||
u16::try_from(index).expect("MeshFoundry-facing sculpt index fits u16");
|
||||
}
|
||||
}
|
||||
for viewer in &mesh.viewer_faces {
|
||||
for coord in [viewer.v1, viewer.v2, viewer.v3] {
|
||||
assert!(coord.x.is_finite() && coord.y.is_finite() && coord.z.is_finite());
|
||||
}
|
||||
for normal in [viewer.n1, viewer.n2, viewer.n3] {
|
||||
assert!(normal.x.is_finite() && normal.y.is_finite() && normal.z.is_finite());
|
||||
}
|
||||
for uv in [viewer.uv1, viewer.uv2, viewer.uv3] {
|
||||
assert!(uv.u.is_finite() && uv.v.is_finite());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user