diff --git a/crates/renderling-shader/src/lib.rs b/crates/renderling-shader/src/lib.rs index d15b73fa..2ee5c601 100644 --- a/crates/renderling-shader/src/lib.rs +++ b/crates/renderling-shader/src/lib.rs @@ -7,7 +7,11 @@ use core::ops::Mul; use glam::{Quat, Vec3, Vec4Swizzles}; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::Float; -use spirv_std::num_traits::Zero; +use spirv_std::{ + image::{Cubemap, Image2d}, + num_traits::Zero, + Sampler, +}; pub mod array; pub mod bits; @@ -178,3 +182,35 @@ impl IsMatrix for glam::Mat4 { (scale, rotation, translation) } } + +pub trait IsSampler: Copy + Clone {} + +impl IsSampler for Sampler {} + +pub trait Sample2d { + type Sampler: IsSampler; + + fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec2, lod: f32) -> glam::Vec4; +} + +impl Sample2d for Image2d { + type Sampler = Sampler; + + fn sample_by_lod(&self, sampler: Self::Sampler, uv: glam::Vec2, lod: f32) -> glam::Vec4 { + self.sample_by_lod(sampler, uv, lod) + } +} + +pub trait SampleCube { + type Sampler: IsSampler; + + fn sample_by_lod(&self, sampler: Self::Sampler, uv: Vec3, lod: f32) -> glam::Vec4; +} + +impl SampleCube for Cubemap { + type Sampler = Sampler; + + fn sample_by_lod(&self, sampler: Self::Sampler, uv: Vec3, lod: f32) -> glam::Vec4 { + self.sample_by_lod(sampler, uv, lod) + } +} diff --git a/crates/renderling-shader/src/pbr.rs b/crates/renderling-shader/src/pbr.rs index 8d09f116..c5acc1ed 100644 --- a/crates/renderling-shader/src/pbr.rs +++ b/crates/renderling-shader/src/pbr.rs @@ -23,7 +23,7 @@ use crate::{ slab::Slab, stage::{light::LightStyle, GpuLight, LightType, LightingModel}, texture::GpuTexture, - IsVector, + IsSampler, IsVector, Sample2d, SampleCube, }; /// Represents a material on the GPU. @@ -173,18 +173,18 @@ fn outgoing_radiance( (k_d * albedo / core::f32::consts::PI + specular) * radiance * n_dot_l } -pub fn sample_irradiance( - irradiance: &Cubemap, - irradiance_sampler: &Sampler, +pub fn sample_irradiance, S: IsSampler>( + irradiance: &T, + irradiance_sampler: &S, // Normal vector n: Vec3, ) -> Vec3 { irradiance.sample_by_lod(*irradiance_sampler, n, 0.0).xyz() } -pub fn sample_specular_reflection( - prefiltered: &Cubemap, - prefiltered_sampler: &Sampler, +pub fn sample_specular_reflection, S: IsSampler>( + prefiltered: &T, + prefiltered_sampler: &S, // camera position in world space camera_pos: Vec3, // fragment position in world space @@ -200,9 +200,9 @@ pub fn sample_specular_reflection( .xyz() } -pub fn sample_brdf( - brdf: &Image2d, - brdf_sampler: &Sampler, +pub fn sample_brdf, S: IsSampler>( + brdf: &T, + brdf_sampler: &S, // camera position in world space camera_pos: Vec3, // fragment position in world space @@ -405,7 +405,7 @@ pub fn stage_shade_fragment( LightStyle::Directional => { let dir_light = slab.read(light.into_directional_id()); - let l = -dir_light.direction.alt_norm_or_zero(); + let l = (-dir_light.direction).alt_norm_or_zero(); let attenuation = dir_light.intensity; lo += outgoing_radiance( dir_light.color, diff --git a/crates/renderling-shader/src/stage.rs b/crates/renderling-shader/src/stage.rs index 8e533fe7..d75d0366 100644 --- a/crates/renderling-shader/src/stage.rs +++ b/crates/renderling-shader/src/stage.rs @@ -24,7 +24,7 @@ use crate::{ pbr::{self, PbrMaterial}, slab::{Slab, Slabbed}, texture::GpuTexture, - IsMatrix, IsVector, + IsMatrix, IsSampler, IsVector, Sample2d, SampleCube, }; pub mod light; @@ -528,11 +528,11 @@ fn texture_color( color } -fn stage_texture_color( +fn stage_texture_color, S: IsSampler>( texture_id: Id, uv: Vec2, - atlas: &Image2d, - sampler: &Sampler, + atlas: &T, + sampler: &S, atlas_size: UVec2, slab: &[u32], ) -> Vec4 { @@ -1157,6 +1157,61 @@ pub fn stage_fragment( output: &mut Vec4, brigtness: &mut Vec4, ) { + stage_fragment_impl( + atlas, + atlas_sampler, + irradiance, + irradiance_sampler, + prefiltered, + prefiltered_sampler, + brdf, + brdf_sampler, + slab, + in_camera, + in_material, + in_color, + in_uv0, + in_uv1, + in_norm, + in_tangent, + in_bitangent, + in_pos, + output, + brigtness, + ); +} + +#[allow(clippy::too_many_arguments)] +/// Scene fragment shader. +pub fn stage_fragment_impl( + atlas: &T, + atlas_sampler: &S, + irradiance: &C, + irradiance_sampler: &S, + prefiltered: &C, + prefiltered_sampler: &S, + brdf: &T, + brdf_sampler: &S, + + slab: &[u32], + + in_camera: u32, + in_material: u32, + in_color: Vec4, + in_uv0: Vec2, + in_uv1: Vec2, + in_norm: Vec3, + in_tangent: Vec3, + in_bitangent: Vec3, + in_pos: Vec3, + + output: &mut Vec4, + brigtness: &mut Vec4, +) where + T: Sample2d, + C: SampleCube, + S: IsSampler, +{ let StageLegend { atlas_size, debug_mode, diff --git a/crates/renderling-shader/src/stage/light.rs b/crates/renderling-shader/src/stage/light.rs index 5226ed92..bf4fb05d 100644 --- a/crates/renderling-shader/src/stage/light.rs +++ b/crates/renderling-shader/src/stage/light.rs @@ -105,7 +105,7 @@ pub fn gltf_light_intensity_units(kind: gltf::khr_lights_punctual::Kind) -> &'st #[repr(u32)] #[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq)] pub enum LightStyle { Directional = 0, Point = 1, @@ -138,7 +138,7 @@ impl Slabbed for LightStyle { /// A type-erased light Id that is used as a slab pointer to any light type. #[repr(C)] #[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Copy, Clone, Slabbed)] +#[derive(Copy, Clone, PartialEq, Slabbed)] pub struct Light { // The type of the light pub light_type: LightStyle, diff --git a/crates/renderling/src/atlas.rs b/crates/renderling/src/atlas.rs index 68828fe9..8c25fbc5 100644 --- a/crates/renderling/src/atlas.rs +++ b/crates/renderling/src/atlas.rs @@ -495,27 +495,28 @@ impl Atlas { } } +#[cfg(test)] +impl Atlas { + fn atlas_img(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> RgbaImage { + let buffer = crate::Texture::read( + &self.texture.texture, + device, + queue, + self.size.x as usize, + self.size.y as usize, + 4, + 1, + ); + buffer.into_rgba(device).unwrap() + } +} + #[cfg(test)] mod test { use crate::Renderling; use super::*; - impl Atlas { - fn atlas_img(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> RgbaImage { - let buffer = crate::Texture::read( - &self.texture.texture, - device, - queue, - self.size.x as usize, - self.size.y as usize, - 4, - 1, - ); - buffer.into_rgba(device).unwrap() - } - } - #[test] fn can_merge_atlas() { let r = Renderling::headless(100, 100); diff --git a/crates/renderling/src/lib.rs b/crates/renderling/src/lib.rs index ea68b728..857819b7 100644 --- a/crates/renderling/src/lib.rs +++ b/crates/renderling/src/lib.rs @@ -70,6 +70,7 @@ pub use atlas::*; pub use buffer_array::*; pub use camera::*; pub use hdr::*; +use image::GenericImageView; pub use renderer::*; pub use scene::*; pub use skybox::*; @@ -106,6 +107,48 @@ pub mod shader { pub use renderling_shader::stage::{GpuEntityInfo, LightingModel}; +/// A CPU-side texture sampler. +/// +/// Provided primarily for testing purposes. +#[derive(Debug, Clone, Copy)] +pub struct CpuSampler; + +impl crate::shader::IsSampler for CpuSampler {} + +#[derive(Debug)] +pub struct CpuTexture2d { + pub image: image::DynamicImage, +} + +impl crate::shader::Sample2d for CpuTexture2d { + type Sampler = CpuSampler; + + fn sample_by_lod(&self, _sampler: Self::Sampler, uv: glam::Vec2, _lod: f32) -> glam::Vec4 { + // TODO: lerp the CPU texture sampling + let image::Rgba([r, g, b, a]) = self.image.get_pixel(uv.x as u32, uv.y as u32); + glam::Vec4::new( + r as f32 / 255.0, + g as f32 / 255.0, + b as f32 / 255.0, + a as f32 / 255.0, + ) + } +} + +/// A CPU-side cubemap texture. +/// +/// Provided primarily for testing purposes. +pub struct CpuCubemap; + +impl crate::shader::SampleCube for CpuCubemap { + type Sampler = CpuSampler; + + fn sample_by_lod(&self, _sampler: Self::Sampler, _uv: glam::Vec3, _lod: f32) -> glam::Vec4 { + // TODO: implement CPU-side cubemap sampling + glam::Vec4::ONE + } +} + /// Set up the render graph, including: /// * 3d scene objects /// * skybox @@ -591,25 +634,31 @@ mod test { } impl VertexInvocation { - pub fn call(&mut self, slab: &[u32]) { - self.render_unit_id = Id::from(self.instance_index); - self.render_unit = slab.read(self.render_unit_id); + pub fn invoke(instance_index: u32, vertex_index: u32, slab: &[u32]) -> Self { + let mut v = Self { + instance_index, + vertex_index, + ..Default::default() + }; + v.render_unit_id = Id::from(v.instance_index); + v.render_unit = slab.read(v.render_unit_id); new_stage_vertex( - self.instance_index, - self.vertex_index, + v.instance_index, + v.vertex_index, slab, - &mut self.out_camera, - &mut self.out_material, - &mut self.out_color, - &mut self.out_uv0, - &mut self.out_uv1, - &mut self.out_norm, - &mut self.out_tangent, - &mut self.out_bitangent, - &mut self.out_pos, - &mut self.clip_pos, + &mut v.out_camera, + &mut v.out_material, + &mut v.out_color, + &mut v.out_uv0, + &mut v.out_uv1, + &mut v.out_norm, + &mut v.out_tangent, + &mut v.out_bitangent, + &mut v.out_pos, + &mut v.clip_pos, ); - self.ndc_pos = self.clip_pos.xyz() / self.clip_pos.w; + v.ndc_pos = v.clip_pos.xyz() / v.clip_pos.w; + v } } @@ -1217,6 +1266,13 @@ mod test { color: blue, intensity: 10.0, }); + assert_eq!( + Light { + light_type: LightStyle::Directional, + index: dir_red.inner() + }, + dir_red.into() + ); let lights = stage.append_array(&[dir_red.into(), dir_green.into(), dir_blue.into()]); stage.set_lights(lights); @@ -1237,13 +1293,21 @@ mod test { ..Default::default() }); - let _cube = stage.draw_unit(&RenderUnit { + let cube = stage.draw_unit(&RenderUnit { vertex_data: VertexData::new_native(vertex_data), vertex_count: vertices.len() as u32, camera, ..Default::default() }); + let data = stage.read_all_raw().unwrap(); + let invocation = VertexInvocation::invoke(cube.inner(), 0, &data); + println!("vertex invocation: {:#?}", invocation); + + let atlas: image::DynamicImage = stage.atlas.read().unwrap().atlas_img().into(); + let atlas = CpuTexture2d { image: atlas }; + let _ = crate::shader::stage_fragment_impl(&atlas, &CpuSampler); + let img = r.render_image().unwrap(); img_diff::assert_img_eq("scene_cube_directional.png", img); } diff --git a/crates/renderling/src/linkage/stage-main_fragment_scene.spv b/crates/renderling/src/linkage/stage-main_fragment_scene.spv index 74e08400..57781915 100644 Binary files a/crates/renderling/src/linkage/stage-main_fragment_scene.spv and b/crates/renderling/src/linkage/stage-main_fragment_scene.spv differ diff --git a/crates/renderling/src/linkage/stage-stage_fragment.spv b/crates/renderling/src/linkage/stage-stage_fragment.spv index 03759cd3..2b2a66b4 100644 Binary files a/crates/renderling/src/linkage/stage-stage_fragment.spv and b/crates/renderling/src/linkage/stage-stage_fragment.spv differ diff --git a/crates/renderling/src/stage.rs b/crates/renderling/src/stage.rs index 5c38c0e1..ee0ddf40 100644 --- a/crates/renderling/src/stage.rs +++ b/crates/renderling/src/stage.rs @@ -74,10 +74,15 @@ pub struct Stage { impl Stage { /// Create a new stage. pub fn new(device: Device, queue: Queue) -> Self { + let atlas = Atlas::empty(&device, &queue); + let legend = StageLegend { + atlas_size: atlas.size, + ..Default::default() + }; let s = Self { slab: SlabBuffer::new(&device, 256), pipeline: Default::default(), - atlas: Arc::new(RwLock::new(Atlas::empty(&device, &queue))), + atlas: Arc::new(RwLock::new(atlas)), skybox: Arc::new(Mutex::new(Skybox::empty(&device, &queue))), skybox_pipeline: Default::default(), has_skybox: Arc::new(AtomicBool::new(false)), @@ -89,7 +94,7 @@ impl Stage { device, queue, }; - let _ = s.append(&StageLegend::default()); + let _ = s.append(&legend); s } @@ -188,7 +193,7 @@ impl Stage { // The textures bindgroup will have to be remade let _ = self.textures_bindgroup.lock().unwrap().take(); // The atlas size must be reset - let size_id = Id::new(0) + StageLegend::offset_of_atlas_size(); + let size_id = Id::::from(StageLegend::offset_of_atlas_size()); self.write(size_id, &atlas.size)?; let textures = atlas