Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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)' ] }
7 changes: 7 additions & 0 deletions NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/Rust-GPU/rust-gpu/issues/465>
* ~~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
Expand Down
1 change: 1 addition & 0 deletions crates/img-diff/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
20 changes: 8 additions & 12 deletions crates/img-diff/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
//! 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;

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 {
Expand All @@ -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 {
Expand All @@ -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(),
}
}
}
Expand Down Expand Up @@ -143,7 +139,7 @@ pub fn save_to(
}

pub fn save(filename: impl AsRef<std::path::Path>, seen: impl Into<DynamicImage>) {
save_to(TEST_OUTPUT_DIR, filename, seen).unwrap()
save_to(test_output_dir(), filename, seen).unwrap()
}

pub fn assert_eq_cfg(
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -228,7 +224,7 @@ pub fn assert_img_eq_cfg_result(
seen: impl Into<DynamicImage>,
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)
Expand Down
24 changes: 23 additions & 1 deletion crates/renderling-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,37 @@ fn wgsl(spv_filepath: impl AsRef<std::path::Path>, destination: impl AsRef<std::
}

/// The cargo workspace directory.
///
/// ## Panics
/// Panics if not called from a checkout of the renderling repo.
pub fn workspace_dir() -> 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.
Expand Down
8 changes: 6 additions & 2 deletions crates/renderling/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -114,3 +115,6 @@ features = [
"Navigator",
"Window"
]

[lints]
workspace = true
21 changes: 16 additions & 5 deletions crates/renderling/src/atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 <https://github.com/Rust-GPU/rust-gpu/pull/460>
// 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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/renderling/src/atlas/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<AnotherPacking>> =
fan_split_n(extent.depth_or_array_layers as usize, new_packing);
Expand Down
3 changes: 1 addition & 2 deletions crates/renderling/src/atlas/shader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 2 additions & 4 deletions crates/renderling/src/bvol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 1 addition & 2 deletions crates/renderling/src/camera/shader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions crates/renderling/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 1 addition & 2 deletions crates/renderling/src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ pub mod shader;
///
/// For more info on morph targets in general, see
/// <https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#morph-targets>
#[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,
Expand Down
6 changes: 2 additions & 4 deletions crates/renderling/src/geometry/shader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ use crate::{
///
/// For more info on vertex skinning, see
/// <https://github.khronos.org/glTF-Tutorials/gltfTutorial/gltfTutorial_019_SimpleSkin.html>
#[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<Id<TransformDescriptor>>,
Expand Down Expand Up @@ -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<CameraDescriptor>,
Expand Down
54 changes: 29 additions & 25 deletions crates/renderling/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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)*);
}
Expand Down Expand Up @@ -304,6 +303,31 @@ pub fn capture_gpu_frame<T>(
}
}

#[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<T: std::future::Future> BlockOnFuture for T {
type Output = <Self as std::future::Future>::Output;

fn block(self) -> Self::Output {
futures_lite::future::block_on(self)
}
}

#[cfg(test)]
mod test {
use super::*;
Expand All @@ -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();
Expand All @@ -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<T: std::future::Future> BlockOnFuture for T {
type Output = <Self as std::future::Future>::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()
Expand Down
Loading