diff --git a/Cargo.lock b/Cargo.lock index ba56abfc..124b80a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2097,6 +2097,7 @@ dependencies = [ "glam", "image 0.25.6", "log", + "renderling_build", "snafu 0.7.5", ] diff --git a/Cargo.toml b/Cargo.toml index dc2adaf0..769dc560 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,3 +76,6 @@ opt-level = 3 [patch.crates-io] spirv-std = { git = "https://github.com/rust-gpu/rust-gpu.git", rev = "05b34493ce661dccd6694cf58afc13e3c8f7a7e0" } + +[workspace.lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = [ 'cfg(spirv)' ] } diff --git a/NOTES.md b/NOTES.md index df7addca..ed8e2803 100644 --- a/NOTES.md +++ b/NOTES.md @@ -22,6 +22,13 @@ Just pro-cons on tech choices and little things I don't want to forget whil impl ## cons / limititions / gotchas +* Can't use an array as a slice, it causes this error: + ``` + error: cannot cast between pointer types + from `*[u32; 3]` + to `*[u32]` + ``` + See the ticket I opened * ~~can't use enums (but you can't in glsl or hlsl or msl or wgsl either)~~ you _can_ but they must be simple (like `#[repr(u32)]`) * ~~struct layout size/alignment errors can be really tricky~~ solved by using a slab * rust code must be no-std diff --git a/crates/img-diff/Cargo.toml b/crates/img-diff/Cargo.toml index 94531c38..d216d56e 100644 --- a/crates/img-diff/Cargo.toml +++ b/crates/img-diff/Cargo.toml @@ -9,4 +9,5 @@ edition = "2021" glam = { workspace = true, features = ["std"] } image.workspace = true log.workspace = true +renderling_build = { path = "../renderling-build" } snafu = "^0.7" diff --git a/crates/img-diff/src/lib.rs b/crates/img-diff/src/lib.rs index e3cd62c8..4b28b3e7 100644 --- a/crates/img-diff/src/lib.rs +++ b/crates/img-diff/src/lib.rs @@ -1,13 +1,9 @@ //! Provides image diffing for testing. use glam::{Vec3, Vec4, Vec4Swizzles}; use image::{DynamicImage, Luma, Rgb, Rgb32FImage, Rgba32FImage}; +use renderling_build::{test_img_dir, test_output_dir}; use snafu::prelude::*; -use std::path::Path; -pub const TEST_IMG_DIR: &str = concat!(std::env!("CARGO_WORKSPACE_DIR"), "test_img"); -pub const TEST_OUTPUT_DIR: &str = concat!(std::env!("CARGO_WORKSPACE_DIR"), "test_output"); -pub const WASM_TEST_OUTPUT_DIR: &str = - concat!(std::env!("CARGO_WORKSPACE_DIR"), "test_output/wasm"); const PIXEL_MAGNITUDE_THRESHOLD: f32 = 0.1; pub const LOW_PIXEL_THRESHOLD: f32 = 0.02; const IMAGE_DIFF_THRESHOLD: f32 = 0.05; @@ -15,9 +11,9 @@ const IMAGE_DIFF_THRESHOLD: f32 = 0.05; fn checkerboard_background_color(x: u32, y: u32) -> Vec3 { let size = 16; let x_square_index = x / size; - let x_grey = x_square_index % 2 == 0; + let x_grey = x_square_index.is_multiple_of(2); let y_square_index = y / size; - let y_grey = y_square_index % 2 == 0; + let y_grey = y_square_index.is_multiple_of(2); if (x_grey && y_grey) || (!x_grey && !y_grey) { Vec3::from([0.5, 0.5, 0.5]) } else { @@ -44,7 +40,7 @@ pub struct DiffCfg { /// The name of the test. pub test_name: Option<&'static str>, /// The output directory to store comparisons in. - pub output_dir: &'static str, + pub output_dir: std::path::PathBuf, } impl Default for DiffCfg { @@ -53,7 +49,7 @@ impl Default for DiffCfg { pixel_threshold: PIXEL_MAGNITUDE_THRESHOLD, image_threshold: IMAGE_DIFF_THRESHOLD, test_name: None, - output_dir: TEST_OUTPUT_DIR, + output_dir: test_output_dir(), } } } @@ -143,7 +139,7 @@ pub fn save_to( } pub fn save(filename: impl AsRef, seen: impl Into) { - save_to(TEST_OUTPUT_DIR, filename, seen).unwrap() + save_to(test_output_dir(), filename, seen).unwrap() } pub fn assert_eq_cfg( @@ -185,7 +181,7 @@ pub fn assert_eq_cfg( return Ok(()); } - let mut dir = Path::new(output_dir).join(test_name.unwrap_or(filename)); + let mut dir = output_dir.join(test_name.unwrap_or(filename)); dir.set_extension(""); std::fs::create_dir_all(&dir).expect("cannot create test output dir"); let expected = dir.join("expected.png"); @@ -228,7 +224,7 @@ pub fn assert_img_eq_cfg_result( seen: impl Into, cfg: DiffCfg, ) -> Result<(), String> { - let path = Path::new(TEST_IMG_DIR).join(filename); + let path = test_img_dir().join(filename); let lhs = image::open(&path) .unwrap_or_else(|e| panic!("can't open expected image '{}': {e}", path.display(),)); assert_eq_cfg(filename, lhs, seen, cfg) diff --git a/crates/renderling-build/src/lib.rs b/crates/renderling-build/src/lib.rs index 64a1a2dd..3248b3ae 100644 --- a/crates/renderling-build/src/lib.rs +++ b/crates/renderling-build/src/lib.rs @@ -138,15 +138,37 @@ fn wgsl(spv_filepath: impl AsRef, destination: impl AsRef std::path::PathBuf { - std::path::PathBuf::from(std::env!("CARGO_WORKSPACE_DIR")) + std::path::PathBuf::from(std::env::var("CARGO_WORKSPACE_DIR").unwrap()) +} + +/// The test_img directory. +/// +/// ## Panics +/// Panics if not called from a checkout of the renderling repo. +pub fn test_img_dir() -> std::path::PathBuf { + workspace_dir().join("test_img") } /// The test_output directory. +/// +/// ## Panics +/// Panics if not called from a checkout of the renderling repo. pub fn test_output_dir() -> std::path::PathBuf { workspace_dir().join("test_output") } +/// The WASM test_output directory. +/// +/// ## Panics +/// Panics if not called from a checkout of the renderling repo. +pub fn wasm_test_output_dir() -> std::path::PathBuf { + test_output_dir().join("wasm") +} + #[derive(Debug)] pub struct RenderlingPaths { /// `cargo_workspace` is not available when building outside of the project directory. diff --git a/crates/renderling/Cargo.toml b/crates/renderling/Cargo.toml index 8c9e6b81..c94d1faa 100644 --- a/crates/renderling/Cargo.toml +++ b/crates/renderling/Cargo.toml @@ -26,7 +26,7 @@ crate-type = ["rlib", "cdylib"] default = ["gltf", "ui", "winit"] gltf = ["dep:gltf", "dep:serde_json"] test_i8_i16_extraction = [] -test-utils = ["dep:metal", "dep:wgpu-core"] +test-utils = ["dep:metal", "dep:wgpu-core", "dep:futures-lite"] ui = ["dep:glyph_brush", "dep:loading-bytes", "dep:lyon"] wasm = ["wgpu/fragile-send-sync-non-atomic-wasm"] debug-slab = [] @@ -57,8 +57,9 @@ async-channel = {workspace = true} bytemuck = {workspace = true} craballoc.workspace = true crabslab = { workspace = true, features = ["default"] } -dagga = {workspace=true} crunch = "0.5" +dagga = {workspace=true} +futures-lite = { workspace = true, optional = true } glam = { workspace = true, features = ["std"] } gltf = {workspace = true, optional = true} glyph_brush = {workspace = true, optional = true} @@ -114,3 +115,6 @@ features = [ "Navigator", "Window" ] + +[lints] +workspace = true diff --git a/crates/renderling/src/atlas.rs b/crates/renderling/src/atlas.rs index 2b2a73d0..04ec6637 100644 --- a/crates/renderling/src/atlas.rs +++ b/crates/renderling/src/atlas.rs @@ -24,8 +24,9 @@ pub use cpu::*; pub mod shader; /// Method of addressing the edges of a texture. -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, SlabItem)] +#[derive( + Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, SlabItem, core::fmt::Debug, +)] pub struct TextureModes { pub s: TextureAddressMode, pub t: TextureAddressMode, @@ -56,9 +57,10 @@ pub fn clamp(input: f32) -> f32 { } /// How edges should be handled in texture addressing/wrapping. -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] #[repr(u32)] -#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, SlabItem)] +#[derive( + Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, SlabItem, core::fmt::Debug, +)] pub enum TextureAddressMode { #[default] ClampToEdge, @@ -93,7 +95,16 @@ impl TextureAddressMode { TextureAddressMode::MirroredRepeat => { let sign = if input >= 0.0 { 1.0f32 } else { -1.0 }; let i = input.abs(); - let flip = i as u32 % 2 == 0; + // TODO: Remove this clippy allowance after + // merges. + #[cfg_attr( + cpu, + expect( + clippy::manual_is_multiple_of, + reason = "rust-gpu is not yet on rustc 1.91, which introduced this lint" + ) + )] + let flip = ((i as u32) % 2) == 0; let t = repeat(i); if sign > 0.0 { if flip { diff --git a/crates/renderling/src/atlas/cpu.rs b/crates/renderling/src/atlas/cpu.rs index 7ec71622..73c6f117 100644 --- a/crates/renderling/src/atlas/cpu.rs +++ b/crates/renderling/src/atlas/cpu.rs @@ -668,7 +668,7 @@ fn pack_images<'a>( ) .collect() }; - new_packing.sort_by_key(|a| (a.size().length_squared())); + new_packing.sort_by_key(|a| a.size().length_squared()); let total_images = new_packing.len(); let new_packing_layers: Vec> = fan_split_n(extent.depth_or_array_layers as usize, new_packing); diff --git a/crates/renderling/src/atlas/shader.rs b/crates/renderling/src/atlas/shader.rs index 7ea18ffd..f7ecf9b9 100644 --- a/crates/renderling/src/atlas/shader.rs +++ b/crates/renderling/src/atlas/shader.rs @@ -9,8 +9,7 @@ pub struct AtlasDescriptor { } /// A texture inside the atlas. -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Clone, Copy, Default, PartialEq, SlabItem)] +#[derive(Clone, Copy, Default, PartialEq, SlabItem, core::fmt::Debug)] pub struct AtlasTextureDescriptor { /// The top left offset of texture in the atlas. pub offset_px: UVec2, diff --git a/crates/renderling/src/bvol.rs b/crates/renderling/src/bvol.rs index 43bdf02d..ef7f8ca2 100644 --- a/crates/renderling/src/bvol.rs +++ b/crates/renderling/src/bvol.rs @@ -212,8 +212,7 @@ impl Aabb { } /// Six planes of a view frustum. -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Clone, Copy, Default, PartialEq, SlabItem)] +#[derive(Clone, Copy, Default, PartialEq, SlabItem, core::fmt::Debug)] pub struct Frustum { /// Planes constructing the sides of the frustum, /// each expressed as a normal vector (xyz) and the distance (w) @@ -350,8 +349,7 @@ impl Frustum { /// the center to the corner. /// /// This is _not_ an axis aligned bounding box. -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Clone, Copy, Default, PartialEq, SlabItem)] +#[derive(Clone, Copy, Default, PartialEq, SlabItem, core::fmt::Debug)] pub struct BoundingBox { pub center: Vec3, pub half_extent: Vec3, diff --git a/crates/renderling/src/camera/shader.rs b/crates/renderling/src/camera/shader.rs index 96f10874..212d095d 100644 --- a/crates/renderling/src/camera/shader.rs +++ b/crates/renderling/src/camera/shader.rs @@ -10,8 +10,7 @@ use crate::{bvol::Frustum, math::IsVector}; /// Used for transforming the stage during rendering. /// /// Use [`CameraDescriptor::new`] to create a new camera. -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Default, Clone, Copy, PartialEq, SlabItem)] +#[derive(Default, Clone, Copy, PartialEq, SlabItem, core::fmt::Debug)] pub struct CameraDescriptor { projection: Mat4, view: Mat4, diff --git a/crates/renderling/src/draw.rs b/crates/renderling/src/draw.rs index 781cc0a9..7a60a00e 100644 --- a/crates/renderling/src/draw.rs +++ b/crates/renderling/src/draw.rs @@ -13,8 +13,8 @@ pub use cpu::*; /// Argument buffer layout for draw_indirect commands. #[repr(C)] -#[cfg_attr(cpu, derive(Debug, bytemuck::Pod, bytemuck::Zeroable))] -#[derive(Clone, Copy, Default, SlabItem)] +#[cfg_attr(cpu, derive(bytemuck::Pod, bytemuck::Zeroable))] +#[derive(Clone, Copy, Default, SlabItem, core::fmt::Debug)] pub struct DrawIndirectArgs { pub vertex_count: u32, pub instance_count: u32, diff --git a/crates/renderling/src/geometry.rs b/crates/renderling/src/geometry.rs index 585b229b..5cf8cf5e 100644 --- a/crates/renderling/src/geometry.rs +++ b/crates/renderling/src/geometry.rs @@ -16,8 +16,7 @@ pub mod shader; /// /// For more info on morph targets in general, see /// -#[derive(Clone, Copy, Default, PartialEq, SlabItem)] -#[cfg_attr(cpu, derive(Debug))] +#[derive(Clone, Copy, Default, PartialEq, SlabItem, core::fmt::Debug)] pub struct MorphTarget { pub position: Vec3, pub normal: Vec3, diff --git a/crates/renderling/src/geometry/shader.rs b/crates/renderling/src/geometry/shader.rs index 93a11ec3..461d7263 100644 --- a/crates/renderling/src/geometry/shader.rs +++ b/crates/renderling/src/geometry/shader.rs @@ -9,8 +9,7 @@ use crate::{ /// /// For more info on vertex skinning, see /// -#[derive(Clone, Copy, Default, SlabItem)] -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] +#[derive(Clone, Copy, Default, SlabItem, core::fmt::Debug)] pub struct SkinDescriptor { // Ids of the skeleton nodes' global transforms used as joints in this skin. pub joints_array: Array>, @@ -54,8 +53,7 @@ impl SkinDescriptor { /// geometry. /// /// This descriptor lives at the root (index 0) of the geometry slab. -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Clone, Copy, PartialEq, SlabItem)] +#[derive(Clone, Copy, PartialEq, SlabItem, core::fmt::Debug)] #[offsets] pub struct GeometryDescriptor { pub camera_id: Id, diff --git a/crates/renderling/src/lib.rs b/crates/renderling/src/lib.rs index 4cb8eaf3..c4249de4 100644 --- a/crates/renderling/src/lib.rs +++ b/crates/renderling/src/lib.rs @@ -192,8 +192,7 @@ //! repo](https://github.com/schell/renderling). //! //! 😀☕ -#![allow(unexpected_cfgs)] -#![cfg_attr(target_arch = "spirv", no_std)] +#![cfg_attr(gpu, no_std)] #![deny(clippy::disallowed_methods)] #[cfg(doc)] @@ -248,7 +247,7 @@ pub extern crate glam; /// A wrapper around `std::println` that is a noop on the GPU. macro_rules! println { ($($arg:tt)*) => { - #[cfg(not(target_arch = "spirv"))] + #[cfg(cpu)] { std::println!($($arg)*); } @@ -304,6 +303,31 @@ pub fn capture_gpu_frame( } } +#[cfg(all(cpu, any(test, feature = "test-utils")))] +#[allow(unused, reason = "Used in sync tests in userland")] +/// Marker trait to block on futures in synchronous code. +/// +/// This is a simple convenience. +/// Many of the tests in this crate render something and then read a +/// texture in order to perform a diff on the result using a known image. +/// Since reading from the GPU is async, this trait helps cut down +/// boilerplate. +pub trait BlockOnFuture { + type Output; + + /// Block on the future using [`futures_util::future::block_on`]. + fn block(self) -> Self::Output; +} + +#[cfg(all(cpu, any(test, feature = "test-utils")))] +impl BlockOnFuture for T { + type Output = ::Output; + + fn block(self) -> Self::Output { + futures_lite::future::block_on(self) + } +} + #[cfg(test)] mod test { use super::*; @@ -318,6 +342,8 @@ mod test { #[allow(unused_imports)] pub use renderling_build::{test_output_dir, workspace_dir}; + pub use super::BlockOnFuture; + #[cfg_attr(not(target_arch = "wasm32"), ctor::ctor)] fn init_logging() { let _ = env_logger::builder().is_test(true).try_init(); @@ -334,28 +360,6 @@ mod test { super::capture_gpu_frame(ctx, path, f) } - /// Marker trait to block on futures in synchronous code. - /// - /// This is a simple convenience. - /// Many of the tests in this crate render something and then read a - /// texture in order to perform a diff on the result using a known image. - /// Since reading from the GPU is async, this trait helps cut down - /// boilerplate. - pub trait BlockOnFuture { - type Output; - - /// Block on the future using [`futures_util::future::block_on`]. - fn block(self) -> Self::Output; - } - - impl BlockOnFuture for T { - type Output = ::Output; - - fn block(self) -> Self::Output { - futures_lite::future::block_on(self) - } - } - pub fn make_two_directional_light_setup(stage: &Stage) -> (AnalyticalLight, AnalyticalLight) { let sunlight_a = stage .new_directional_light() diff --git a/crates/renderling/src/light/shader.rs b/crates/renderling/src/light/shader.rs index 7aea57eb..05a67279 100644 --- a/crates/renderling/src/light/shader.rs +++ b/crates/renderling/src/light/shader.rs @@ -271,8 +271,7 @@ impl SpotLightCalculation { /// enough to cover at least one pixel at the distance between the light and /// the scenery. #[repr(C)] -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Copy, Clone, SlabItem)] +#[derive(Copy, Clone, SlabItem, core::fmt::Debug)] pub struct SpotLightDescriptor { // TODO: add `range` to SpotLightDescriptor // See @@ -441,8 +440,7 @@ impl DirectionalLightDescriptor { } #[repr(C)] -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Copy, Clone, SlabItem)] +#[derive(Copy, Clone, SlabItem, core::fmt::Debug)] pub struct PointLightDescriptor { // TODO: add `range` to PointLightDescriptor // See @@ -514,8 +512,7 @@ pub fn radius_of_illumination(intensity_candelas: f32, minimum_illuminance_lux: } #[repr(u32)] -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, core::fmt::Debug)] pub enum LightStyle { Directional = 0, Point = 1, @@ -554,8 +551,7 @@ impl SlabItem for LightStyle { /// A generic light that is used as a slab pointer to a /// specific light type. #[repr(C)] -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Copy, Clone, PartialEq, SlabItem)] +#[derive(Copy, Clone, PartialEq, SlabItem, core::fmt::Debug)] pub struct LightDescriptor { /// The type of the light pub light_type: LightStyle, diff --git a/crates/renderling/src/pbr/debug.rs b/crates/renderling/src/pbr/debug.rs index 5c36e522..7694b67f 100644 --- a/crates/renderling/src/pbr/debug.rs +++ b/crates/renderling/src/pbr/debug.rs @@ -3,9 +3,8 @@ use crabslab::SlabItem; /// Used to debug shaders by early exiting the shader and attempting to display /// the value as shaded colors. -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] #[repr(u32)] -#[derive(Clone, Copy, Default, PartialEq, PartialOrd, SlabItem)] +#[derive(Clone, Copy, Default, PartialEq, PartialOrd, SlabItem, core::fmt::Debug)] pub enum DebugChannel { #[default] None, diff --git a/crates/renderling/src/primitive/shader.rs b/crates/renderling/src/primitive/shader.rs index ef8a5c1b..85843d06 100644 --- a/crates/renderling/src/primitive/shader.rs +++ b/crates/renderling/src/primitive/shader.rs @@ -256,7 +256,7 @@ pub fn primitive_fragment( #[spirv(descriptor_set = 0, binding = 12)] shadow_map_sampler: &Sampler, #[cfg(feature = "debug-slab")] #[spirv(storage_buffer, descriptor_set = 0, binding = 13)] - debug_slab: &mut [u32], + _debug_slab: &mut [u32], #[spirv(flat)] primitive_id: Id, #[spirv(frag_coord)] frag_coord: Vec4, diff --git a/crates/renderling/src/tonemapping.rs b/crates/renderling/src/tonemapping.rs index 96fd63e7..5bf9cda9 100644 --- a/crates/renderling/src/tonemapping.rs +++ b/crates/renderling/src/tonemapping.rs @@ -84,8 +84,7 @@ pub fn tone_map_reinhard(color: Vec3) -> Vec3 { } #[repr(transparent)] -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Clone, Copy, Default, PartialEq, Eq, SlabItem)] +#[derive(Clone, Copy, Default, PartialEq, Eq, SlabItem, core::fmt::Debug)] pub struct Tonemap(u32); impl Tonemap { diff --git a/crates/xtask/src/server.rs b/crates/xtask/src/server.rs index f1b54c27..d549d71d 100644 --- a/crates/xtask/src/server.rs +++ b/crates/xtask/src/server.rs @@ -117,7 +117,7 @@ async fn assert_img_eq_inner( filename, seen, DiffCfg { - output_dir: img_diff::WASM_TEST_OUTPUT_DIR, + output_dir: renderling_build::wasm_test_output_dir(), ..Default::default() }, ) @@ -149,7 +149,7 @@ async fn assert_img_eq( async fn save_inner(filename: &str, img: wire_types::Image) -> Result<(), Error> { let img = image_from_wire(img)?; - img_diff::save_to(img_diff::WASM_TEST_OUTPUT_DIR, filename, img) + img_diff::save_to(renderling_build::wasm_test_output_dir(), filename, img) .map_err(|description| Error { description }) } @@ -197,7 +197,7 @@ async fn artifact_inner(filename: impl AsRef, body: Body) -> Re } async fn artifact(Path(parts): Path>, body: Body) -> Response { - let filename = std::path::PathBuf::from(img_diff::WASM_TEST_OUTPUT_DIR).join(parts.join("/")); + let filename = renderling_build::wasm_test_output_dir().join(parts.join("/")); log::info!("saving artifact to {filename:?}"); let result = artifact_inner(filename, body).await; Response::builder()