diff --git a/Cargo.toml b/Cargo.toml index f124a478..89a2c405 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "crates/renderling-shader", "crates/renderling-gpui", "crates/renderling-derive", - "crates/sandbox", ] exclude = ["./shaders"] diff --git a/crates/example-wasm/src/lib.rs b/crates/example-wasm/src/lib.rs index 12ef43b4..299f8ef8 100644 --- a/crates/example-wasm/src/lib.rs +++ b/crates/example-wasm/src/lib.rs @@ -15,7 +15,7 @@ pub async fn main() { .apply() .unwrap(); - let event_loop = winit::event_loop::EventLoop::new(); + let event_loop = winit::event_loop::EventLoop::new(); let window_size = winit::dpi::LogicalSize { width: 800, height: 600, diff --git a/crates/example/src/main.rs b/crates/example/src/main.rs index 9544f3bc..0537ba97 100644 --- a/crates/example/src/main.rs +++ b/crates/example/src/main.rs @@ -1,7 +1,7 @@ //! Main entry point for the gltf viewer. -use renderling::Renderling; use clap::Parser; use example::gltf; +use renderling::Renderling; #[derive(Parser)] #[command(author, version, about)] @@ -12,7 +12,7 @@ struct Cli { /// Optional HDR image to use as skybox at startup #[arg(short, long)] - skybox: Option + skybox: Option, } fn run() -> Result<(), anyhow::Error> { @@ -38,7 +38,9 @@ fn run() -> Result<(), anyhow::Error> { // Set up a new renderling let mut r = Renderling::try_from_window(&window).unwrap(); let mut run_current_frame: Box)> = - Box::new(futures_lite::future::block_on(gltf::demo(&mut r, cli.model, cli.skybox))); + Box::new(futures_lite::future::block_on(gltf::demo( + &mut r, cli.model, cli.skybox, + ))); event_loop.run(move |event, _target, control_flow| { *control_flow = winit::event_loop::ControlFlow::Poll; diff --git a/crates/renderling-derive/src/lib.rs b/crates/renderling-derive/src/lib.rs index 9d9ecae1..236210f4 100644 --- a/crates/renderling-derive/src/lib.rs +++ b/crates/renderling-derive/src/lib.rs @@ -128,14 +128,12 @@ pub fn derive_from_slab(input: proc_macro::TokenStream) -> proc_macro::TokenStre FieldName::Index(i) => Ident::new(&format!("offset_of_{}", i.index), i.span), FieldName::Ident(field) => Ident::new(&format!("offset_of_{}", field), field.span()), }; - offsets.push( - quote!{ - pub fn #ident() -> usize { - #(<#offset_tys as renderling_shader::slab::Slabbed>::slab_size()+)* - 0 - } + offsets.push(quote! { + pub fn #ident() -> usize { + #(<#offset_tys as renderling_shader::slab::Slabbed>::slab_size()+)* + 0 } - ); + }); offset_tys.push(ty.clone()); } diff --git a/crates/renderling-gpui/src/lib.rs b/crates/renderling-gpui/src/lib.rs index 2d0e48d5..6c6ba613 100644 --- a/crates/renderling-gpui/src/lib.rs +++ b/crates/renderling-gpui/src/lib.rs @@ -3,10 +3,10 @@ use snafu::prelude::*; use std::sync::Arc; use renderling::frame::FrameTextureView; -use renderling::{Device, View, RenderTarget}; +use renderling::{Device, RenderTarget, View}; use renderling::{ FontArc, Frame, GlyphCache, Id, OwnedSection, OwnedText, Queue, Renderling, UiDrawObject, - UiDrawObjectBuilder, UiMode, UiScene, UiVertex, WgpuStateError, ViewMut, + UiDrawObjectBuilder, UiMode, UiScene, UiVertex, ViewMut, WgpuStateError, }; use renderling::{UiRenderPipeline, UiSceneError}; @@ -330,8 +330,12 @@ impl Gpui { Ok(()) } - use renderling::{Graph, graph, frame::{create_frame, clear_frame_and_depth}}; - r.graph.add_subgraph(graph!(create_frame, clear_frame_and_depth, update_scene)); + use renderling::{ + frame::{clear_frame_and_depth, create_frame}, + graph, Graph, + }; + r.graph + .add_subgraph(graph!(create_frame, clear_frame_and_depth, update_scene)); r.graph.add_barrier(); r.graph.add_local::("render"); Self(r) diff --git a/crates/renderling-shader/src/array.rs b/crates/renderling-shader/src/array.rs index 73c57dac..6054615d 100644 --- a/crates/renderling-shader/src/array.rs +++ b/crates/renderling-shader/src/array.rs @@ -5,14 +5,25 @@ use crate::id::Id; use crate::slab::Slabbed; #[repr(C)] -#[derive(Clone, Copy)] -pub struct Array { +pub struct Array { index: u32, len: u32, _phantom: PhantomData, } -impl core::fmt::Debug for Array { +impl Clone for Array { + fn clone(&self) -> Self { + Self { + index: self.index, + len: self.len, + _phantom: PhantomData, + } + } +} + +impl Copy for Array {} + +impl core::fmt::Debug for Array { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Array") .field("index", &self.index) @@ -22,7 +33,7 @@ impl core::fmt::Debug for Array { } } -impl PartialEq for Array { +impl PartialEq for Array { fn eq(&self, other: &Self) -> bool { self.index == other.index && self.len == other.len } @@ -92,4 +103,8 @@ impl Array { Id::new(self.index + (T::slab_size() * index) as u32) } } + + pub fn starting_index(&self) -> usize { + self.index as usize + } } diff --git a/crates/renderling-shader/src/gltf.rs b/crates/renderling-shader/src/gltf.rs new file mode 100644 index 00000000..9187978a --- /dev/null +++ b/crates/renderling-shader/src/gltf.rs @@ -0,0 +1,473 @@ +//! Gltf types that are used in shaders. +use crate::{ + self as renderling_shader, array::Array, id::Id, pbr::PbrMaterial, slab::Slabbed, + stage::GpuTexture, +}; +#[repr(transparent)] +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfBuffer(pub Array); + +#[repr(u32)] +#[derive(Default, Clone, Copy)] +pub enum DataType { + I8, + U8, + I16, + U16, + #[default] + U32, + F32, +} + +impl Slabbed for DataType { + fn slab_size() -> usize { + // 1 + u32::slab_size() + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut hash = 0u32; + let index = hash.read_slab(index, slab); + match hash { + 0 => *self = DataType::I8, + 1 => *self = DataType::U8, + 2 => *self = DataType::I16, + 3 => *self = DataType::U16, + 4 => *self = DataType::U32, + 5 => *self = DataType::F32, + _ => return original_index, + } + index + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + let hash = *self as u32; + hash.write_slab(index, slab) + } +} + +#[repr(u32)] +#[derive(Default, Clone, Copy)] +pub enum Dimensions { + #[default] + Scalar, + Vec2, + Vec3, + Vec4, + Mat2, + Mat3, + Mat4, +} + +impl Slabbed for Dimensions { + fn slab_size() -> usize { + 1 + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut hash = 0u32; + let index = hash.read_slab(index, slab); + match hash { + 0 => *self = Dimensions::Scalar, + 1 => *self = Dimensions::Vec2, + 2 => *self = Dimensions::Vec3, + 3 => *self = Dimensions::Vec4, + 4 => *self = Dimensions::Mat2, + 5 => *self = Dimensions::Mat3, + 6 => *self = Dimensions::Mat4, + _ => return original_index, + } + index + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + match self { + Dimensions::Scalar => 0, + Dimensions::Vec2 => 1, + Dimensions::Vec3 => 2, + Dimensions::Vec4 => 3, + Dimensions::Mat2 => 4, + Dimensions::Mat3 => 5, + Dimensions::Mat4 => 6, + } + .write_slab(index, slab) + } +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfAccessor { + // The byte size of each component that this accessor describes. + pub size: u32, + pub buffer: Id, + // Returns the offset relative to the start of the parent buffer view in bytes. + // + // This will be 0 if the corresponding accessor is sparse. + pub view_offset: u32, + // The stride in bytes between vertex attributes or other interleavable data. + pub view_stride: u32, + // The number of components within the buffer view - not to be confused with the + // number of bytes in the buffer view. + pub count: u32, + // The data type of components in the attribute. + pub component_type: DataType, + // Specifies if the attribute is a scalar, vector, or matrix. + pub dimensions: Dimensions, + // Whether or not the attribute is normalized. + pub normalized: bool, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfPrimitive { + pub material: Id, + pub indices: Id, + pub positions: Id, + pub normals: Id, + pub tangents: Id, + pub colors: Id, + pub tex_coords0: Id, + pub tex_coords1: Id, + pub joints: Id, + pub weights: Id, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfMesh { + pub primitives: Array, + pub weights: Array, +} + +#[derive(Clone, Copy)] +pub enum GltfCamera { + Orthographic { + xmag: f32, + ymag: f32, + zfar: f32, + znear: f32, + }, + Perspective { + aspect_ratio: f32, + yfov: f32, + zfar: f32, + znear: f32, + }, +} + +impl GltfCamera { + const ORTHOGRAPHIC_HASH: u32 = 0; + const PERSPECTIVE_HASH: u32 = 1; +} + +impl Default for GltfCamera { + fn default() -> Self { + GltfCamera::Orthographic { + xmag: 0.0, + ymag: 0.0, + zfar: 0.0, + znear: 0.0, + } + } +} + +impl Slabbed for GltfCamera { + fn slab_size() -> usize { + 1 + 4 + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut hash = 0u32; + let index = hash.read_slab(index, slab); + match hash { + Self::ORTHOGRAPHIC_HASH => { + let mut xmag = 0.0; + let mut ymag = 0.0; + let mut zfar = 0.0; + let mut znear = 0.0; + let index = xmag.read_slab(index, slab); + let index = ymag.read_slab(index, slab); + let index = zfar.read_slab(index, slab); + let _index = znear.read_slab(index, slab); + *self = Self::Orthographic { + xmag, + ymag, + zfar, + znear, + }; + } + Self::PERSPECTIVE_HASH => { + let mut aspect_ratio = 0.0; + let mut yfov = 0.0; + let mut zfar = 0.0; + let mut znear = 0.0; + let index = aspect_ratio.read_slab(index, slab); + let index = yfov.read_slab(index, slab); + let index = zfar.read_slab(index, slab); + let _index = znear.read_slab(index, slab); + *self = Self::Perspective { + aspect_ratio, + yfov, + zfar, + znear, + }; + } + _ => return index, + } + original_index + Self::slab_size() + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + match self { + Self::Orthographic { + xmag, + ymag, + zfar, + znear, + } => { + let index = Self::ORTHOGRAPHIC_HASH.write_slab(index, slab); + let index = xmag.write_slab(index, slab); + let index = ymag.write_slab(index, slab); + let index = zfar.write_slab(index, slab); + let _ = znear.write_slab(index, slab); + } + Self::Perspective { + aspect_ratio, + yfov, + zfar, + znear, + } => { + let index = Self::PERSPECTIVE_HASH.write_slab(index, slab); + let index = aspect_ratio.write_slab(index, slab); + let index = yfov.write_slab(index, slab); + let index = zfar.write_slab(index, slab); + let _ = znear.write_slab(index, slab); + } + } + index + Self::slab_size() + } +} + +#[derive(Default, Clone, Copy)] +pub enum GltfLightKind { + #[default] + Directional, + Point, + Spot { + inner_cone_angle: f32, + outer_cone_angle: f32, + }, +} + +impl Slabbed for GltfLightKind { + fn slab_size() -> usize { + 1 // hash + + 2 // inner_cone_angle, outer_cone_angle + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut hash = 0u32; + let index = hash.read_slab(index, slab); + match hash { + 0 => *self = Self::Directional, + 1 => *self = Self::Point, + 2 => { + let mut inner_cone_angle = 0.0; + let mut outer_cone_angle = 0.0; + let index = inner_cone_angle.read_slab(index, slab); + let _index = outer_cone_angle.read_slab(index, slab); + *self = Self::Spot { + inner_cone_angle, + outer_cone_angle, + }; + } + _ => return index, + } + original_index + Self::slab_size() + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + match self { + Self::Directional => { + let _index = 0.write_slab(index, slab); + } + Self::Point => { + let _index = 1.write_slab(index, slab); + } + Self::Spot { + inner_cone_angle, + outer_cone_angle, + } => { + let index = 2.write_slab(index, slab); + let index = inner_cone_angle.write_slab(index, slab); + let _index = outer_cone_angle.write_slab(index, slab); + } + } + index + Self::slab_size() + } +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfLight { + pub color: glam::Vec3, + pub intensity: f32, + // If `range` is f32::MAX, then the light is a directional light. + pub range: f32, + pub kind: GltfLightKind, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfSkin { + pub joints: Array>, + pub inverse_bind_matrices: Id, + pub skeleton: Id, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfNode { + pub camera: Id, + pub children: Array>, + pub mesh: Id, + pub light: Id, + pub skin: Id, + pub weights: Array, + pub translation: glam::Vec3, + pub rotation: glam::Quat, + pub scale: glam::Vec3, +} + +#[repr(u32)] +#[derive(Default, Clone, Copy, PartialEq)] +pub enum GltfInterpolation { + #[default] + Linear, + Step, + CubicSpline, +} + +impl Slabbed for GltfInterpolation { + fn slab_size() -> usize { + 1 + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut proxy = 0u32; + let index = proxy.read_slab(index, slab); + match proxy { + 0 => *self = GltfInterpolation::Linear, + 1 => *self = GltfInterpolation::Step, + 2 => *self = GltfInterpolation::CubicSpline, + _ => return original_index, + } + index + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + match self { + GltfInterpolation::Linear => 0, + GltfInterpolation::Step => 1, + GltfInterpolation::CubicSpline => 2, + } + .write_slab(index, slab) + } +} + +#[derive(Default, Clone, Copy, PartialEq, Slabbed)] +pub struct GltfAnimationSampler { + pub input: Id, + pub output: Id, + pub interpolation: GltfInterpolation, +} + +#[repr(u32)] +#[derive(Default, Clone, Copy)] +pub enum GltfTargetProperty { + #[default] + Translation, + Rotation, + Scale, + MorphTargetWeights, +} + +impl Slabbed for GltfTargetProperty { + fn slab_size() -> usize { + 1 + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let original_index = index; + let mut proxy = 0u32; + let index = proxy.read_slab(index, slab); + match proxy { + 0 => *self = GltfTargetProperty::Translation, + 1 => *self = GltfTargetProperty::Rotation, + 2 => *self = GltfTargetProperty::Scale, + 3 => *self = GltfTargetProperty::MorphTargetWeights, + _ => return original_index, + } + index + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + match self { + GltfTargetProperty::Translation => 0, + GltfTargetProperty::Rotation => 1, + GltfTargetProperty::Scale => 2, + GltfTargetProperty::MorphTargetWeights => 3, + } + .write_slab(index, slab) + } +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfTarget { + pub node: Id, + pub property: GltfTargetProperty, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfChannel { + pub sampler: Id, + pub target: GltfTarget, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfAnimation { + pub channels: Array, + pub samplers: Array, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfScene { + pub nodes: Array>, +} + +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfBufferView { + pub buffer: Id, + pub offset: u32, + pub length: u32, + pub stride: u32, +} + +/// A document of Gltf data. +/// +/// This tells where certain parts of the Gltf document are stored in the [`Stage`]'s slab. +#[derive(Default, Clone, Copy, Slabbed)] +pub struct GltfDocument { + pub accessors: Array, + pub animations: Array, + pub buffers: Array, + pub cameras: Array, + // TODO: Think about making a `GltfMaterial` + pub materials: Array, + pub meshes: Array, + pub nodes: Array, + pub scenes: Array, + pub skins: Array, + // TODO: Think about making a `GltfTexture` + pub textures: Array, + pub views: Array, +} diff --git a/crates/renderling-shader/src/lib.rs b/crates/renderling-shader/src/lib.rs index b567b566..028b8f44 100644 --- a/crates/renderling-shader/src/lib.rs +++ b/crates/renderling-shader/src/lib.rs @@ -13,6 +13,7 @@ pub mod array; pub mod bits; pub mod convolution; pub mod debug; +pub mod gltf; pub mod id; pub mod math; pub mod pbr; diff --git a/crates/renderling-shader/src/math.rs b/crates/renderling-shader/src/math.rs index 89728a59..3a79962b 100644 --- a/crates/renderling-shader/src/math.rs +++ b/crates/renderling-shader/src/math.rs @@ -77,7 +77,7 @@ pub fn unit_cube() -> Vec<(Vec3, Vec3)> { let n = triangle_face_normal(a, b, c); [(a, n), (b, n), (c, n)] } - _ => unreachable!() + _ => unreachable!(), }) .collect::>() } diff --git a/crates/renderling-shader/src/pbr.rs b/crates/renderling-shader/src/pbr.rs index ccac614c..af1d757f 100644 --- a/crates/renderling-shader/src/pbr.rs +++ b/crates/renderling-shader/src/pbr.rs @@ -17,9 +17,12 @@ use glam::{Vec2, Vec3, Vec4, Vec4Swizzles}; use crate::{ self as renderling_shader, + array::Array, + id::Id, math, + slab::Slab, stage::{GpuLight, GpuTexture, LightType, LightingModel}, - id::Id, IsVector, array::Array, slab::Slab, + IsVector, }; /// Represents a material on the GPU. diff --git a/crates/renderling-shader/src/skybox.rs b/crates/renderling-shader/src/skybox.rs index 210cb35a..36a69e0e 100644 --- a/crates/renderling-shader/src/skybox.rs +++ b/crates/renderling-shader/src/skybox.rs @@ -9,7 +9,13 @@ use spirv_std::{ #[cfg(target_arch = "spirv")] use spirv_std::num_traits::Float; -use crate::{math, stage::GpuConstants, IsVector}; +use crate::{ + id::Id, + math, + slab::Slab, + stage::{Camera, GpuConstants}, + IsVector, +}; const INV_ATAN: Vec2 = Vec2::new(0.1591, core::f32::consts::FRAC_1_PI); @@ -37,6 +43,36 @@ pub fn vertex( *gl_pos = clip_pos.xyww(); } +#[spirv(vertex)] +pub fn slabbed_vertex( + #[spirv(instance_index)] camera_index: u32, + #[spirv(vertex_index)] vertex_index: u32, + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32], + local_pos: &mut Vec3, + #[spirv(position)] clip_pos: &mut Vec4, +) { + let camera_id = Id::::from(camera_index); + let camera = slab.read(camera_id); + let point = math::CUBE[vertex_index as usize]; + *local_pos = point; + let camera_view_without_translation = Mat3::from_mat4(camera.view); + let rot_view = Mat4::from_mat3(camera_view_without_translation); + let position = camera.projection * rot_view * point.extend(1.0); + *clip_pos = position.xyww(); +} + +/// Colors a skybox using a cubemap texture. +#[spirv(fragment)] +pub fn stage_skybox_cubemap( + #[spirv(descriptor_set = 1, binding = 8)] texture: &Cubemap, + #[spirv(descriptor_set = 1, binding = 9)] sampler: &Sampler, + local_pos: Vec3, + out_color: &mut Vec4, +) { + let env_color: Vec3 = texture.sample(*sampler, local_pos.alt_norm_or_zero()).xyz(); + *out_color = env_color.extend(1.0); +} + /// Colors a skybox using a cubemap texture. #[spirv(fragment)] pub fn fragment_cubemap( diff --git a/crates/renderling-shader/src/slab.rs b/crates/renderling-shader/src/slab.rs index 599094c2..60066330 100644 --- a/crates/renderling-shader/src/slab.rs +++ b/crates/renderling-shader/src/slab.rs @@ -99,6 +99,36 @@ impl Slabbed for f32 { } } +impl Slabbed for Option { + fn slab_size() -> usize { + 1 + T::slab_size() + } + + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { + let mut proxy = 0u32; + let index = proxy.read_slab(index, slab); + if proxy == 1 { + let mut t = T::default(); + let index = t.read_slab(index, slab); + *self = Some(t); + index + } else { + *self = None; + index + T::slab_size() + } + } + + fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { + if let Some(t) = self { + let index = 1u32.write_slab(index, slab); + t.write_slab(index, slab) + } else { + let index = 0u32.write_slab(index, slab); + index + T::slab_size() + } + } +} + impl Slabbed for [T; N] { fn slab_size() -> usize { ::slab_size() * N @@ -138,9 +168,6 @@ impl Slabbed for glam::Mat4 { } fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { - if slab.len() < index + 16 { - return index; - } let Self { x_axis, y_axis, @@ -160,9 +187,6 @@ impl Slabbed for glam::Vec2 { } fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { - if slab.len() < index + 2 { - return index; - } let index = self.x.read_slab(index, slab); let index = self.y.read_slab(index, slab); index @@ -184,9 +208,6 @@ impl Slabbed for glam::Vec3 { } fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { - if slab.len() < index + 3 { - return index; - } let Self { x, y, z } = self; let index = x.read_slab(index, slab); let index = y.read_slab(index, slab); @@ -195,9 +216,6 @@ impl Slabbed for glam::Vec3 { } fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { - if slab.len() < index + 3 { - return index; - } let Self { x, y, z } = self; let index = x.write_slab(index, slab); let index = y.write_slab(index, slab); @@ -212,9 +230,6 @@ impl Slabbed for glam::Vec4 { } fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { - if slab.len() < index + 4 { - return index; - } let index = self.x.read_slab(index, slab); let index = self.y.read_slab(index, slab); let index = self.z.read_slab(index, slab); @@ -222,9 +237,6 @@ impl Slabbed for glam::Vec4 { } fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { - if slab.len() < index + 4 { - return index; - } let Self { x, y, z, w } = self; let index = x.write_slab(index, slab); let index = y.write_slab(index, slab); @@ -237,10 +249,8 @@ impl Slabbed for glam::Quat { fn slab_size() -> usize { 16 } + fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { - if slab.len() < index + 4 { - return index; - } let Self { x, y, z, w } = self; let index = x.read_slab(index, slab); let index = y.read_slab(index, slab); @@ -249,9 +259,6 @@ impl Slabbed for glam::Quat { } fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { - if slab.len() < index + 4 { - return index; - } let Self { x, y, z, w } = self; let index = x.write_slab(index, slab); let index = y.write_slab(index, slab); @@ -266,18 +273,12 @@ impl Slabbed for glam::UVec2 { } fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { - if slab.len() < index + 2 { - return index; - } let index = self.x.read_slab(index, slab); let index = self.y.read_slab(index, slab); index } fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { - if slab.len() < index + 2 { - return index; - } let index = self.x.write_slab(index, slab); let index = self.y.write_slab(index, slab); index @@ -290,9 +291,6 @@ impl Slabbed for glam::UVec3 { } fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { - if slab.len() < index + 3 { - return index; - } let index = self.x.read_slab(index, slab); let index = self.y.read_slab(index, slab); let index = self.z.read_slab(index, slab); @@ -300,9 +298,6 @@ impl Slabbed for glam::UVec3 { } fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { - if slab.len() < index + 3 { - return index; - } let index = self.x.write_slab(index, slab); let index = self.y.write_slab(index, slab); let index = self.z.write_slab(index, slab); @@ -316,9 +311,6 @@ impl Slabbed for glam::UVec4 { } fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { - if slab.len() < index + 4 { - return index; - } let index = self.x.read_slab(index, slab); let index = self.y.read_slab(index, slab); let index = self.z.read_slab(index, slab); diff --git a/crates/renderling-shader/src/stage/texture.rs b/crates/renderling-shader/src/stage/texture.rs index f7b0b81d..622179ec 100644 --- a/crates/renderling-shader/src/stage/texture.rs +++ b/crates/renderling-shader/src/stage/texture.rs @@ -1,8 +1,13 @@ //! GPU textures. +//! +// TODO: move this (stage/texture.rs) to lib/texture.rs use glam::{UVec2, Vec2}; use renderling_derive::Slabbed; -use crate::{self as renderling_shader, bits::{bits, extract, insert}}; +use crate::{ + self as renderling_shader, + bits::{bits, extract, insert}, +}; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::*; @@ -31,6 +36,16 @@ impl TextureModes { pub fn set_wrap_t(&mut self, wrap_t: TextureAddressMode) { insert(&mut self.0, Self::BITS_WRAP_T, wrap_t.0) } + + pub fn with_wrap_s(mut self, wrap_s: TextureAddressMode) -> Self { + self.set_wrap_s(wrap_s); + self + } + + pub fn with_wrap_t(mut self, wrap_t: TextureAddressMode) -> Self { + self.set_wrap_t(wrap_t); + self + } } /// A GPU texture. @@ -44,8 +59,8 @@ pub struct GpuTexture { pub size_px: UVec2, // Various toggles of texture modes. pub modes: TextureModes, - - pub padding: u32, + // The index of the image in the atlas. + pub atlas_index: u32, } impl GpuTexture { diff --git a/crates/renderling/src/atlas.rs b/crates/renderling/src/atlas.rs index 6474d66d..668e1d23 100644 --- a/crates/renderling/src/atlas.rs +++ b/crates/renderling/src/atlas.rs @@ -23,7 +23,82 @@ fn gpu_frame_from_rect(r: crunch::Rect) -> (UVec2, UVec2) { #[derive(Debug, Snafu)] pub enum AtlasError { #[snafu(display("Cannot pack textures. {len} textures took up too much space."))] - CannotPackTextures{ len: usize }, + CannotPackTextures { len: usize }, +} + +/// A texture atlas packing, before it is committed to the GPU. +#[derive(Clone)] +pub enum Packing { + Img { + index: usize, + image: SceneImage, + }, + AtlasImg { + index: usize, + offset_px: UVec2, + size_px: UVec2, + }, +} + +impl Packing { + pub fn width(&self) -> u32 { + match self { + Packing::Img { image, .. } => image.width, + Packing::AtlasImg { size_px, .. } => size_px.x, + } + } + + pub fn height(&self) -> u32 { + match self { + Packing::Img { image, .. } => image.height, + Packing::AtlasImg { size_px, .. } => size_px.y, + } + } + + pub fn index(&self) -> usize { + match self { + Packing::Img { index, .. } => *index, + Packing::AtlasImg { index, .. } => *index, + } + } + + pub fn set_index(&mut self, index: usize) { + match self { + Packing::Img { index: i, .. } => *i = index, + Packing::AtlasImg { index: i, .. } => *i = index, + } + } + + pub fn as_scene_img_mut(&mut self) -> Option<&mut SceneImage> { + match self { + Packing::Img { image, .. } => Some(image), + Packing::AtlasImg { .. } => None, + } + } +} + +/// A preview of the packed atlas. +/// +/// Using a preview of the atlas allows us to access the packed frames +/// without committing the atlas to the GPU. Since the items images +/// are still in CPU memory, we can mutate them and then commit the +/// atlas to the GPU. +#[repr(transparent)] +pub struct RepackPreview { + pub items: crunch::PackedItems, +} + +impl RepackPreview { + pub fn get_frame(&self, index: usize) -> Option<(UVec2, UVec2)> { + self.items.items.get(index).map(|item| { + let rect = item.rect; + gpu_frame_from_rect(rect) + }) + } + + pub fn get_mut(&mut self, index: usize) -> Option<&mut Packing> { + self.items.items.get_mut(index).map(|item| &mut item.data) + } } /// A texture atlas, used to store all the textures in a scene. @@ -34,25 +109,28 @@ pub struct Atlas { } impl Atlas { - pub fn new_with_texture(texture: crate::Texture, width: u32, height: u32) -> Self { + pub fn new_with_texture(texture: crate::Texture, size: UVec2) -> Self { Atlas { texture, rects: vec![], - size: UVec2::new(width, height), + size, } } - pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, width: u32, height: u32) -> Self { - log::trace!("creating new atlas with dimensions {width} {height}"); - let size = wgpu::Extent3d { - width, - height, + /// Create a new atlas. + /// + /// Size should be a power of two. + fn new(device: &wgpu::Device, queue: &wgpu::Queue, size: UVec2) -> Self { + log::trace!("creating new atlas with dimensions {size}"); + let extent = wgpu::Extent3d { + width: size.x, + height: size.y, depth_or_array_layers: 1, }; let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("atlas texture"), - size, + size: extent, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, @@ -62,7 +140,7 @@ impl Atlas { | wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); - let img = RgbaImage::from_pixel(width, height, image::Rgba([0, 0, 0, 0])); + let img = RgbaImage::from_pixel(extent.width, extent.height, image::Rgba([0, 0, 0, 0])); queue.write_texture( wgpu::ImageCopyTextureBase { texture: &texture, @@ -73,10 +151,10 @@ impl Atlas { img.as_bytes(), wgpu::ImageDataLayout { offset: 0, - bytes_per_row: Some(4 * width), - rows_per_image: Some(height), + bytes_per_row: Some(4 * extent.width), + rows_per_image: Some(extent.height), }, - size, + extent, ); let sampler_desc = wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge, @@ -88,7 +166,215 @@ impl Atlas { ..Default::default() }; let gpu_texture = crate::Texture::from_wgpu_tex(device, texture, Some(sampler_desc), None); - Self::new_with_texture(gpu_texture, width, height) + Self::new_with_texture(gpu_texture, size) + } + + /// Creates a new empty atlas. + pub fn empty(device: &wgpu::Device, queue: &wgpu::Queue) -> Self { + Self::new(device, queue, UVec2::new(1, 1)) + } + + pub fn is_empty(&self) -> bool { + self.rects.is_empty() + } + + /// Does a dry-run packing of the atlas with the list of images. + /// + /// Returns a vector of ids that determine the locations of the given images + /// but doesn't send any data to the GPU. + pub fn pack_preview<'a>( + device: &wgpu::Device, + images: impl IntoIterator, + ) -> Result, AtlasError> { + let images = images.into_iter().collect::>(); + let len = images.len(); + let limit = device.limits().max_texture_dimension_1d; + let items = crunch::pack_into_po2( + limit as usize, + images.into_iter().map(|img| { + let w = img.width; + let h = img.height; + crunch::Item::new(img, w as usize, h as usize, crunch::Rotation::None) + }), + ) + .ok() + .context(CannotPackTexturesSnafu { len })?; + Ok(items) + } + + pub fn commit_preview( + device: &wgpu::Device, + queue: &wgpu::Queue, + crunch::PackedItems { w, h, items }: crunch::PackedItems, + ) -> Result { + let mut atlas = Atlas::new(device, queue, UVec2::new(w as u32, h as u32)); + atlas.rects = items + .into_iter() + .map( + |crunch::PackedItem { + data: mut img, + rect, + }| { + let bytes = crate::convert_to_rgba8_bytes( + std::mem::take(&mut img.pixels), + img.format, + img.apply_linear_transfer, + ); + queue.write_texture( + wgpu::ImageCopyTextureBase { + texture: &atlas.texture.texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: rect.x as u32, + y: rect.y as u32, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + &bytes, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * img.width), + rows_per_image: Some(img.height), + }, + wgpu::Extent3d { + width: img.width, + height: img.height, + depth_or_array_layers: 1, + }, + ); + rect + }, + ) + .collect(); + + Ok(atlas) + } + + pub fn repack_preview( + &self, + device: &wgpu::Device, + images: impl IntoIterator, + ) -> Result { + let mut images = images.into_iter().collect::>(); + let len = images.len() + self.rects.len(); + let items = crunch::pack_into_po2( + device.limits().max_texture_dimension_1d as usize, + self.rects + .iter() + .map(|r| Packing::AtlasImg { + index: 0, + offset_px: UVec2::new(r.x as u32, r.y as u32), + size_px: UVec2::new(r.w as u32, r.y as u32), + }) + .chain( + images + .drain(..) + .map(|image| Packing::Img { index: 0, image }), + ) + .enumerate() + .map(|(i, mut p)| { + let w = p.width() as usize; + let h = p.height() as usize; + p.set_index(i); + crunch::Item::new(p, w, h, crunch::Rotation::None) + }), + ) + .ok() + .context(CannotPackTexturesSnafu { len })?; + debug_assert!(items + .items + .iter() + .zip(0..) + .all(|(item, i)| item.data.index() == i)); + Ok(RepackPreview { items }) + } + + pub fn commit_repack_preview( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + RepackPreview { + items: crunch::PackedItems { w, h, items }, + }: RepackPreview, + ) -> Result { + let mut atlas = Atlas::new(device, queue, UVec2::new(w as u32, h as u32)); + atlas.rects = vec![crunch::Rect::default(); items.len()]; + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("repack atlas"), + }); + for crunch::PackedItem { data: p, rect } in items.into_iter() { + match p { + Packing::Img { index, mut image } => { + let bytes = crate::convert_to_rgba8_bytes( + std::mem::take(&mut image.pixels), + image.format, + image.apply_linear_transfer, + ); + queue.write_texture( + wgpu::ImageCopyTextureBase { + texture: &atlas.texture.texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: rect.x as u32, + y: rect.y as u32, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + &bytes, + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * image.width), + rows_per_image: Some(image.height), + }, + wgpu::Extent3d { + width: image.width, + height: image.height, + depth_or_array_layers: 1, + }, + ); + atlas.rects[index] = rect; + } + Packing::AtlasImg { + index, + offset_px, + size_px, + } => { + encoder.copy_texture_to_texture( + wgpu::ImageCopyTexture { + texture: &self.texture.texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: offset_px.x, + y: offset_px.y, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyTexture { + texture: &atlas.texture.texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: rect.x as u32, + y: rect.y as u32, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::Extent3d { + width: size_px.x, + height: size_px.y, + depth_or_array_layers: 1, + }, + ); + atlas.rects[index] = rect; + } + } + } + queue.submit(std::iter::once(encoder.finish())); + Ok(atlas) } /// Packs the atlas with the list of images. @@ -102,31 +388,76 @@ impl Atlas { queue: &wgpu::Queue, images: impl IntoIterator, ) -> Result { - let mut images = images.into_iter().collect::>(); + let images = images.into_iter().collect::>(); + let items = Self::pack_preview(device, images)?; + Self::commit_preview(device, queue, items) + } + + pub fn repack( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + images: impl IntoIterator, + ) -> Result { + let images = images.into_iter().collect::>(); + let items = self.repack_preview(device, images)?; + self.commit_repack_preview(device, queue, items) + } + + pub fn frames(&self) -> impl Iterator + '_ { + (0u32..).zip(self.rects.iter().copied().map(gpu_frame_from_rect)) + } + + /// Return the position and size of the frame at the given index. + pub fn get_frame(&self, index: usize) -> Option<(UVec2, UVec2)> { + self.rects.get(index).copied().map(gpu_frame_from_rect) + } + + pub fn merge( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + other: &Atlas, + ) -> Result { + let images = self + .rects + .iter() + .zip(std::iter::repeat(&self.texture)) + .chain(other.rects.iter().zip(std::iter::repeat(&other.texture))) + .collect::>(); let len = images.len(); + let size = device.limits().max_texture_dimension_1d; let crunch::PackedItems { w, h, items } = crunch::pack_into_po2( - 8192, - images.iter().enumerate().map(|(i, img)| { - let w = img.width; - let h = img.height; - crunch::Item::new(i, w as usize, h as usize, crunch::Rotation::None) + size as usize, + images.iter().enumerate().map(|(i, (rect, _))| { + let w = rect.w as usize; + let h = rect.h as usize; + crunch::Item::new(i, w, h, crunch::Rotation::None) }), ) .ok() - .context(CannotPackTexturesSnafu{len})?; + .context(CannotPackTexturesSnafu { len })?; - let mut atlas = Atlas::new(device, queue, w as u32, h as u32); + let mut atlas = Atlas::new(device, queue, UVec2::new(w as u32, h as u32)); atlas.rects = vec![crunch::Rect::default(); len]; + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("merge atlas"), + }); for crunch::PackedItem { data: i, rect } in items.into_iter() { - let img = &mut images[i]; - let bytes = crate::convert_to_rgba8_bytes( - std::mem::take(&mut img.pixels), - img.format, - img.apply_linear_transfer, - ); - queue.write_texture( - wgpu::ImageCopyTextureBase { + let (original_rect, texture) = &images[i]; + encoder.copy_texture_to_texture( + wgpu::ImageCopyTexture { + texture: &texture.texture, + mip_level: 0, + origin: wgpu::Origin3d { + x: original_rect.x as u32, + y: original_rect.y as u32, + z: 0, + }, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyTexture { texture: &atlas.texture.texture, mip_level: 0, origin: wgpu::Origin3d { @@ -136,29 +467,53 @@ impl Atlas { }, aspect: wgpu::TextureAspect::All, }, - &bytes, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: Some(4 * img.width), - rows_per_image: Some(img.height), - }, wgpu::Extent3d { - width: img.width, - height: img.height, + width: original_rect.w as u32, + height: original_rect.h as u32, depth_or_array_layers: 1, }, ); atlas.rects[i] = rect; } + queue.submit(std::iter::once(encoder.finish())); Ok(atlas) } +} - pub fn frames(&self) -> impl Iterator + '_ { - (0u32..).zip(self.rects.iter().copied().map(gpu_frame_from_rect)) +#[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() + } } - pub fn get_frame(&self, index: usize) -> Option<(UVec2, UVec2)> { - self.rects.get(index).copied().map(gpu_frame_from_rect) + #[test] + fn can_merge_atlas() { + let r = Renderling::headless(100, 100).unwrap(); + let (device, queue) = r.get_device_and_queue_owned(); + println!("{}", std::env::current_dir().unwrap().display()); + let cheetah = SceneImage::from_path("../../img/cheetah.jpg").unwrap(); + let dirt = SceneImage::from_path("../../img/dirt.jpg").unwrap(); + let happy_mac = SceneImage::from_path("../../img/happy_mac.png").unwrap(); + let sandstone = SceneImage::from_path("../../img/sandstone.png").unwrap(); + let atlas1 = Atlas::pack(&device, &queue, vec![cheetah, dirt]).unwrap(); + let atlas2 = Atlas::pack(&device, &queue, vec![happy_mac, sandstone]).unwrap(); + let atlas3 = atlas1.merge(&device, &queue, &atlas2).unwrap(); + img_diff::assert_img_eq("atlas/merge3.png", atlas3.atlas_img(&device, &queue)); } } diff --git a/crates/renderling/src/hdr.rs b/crates/renderling/src/hdr.rs index b0d833c9..f8d5fda5 100644 --- a/crates/renderling/src/hdr.rs +++ b/crates/renderling/src/hdr.rs @@ -24,6 +24,8 @@ pub struct HdrSurface { } impl HdrSurface { + pub const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; + pub fn create_texture( device: &wgpu::Device, queue: &wgpu::Queue, @@ -46,7 +48,7 @@ impl HdrSurface { ..Default::default() }) }), - wgpu::TextureFormat::Rgba16Float, + Self::TEXTURE_FORMAT, 4, // TODO: pretty sure this should be `2` 1, diff --git a/crates/renderling/src/ibl/mod.rs b/crates/renderling/src/ibl/mod.rs index 0102c635..0b8ebcc2 100644 --- a/crates/renderling/src/ibl/mod.rs +++ b/crates/renderling/src/ibl/mod.rs @@ -1,4 +1,4 @@ //! Resources for image based lighting. -pub mod prefiltered_environment; pub mod diffuse_irradiance; +pub mod prefiltered_environment; diff --git a/crates/renderling/src/linkage/skybox-fragment_cubemap.spv b/crates/renderling/src/linkage/skybox-fragment_cubemap.spv index 0642e9b6..c6945d99 100644 Binary files a/crates/renderling/src/linkage/skybox-fragment_cubemap.spv and b/crates/renderling/src/linkage/skybox-fragment_cubemap.spv differ diff --git a/crates/renderling/src/linkage/skybox-fragment_equirectangular.spv b/crates/renderling/src/linkage/skybox-fragment_equirectangular.spv index 3c64d09e..3f80ad1e 100644 Binary files a/crates/renderling/src/linkage/skybox-fragment_equirectangular.spv and b/crates/renderling/src/linkage/skybox-fragment_equirectangular.spv differ diff --git a/crates/renderling/src/linkage/skybox-slabbed_vertex.spv b/crates/renderling/src/linkage/skybox-slabbed_vertex.spv new file mode 100644 index 00000000..47ea414c Binary files /dev/null and b/crates/renderling/src/linkage/skybox-slabbed_vertex.spv differ diff --git a/crates/renderling/src/linkage/skybox-stage_skybox_cubemap.spv b/crates/renderling/src/linkage/skybox-stage_skybox_cubemap.spv new file mode 100644 index 00000000..c025bede Binary files /dev/null and b/crates/renderling/src/linkage/skybox-stage_skybox_cubemap.spv differ diff --git a/crates/renderling/src/linkage/stage-compute_cull_entities.spv b/crates/renderling/src/linkage/stage-compute_cull_entities.spv index b09f4f66..70a1934e 100644 Binary files a/crates/renderling/src/linkage/stage-compute_cull_entities.spv and b/crates/renderling/src/linkage/stage-compute_cull_entities.spv differ diff --git a/crates/renderling/src/linkage/stage-main_fragment_scene.spv b/crates/renderling/src/linkage/stage-main_fragment_scene.spv index b616dd96..74e08400 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-main_vertex_scene.spv b/crates/renderling/src/linkage/stage-main_vertex_scene.spv index 7152a723..b65d5bdf 100644 Binary files a/crates/renderling/src/linkage/stage-main_vertex_scene.spv and b/crates/renderling/src/linkage/stage-main_vertex_scene.spv differ diff --git a/crates/renderling/src/linkage/stage-new_stage_vertex.spv b/crates/renderling/src/linkage/stage-new_stage_vertex.spv index d4e22e32..d98b0bd1 100644 Binary files a/crates/renderling/src/linkage/stage-new_stage_vertex.spv and b/crates/renderling/src/linkage/stage-new_stage_vertex.spv differ diff --git a/crates/renderling/src/linkage/stage-stage_fragment.spv b/crates/renderling/src/linkage/stage-stage_fragment.spv index f201ca3d..03728627 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/linkage/tutorial-implicit_isosceles_vertex.spv b/crates/renderling/src/linkage/tutorial-implicit_isosceles_vertex.spv index aaa2cfd3..1afdac93 100644 Binary files a/crates/renderling/src/linkage/tutorial-implicit_isosceles_vertex.spv and b/crates/renderling/src/linkage/tutorial-implicit_isosceles_vertex.spv differ diff --git a/crates/renderling/src/linkage/tutorial-slabbed_render_unit.spv b/crates/renderling/src/linkage/tutorial-slabbed_render_unit.spv index a1f3008c..fe78cbc0 100644 Binary files a/crates/renderling/src/linkage/tutorial-slabbed_render_unit.spv and b/crates/renderling/src/linkage/tutorial-slabbed_render_unit.spv differ diff --git a/crates/renderling/src/linkage/tutorial-slabbed_vertices.spv b/crates/renderling/src/linkage/tutorial-slabbed_vertices.spv index 69157296..880e361f 100644 Binary files a/crates/renderling/src/linkage/tutorial-slabbed_vertices.spv and b/crates/renderling/src/linkage/tutorial-slabbed_vertices.spv differ diff --git a/crates/renderling/src/linkage/tutorial-slabbed_vertices_no_instance.spv b/crates/renderling/src/linkage/tutorial-slabbed_vertices_no_instance.spv index 72db0623..a5d9b952 100644 Binary files a/crates/renderling/src/linkage/tutorial-slabbed_vertices_no_instance.spv and b/crates/renderling/src/linkage/tutorial-slabbed_vertices_no_instance.spv differ diff --git a/crates/renderling/src/linkage/ui-vertex.spv b/crates/renderling/src/linkage/ui-vertex.spv index 304e102a..c191daa7 100644 Binary files a/crates/renderling/src/linkage/ui-vertex.spv and b/crates/renderling/src/linkage/ui-vertex.spv differ diff --git a/crates/renderling/src/renderer.rs b/crates/renderling/src/renderer.rs index 3f9d2db0..fe07437e 100644 --- a/crates/renderling/src/renderer.rs +++ b/crates/renderling/src/renderer.rs @@ -226,7 +226,9 @@ impl Renderling { #[cfg(feature = "raw-window-handle")] pub async fn from_window_async(window: &winit::window::Window) -> Self { let inner_size = window.inner_size(); - Self::try_from_raw_window(inner_size.width, inner_size.height, window).await.unwrap() + Self::try_from_raw_window(inner_size.width, inner_size.height, window) + .await + .unwrap() } #[cfg(all(feature = "raw-window-handle", not(target_arch = "wasm32")))] @@ -239,7 +241,11 @@ impl Renderling { futures_lite::future::block_on(Self::try_from_raw_window(width, height, window_handle)) } - #[cfg(all(feature = "winit", feature = "raw-window-handle", not(target_arch = "wasm32")))] + #[cfg(all( + feature = "winit", + feature = "raw-window-handle", + not(target_arch = "wasm32") + ))] pub fn try_from_window(window: &winit::window::Window) -> Result { let inner_size = window.inner_size(); Self::try_from_raw_window_handle(window, inner_size.width, inner_size.height) @@ -412,11 +418,14 @@ impl Renderling { /// Sets up the render graph with the given scenes and objects. /// /// The scenes and objects may be "visited" later, or even retrieved. - pub fn setup_render_graph( - &mut self, - config: RenderGraphConfig, - ) { - let RenderGraphConfig { scene, ui, objs, with_screen_capture, with_bloom } = config; + pub fn setup_render_graph(&mut self, config: RenderGraphConfig) { + let RenderGraphConfig { + scene, + ui, + objs, + with_screen_capture, + with_bloom, + } = config; let scene = scene.unwrap_or_else(|| self.empty_scene()); let ui = ui.unwrap_or_else(|| self.empty_ui_scene()); crate::setup_render_graph(self, scene, ui, objs, with_screen_capture, with_bloom) diff --git a/crates/renderling/src/scene/entity.rs b/crates/renderling/src/scene/entity.rs index 7f4b6476..d881171d 100644 --- a/crates/renderling/src/scene/entity.rs +++ b/crates/renderling/src/scene/entity.rs @@ -1,6 +1,9 @@ //! Entity builder. use glam::{Quat, Vec3}; -use renderling_shader::{pbr::PbrMaterial, stage::{GpuEntity, Vertex}}; +use renderling_shader::{ + pbr::PbrMaterial, + stage::{GpuEntity, Vertex}, +}; use crate::{Id, SceneBuilder}; diff --git a/crates/renderling/src/scene/gltf_support.rs b/crates/renderling/src/scene/gltf_support.rs index 75b613b7..8fc69a90 100644 --- a/crates/renderling/src/scene/gltf_support.rs +++ b/crates/renderling/src/scene/gltf_support.rs @@ -1191,7 +1191,7 @@ mod test { use glam::{Vec3, Vec4}; use renderling_shader::pbr::PbrMaterial; - use crate::{camera, Vertex, Id, LightingModel, RenderGraphConfig, Renderling}; + use crate::{camera, Id, LightingModel, RenderGraphConfig, Renderling, Vertex}; #[test] // tests importing a gltf file and rendering the first image as a 2d object @@ -1234,8 +1234,8 @@ mod test { let scene = builder.build().unwrap(); let (device, queue) = r.get_device_and_queue_owned(); let texture = - futures_lite::future::block_on(scene.textures.read_gpu(&device, &queue, 0, 1)) - .unwrap()[0]; + futures_lite::future::block_on(scene.textures.read_gpu(&device, &queue, 0, 1)).unwrap() + [0]; println!("{texture:?}"); r.setup_render_graph(RenderGraphConfig { scene: Some(scene), diff --git a/crates/renderling/src/scene/img.rs b/crates/renderling/src/scene/img.rs index b48c9e4e..12158271 100644 --- a/crates/renderling/src/scene/img.rs +++ b/crates/renderling/src/scene/img.rs @@ -121,6 +121,15 @@ impl From for SceneImage { } } +impl TryFrom for SceneImage { + type Error = SceneImageError; + + fn try_from(value: std::path::PathBuf) -> Result { + let img = image::open(value).context(ImageSnafu)?; + Ok(img.into()) + } +} + impl SceneImage { pub fn from_hdr_path(p: impl AsRef) -> Result { let bytes = std::fs::read(p.as_ref()).with_context(|_| CannotLoadSnafu { @@ -156,6 +165,10 @@ impl SceneImage { }) } + pub fn from_path(p: impl AsRef) -> Result { + Self::try_from(p.as_ref().to_path_buf()) + } + pub fn into_rgba8(self) -> Option { let pixels = convert_to_rgba8_bytes(self.pixels, self.format, self.apply_linear_transfer); image::RgbaImage::from_vec(self.width, self.height, pixels) diff --git a/crates/renderling/src/scene/light.rs b/crates/renderling/src/scene/light.rs index 4e3a6fe1..67cd82ff 100644 --- a/crates/renderling/src/scene/light.rs +++ b/crates/renderling/src/scene/light.rs @@ -1,8 +1,8 @@ //! Light builders. +use crate::Id; use glam::{Vec3, Vec4}; use renderling_shader::stage::{GpuLight, LightType}; -use crate::Id; #[cfg(feature = "gltf")] pub fn from_gltf_light_kind(kind: gltf::khr_lights_punctual::Kind) -> LightType { diff --git a/crates/renderling/src/slab.rs b/crates/renderling/src/slab.rs index f12b9bee..36767ed5 100644 --- a/crates/renderling/src/slab.rs +++ b/crates/renderling/src/slab.rs @@ -4,21 +4,46 @@ use std::{ sync::{atomic::AtomicUsize, Arc, RwLock}, }; -use renderling_shader::{ - array::Array, - id::Id, - slab::{Slab, Slabbed}, -}; +use renderling_shader::{array::Array, id::Id}; use snafu::{ResultExt, Snafu}; +pub use renderling_shader::slab::{Slab, Slabbed}; + #[derive(Debug, Snafu)] -pub enum SlabError { +pub enum SlabError { #[snafu(display( - "Out of capacity. Tried to write {}(slab size={}) \ - at {} but capacity is {capacity}", - std::any::type_name::(), T::slab_size(), id.index() + "Out of capacity. Tried to write {type_is}(slab size={slab_size}) \ + at {index} but capacity is {capacity}", ))] - Capacity { id: Id, capacity: usize }, + Capacity { + type_is: &'static str, + slab_size: usize, + index: usize, + capacity: usize, + }, + + #[snafu(display( + "Out of capacity. Tried to write an array of {elements} {type_is}\ + (each of slab size={slab_size}) \ + at {index} but capacity is {capacity}", + ))] + ArrayCapacity { + type_is: &'static str, + elements: usize, + slab_size: usize, + index: usize, + capacity: usize, + }, + + #[snafu(display( + "Array({type_is}) length mismatch. Tried to write {data_len} elements \ + into array of length {array_len}", + ))] + ArrayLen { + type_is: &'static str, + array_len: usize, + data_len: usize, + }, #[snafu(display("Async recv error: {source}"))] AsyncRecv { source: async_channel::RecvError }, @@ -82,6 +107,64 @@ impl SlabBuffer { self.capacity.load(std::sync::atomic::Ordering::Relaxed) } + fn maybe_expand_to_fit( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + len: usize, + ) { + let size = T::slab_size(); + let capacity = self.capacity(); + //log::trace!( + // "append_slice: {size} * {ts_len} + {len} ({}) >= {capacity}", + // size * ts_len + len + //); + let capacity_needed = self.len() + size * len; + if capacity_needed > capacity { + let mut new_capacity = capacity * 2; + while new_capacity < capacity_needed { + new_capacity *= 2; + } + self.resize(device, queue, new_capacity); + } + } + + /// Preallocate space for one `T` element, but don't write anything to the buffer. + /// + /// This can be used to write later with [`Self::write`]. + /// + /// NOTE: This changes the next available buffer index and may change the buffer capacity. + pub fn allocate(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> Id { + self.maybe_expand_to_fit::(device, queue, 1); + let index = self + .len + .fetch_add(T::slab_size(), std::sync::atomic::Ordering::Relaxed); + Id::from(index) + } + + /// Preallocate space for `len` `T` elements, but don't write to + /// the buffer. + /// + /// This can be used to allocate space for a bunch of elements that get written + /// later with [`Self::write_array`]. + /// + /// NOTE: This changes the length of the buffer and may change the capacity. + pub fn allocate_array( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + len: usize, + ) -> Array { + if len == 0 { + return Array::default(); + } + self.maybe_expand_to_fit::(device, queue, len); + let index = self + .len + .fetch_add(T::slab_size() * len, std::sync::atomic::Ordering::Relaxed); + Array::new(index as u32, len as u32) + } + /// Write into the slab buffer, modifying in place. /// /// NOTE: This has no effect on the length of the buffer. @@ -91,7 +174,7 @@ impl SlabBuffer { queue: &wgpu::Queue, id: Id, data: &T, - ) -> Result<(), SlabError> { + ) -> Result<(), SlabError> { let byte_offset = id.index() * std::mem::size_of::(); let size = T::slab_size(); let mut bytes = vec![0u32; size]; @@ -99,7 +182,12 @@ impl SlabBuffer { let capacity = self.capacity(); snafu::ensure!( id.index() + size <= capacity, - CapacitySnafu { id, capacity } + CapacitySnafu { + type_is: std::any::type_name::(), + slab_size: T::slab_size(), + index: id.index(), + capacity + } ); let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); @@ -113,21 +201,69 @@ impl SlabBuffer { Ok(()) } + /// Write elements into the slab buffer, modifying in place. + /// + /// NOTE: This has no effect on the length of the buffer. + /// + /// ## Errors + /// Errors if the capacity is exceeded. + pub fn write_array( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + array: Array, + data: &[T], + ) -> Result<(), SlabError> { + snafu::ensure!( + array.len() == data.len(), + ArrayLenSnafu { + type_is: std::any::type_name::(), + array_len: array.len(), + data_len: data.len() + } + ); + let capacity = self.capacity(); + let size = T::slab_size() * array.len(); + snafu::ensure!( + array.starting_index() + size <= capacity, + ArrayCapacitySnafu { + capacity, + type_is: std::any::type_name::(), + elements: array.len(), + slab_size: T::slab_size(), + index: array.at(0).index() + } + ); + let encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + let mut u32_data = vec![0u32; size]; + let _ = u32_data.write_slice(data, 0); + let byte_offset = array.starting_index() * std::mem::size_of::(); + queue.write_buffer( + // UNWRAP: if we can't lock we want to panic + &self.buffer.read().unwrap(), + byte_offset as u64, + bytemuck::cast_slice(&u32_data), + ); + queue.submit(std::iter::once(encoder.finish())); + Ok(()) + } + /// Read from the slab buffer. /// /// `T` is only for the error message. - pub async fn read_raw( + pub async fn read_raw( &self, device: &wgpu::Device, queue: &wgpu::Queue, start: usize, len: usize, - ) -> Result, SlabError> { + ) -> Result, SlabError> { let byte_offset = start * std::mem::size_of::(); let length = len * std::mem::size_of::(); let output_buffer_size = length as u64; let output_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some(&format!("SlabBuffer::read<{}>", std::any::type_name::())), + label: Some("SlabBuffer::read_raw"), size: output_buffer_size, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, mapped_at_creation: false, @@ -167,7 +303,7 @@ impl SlabBuffer { device: &wgpu::Device, queue: &wgpu::Queue, id: Id, - ) -> Result> { + ) -> Result { let vec = self .read_raw(device, queue, id.index(), T::slab_size()) .await?; @@ -176,63 +312,30 @@ impl SlabBuffer { } /// Append to the end of the buffer. - pub fn append( + pub fn append( &self, device: &wgpu::Device, queue: &wgpu::Queue, t: &T, ) -> Id { - let len = self.len(); - let capacity = self.capacity(); - if T::slab_size() + len > capacity { - self.resize(device, queue, capacity * 2); - } - let id = Id::::from(len); - // UNWRAP: We just checked that there is enough capacity, and added some if not. - self.write(device, queue, id, t).unwrap(); - self.len - .store(len + T::slab_size(), std::sync::atomic::Ordering::Relaxed); + let id = self.allocate::(device, queue); + // IGNORED: safe because we just allocated the id + let _ = self.write(device, queue, id, t); id } - /// Append a slice to the end of the buffer, returning a slab array. - pub fn append_slice( + /// Append a slice to the end of the buffer, resizing if necessary + /// and returning a slabbed array. + pub fn append_array( &self, device: &wgpu::Device, queue: &wgpu::Queue, ts: &[T], ) -> Array { - let ts_len = ts.len(); - let size = T::slab_size(); - let capacity = self.capacity(); - let len = self.len(); - //log::trace!( - // "append_slice: {size} * {ts_len} + {len} ({}) >= {capacity}", - // size * ts_len + len - //); - let capacity_needed = size * ts_len + len; - if capacity_needed >= capacity { - let mut new_capacity = capacity * 2; - while new_capacity < capacity_needed { - new_capacity *= 2; - } - self.resize(device, queue, new_capacity); - } - let starting_index = len as u32; - for (i, t) in ts.iter().enumerate() { - // UNWRAP: Safe because we just checked that there is enough capacity, - // and added some if not. - self.write( - device, - queue, - Id::::from(starting_index + (size * i) as u32), - t, - ) - .unwrap(); - } - self.len - .store(len + size * ts_len, std::sync::atomic::Ordering::Relaxed); - Array::new(starting_index, ts_len as u32) + let array = self.allocate_array::(device, queue, ts.len()); + // IGNORED: safe because we just allocated the array + let _ = self.write_array(device, queue, array, ts); + array } /// Resize the slab buffer. @@ -309,19 +412,17 @@ mod test { let b = glam::Vec3::new(1.0, 1.0, 1.0); let c = glam::Vec3::new(2.0, 2.0, 2.0); let points = vec![a, b, c]; - let array = slab.append_slice(device, queue, &points); + let array = slab.append_array(device, queue, &points); let slab_u32 = - futures_lite::future::block_on(slab.read_raw::(device, queue, 0, slab.len())) - .unwrap(); + futures_lite::future::block_on(slab.read_raw(device, queue, 0, slab.len())).unwrap(); let points_out = slab_u32.read_vec::(array); assert_eq!(points, points_out); println!("append slice 2"); let points = vec![a, a, a, a, b, b, b, c, c]; - let array = slab.append_slice(device, queue, &points); + let array = slab.append_array(device, queue, &points); let slab_u32 = - futures_lite::future::block_on(slab.read_raw::(device, queue, 0, slab.len())) - .unwrap(); + futures_lite::future::block_on(slab.read_raw(device, queue, 0, slab.len())).unwrap(); let points_out = slab_u32.read_vec::(array); assert_eq!(points, points_out); } diff --git a/crates/renderling/src/stage.rs b/crates/renderling/src/stage.rs index fce17751..58cebef2 100644 --- a/crates/renderling/src/stage.rs +++ b/crates/renderling/src/stage.rs @@ -1,145 +1,175 @@ //! Rendering objects in the scene graph. //! //! Provides a `Stage` object that can be used to render a scene graph. -use std::sync::{Arc, Mutex}; +use std::{ + ops::{Deref, DerefMut}, + sync::{atomic::AtomicBool, Arc, Mutex, RwLock}, +}; +use moongraph::{View, ViewMut}; use renderling_shader::{ array::Array, debug::DebugMode, id::Id, slab::Slabbed, - stage::{DrawIndirect, GpuLight, RenderUnit, StageLegend}, + stage::{GpuLight, RenderUnit, StageLegend}, }; -use snafu::Snafu; -use crate::{Atlas, Device, Queue, Skybox, SlabBuffer}; +use crate::{ + bloom::{BloomFilter, BloomResult}, + Atlas, DepthTexture, Device, HdrSurface, Queue, Skybox, SlabBuffer, SlabError, +}; +#[cfg(feature = "gltf")] +mod gltf_support; pub mod light; -#[derive(Debug, Snafu)] -pub enum StageError { - #[snafu(display("Out of capacity. Tried to write {:?} but capacity is {capacity}"))] - Capacity { id: Id, capacity: usize }, - - #[snafu(display("Async recv error: {source}"))] - AsyncRecv { source: async_channel::RecvError }, - - #[snafu(display("Async error: {source}"))] - Async { source: wgpu::BufferAsyncError }, -} +#[cfg(feature = "gltf")] +pub use gltf_support::*; /// Represents an entire scene worth of rendering data. /// /// A clone of a stage is a reference to the same stage. #[derive(Clone)] pub struct Stage { - pub(crate) stage_slab: SlabBuffer, - pub(crate) render_unit_slab: SlabBuffer, - pub(crate) indirect_draws: SlabBuffer, - pub(crate) atlas: Arc>, + pub(crate) slab: SlabBuffer, + pub(crate) atlas: Arc>, pub(crate) skybox: Arc>, pub(crate) pipeline: Arc>>>, - pub(crate) slab_buffers_bindgroup: Arc>>>, + pub(crate) skybox_pipeline: Arc>>>, + pub(crate) has_skybox: Arc, + pub(crate) bloom: Arc>, + pub(crate) has_bloom: Arc, + pub(crate) buffers_bindgroup: Arc>>>, pub(crate) textures_bindgroup: Arc>>>, + pub(crate) draws: Arc>, pub(crate) device: Device, pub(crate) queue: Queue, } impl Stage { /// Create a new stage. - pub fn new(device: Device, queue: Queue, legend: StageLegend) -> Self { - let mut s = Self { - stage_slab: SlabBuffer::new(&device, 256), - render_unit_slab: SlabBuffer::new(&device, 256), - indirect_draws: SlabBuffer::new_usage(&device, 256, wgpu::BufferUsages::INDIRECT), + pub fn new(device: Device, queue: Queue) -> Self { + let s = Self { + slab: SlabBuffer::new(&device, 256), pipeline: Default::default(), - atlas: Arc::new(Mutex::new(Atlas::new(&device, &queue, 1, 1))), + atlas: Arc::new(RwLock::new(Atlas::empty(&device, &queue))), skybox: Arc::new(Mutex::new(Skybox::empty(&device, &queue))), - slab_buffers_bindgroup: Default::default(), + skybox_pipeline: Default::default(), + has_skybox: Arc::new(AtomicBool::new(false)), + bloom: Arc::new(RwLock::new(BloomFilter::new(&device, &queue, 1, 1))), + has_bloom: Arc::new(AtomicBool::new(false)), + buffers_bindgroup: Default::default(), textures_bindgroup: Default::default(), + draws: Arc::new(RwLock::new(StageDrawStrategy::Direct(vec![]))), device, queue, }; - let _ = s.append(&legend); + let _ = s.append(&StageLegend::default()); s } - /// Add an object to the slab and return its ID. - pub fn append(&mut self, object: &T) -> Id { - self.stage_slab.append(&self.device, &self.queue, object) + /// Allocate some storage for a type on the slab, but don't write it. + pub fn allocate(&self) -> Id { + self.slab.allocate(&self.device, &self.queue) } - /// Add a slice of objects to the slab and return an [`Array`]. - pub fn append_slice( - &mut self, + /// Allocate contiguous storage for `len` elements of a type on the slab, but don't write them. + pub fn allocate_array(&self, len: usize) -> Array { + self.slab.allocate_array(&self.device, &self.queue, len) + } + + /// Write an object to the slab. + pub fn write(&self, id: Id, object: &T) -> Result<(), SlabError> { + let () = self.slab.write(&self.device, &self.queue, id, object)?; + Ok(()) + } + + /// Write many objects to the slab. + pub fn write_array( + &self, + array: Array, objects: &[T], - ) -> Array { - self.stage_slab - .append_slice(&self.device, &self.queue, objects) + ) -> Result<(), SlabError> { + let () = self + .slab + .write_array(&self.device, &self.queue, array, objects)?; + Ok(()) + } + + /// Add an object to the slab and return its ID. + pub fn append(&self, object: &T) -> Id { + self.slab.append(&self.device, &self.queue, object) } - ///// Add a render unit to the stage. - ///// - ///// The render unit will be added to the stage and its ID will be returned. - ///// The ID of the input render unit will be overwritten. - //pub fn add_render_unit(&mut self, mut unit: RenderUnit) -> Id { - // unit.id = Id::from(self.render_unit_slab.len()); - // self.indirect_draws.append( - // &self.device, - // &self.queue, - // &DrawIndirect { - // vertex_count: unit.vertices.len() as u32, - // instance_count: 1, - // base_vertex: 0, - // base_instance: unit.id.into(), - // }, - // ); - // self.render_unit_slab - // .append(&self.device, &self.queue, &unit) - //} + /// Add a slice of objects to the slab and return an [`Array`]. + pub fn append_array(&self, objects: &[T]) -> Array { + self.slab.append_array(&self.device, &self.queue, objects) + } /// Set the debug mode. - pub fn set_debug_mode(&mut self, debug_mode: DebugMode) { + pub fn set_debug_mode(&self, debug_mode: DebugMode) { let id = Id::::from(StageLegend::offset_of_debug_mode()); // UNWRAP: safe because the debug mode offset is guaranteed to be valid. - self.stage_slab + self.slab .write(&self.device, &self.queue, id, &debug_mode) .unwrap(); } /// Set the debug mode. - pub fn with_debug_mode(mut self, debug_mode: DebugMode) -> Self { + pub fn with_debug_mode(self, debug_mode: DebugMode) -> Self { self.set_debug_mode(debug_mode); self } /// Set whether the stage uses lighting. - pub fn set_has_lighting(&mut self, use_lighting: bool) { + pub fn set_has_lighting(&self, use_lighting: bool) { let id = Id::::from(StageLegend::offset_of_has_lighting()); // UNWRAP: safe because the has lighting offset is guaranteed to be valid. - self.stage_slab + self.slab .write(&self.device, &self.queue, id, &use_lighting) .unwrap(); } /// Set whether the stage uses lighting. - pub fn with_lighting(mut self, use_lighting: bool) -> Self { + pub fn with_lighting(self, use_lighting: bool) -> Self { self.set_has_lighting(use_lighting); self } + /// Set the skybox. + pub fn set_skybox(&self, skybox: Skybox) { + // UNWRAP: if we can't acquire the lock we want to panic. + let mut guard = self.skybox.lock().unwrap(); + *guard = skybox; + self.has_skybox + .store(true, std::sync::atomic::Ordering::Relaxed); + } + + /// Turn the bloom effect on or off. + pub fn set_has_bloom(&self, has_bloom: bool) { + self.has_bloom + .store(has_bloom, std::sync::atomic::Ordering::Relaxed); + } + + /// Turn the bloom effect on or off. + pub fn with_bloom(self, has_bloom: bool) -> Self { + self.set_has_bloom(has_bloom); + self + } + /// Create a new spot light and return its builder. - pub fn new_spot_light(&mut self) -> light::GpuSpotLightBuilder { + pub fn new_spot_light(&self) -> light::GpuSpotLightBuilder { light::GpuSpotLightBuilder::new(self) } /// Create a new directional light and return its builder. - pub fn new_directional_light(&mut self) -> light::GpuDirectionalLightBuilder { + pub fn new_directional_light(&self) -> light::GpuDirectionalLightBuilder { light::GpuDirectionalLightBuilder::new(self) } /// Create a new point light and return its builder. - pub fn new_point_light(&mut self) -> light::GpuPointLightBuilder { + pub fn new_point_light(&self) -> light::GpuPointLightBuilder { light::GpuPointLightBuilder::new(self) } @@ -147,15 +177,15 @@ impl Stage { /// /// This should be an iterator over the ids of all the lights on the stage. pub fn set_light_array( - &mut self, + &self, lights: impl IntoIterator>, ) -> Array> { let lights = lights.into_iter().collect::>(); - let light_array = self.append_slice(&lights); + let light_array = self.append_array(&lights); let id = Id::>>::from(StageLegend::offset_of_light_array()); // UNWRAP: safe because we just appended the array, and the light array offset is // guaranteed to be valid. - self.stage_slab + self.slab .write(&self.device, &self.queue, id, &light_array) .unwrap(); light_array @@ -164,54 +194,32 @@ impl Stage { /// Set the light array. /// /// This should be an iterator over the ids of all the lights on the stage. - pub fn with_light_array(mut self, lights: impl IntoIterator>) -> Self { + pub fn with_light_array(self, lights: impl IntoIterator>) -> Self { self.set_light_array(lights); self } - /// Return the render pipeline, creating it if necessary. - pub fn get_pipeline(&self) -> Arc { - fn buffers_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { - let visibility = wgpu::ShaderStages::VERTEX - | wgpu::ShaderStages::FRAGMENT - | wgpu::ShaderStages::COMPUTE; - let stage_slab = wgpu::BindGroupLayoutEntry { - binding: 0, - visibility, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }; - let unit_slab = wgpu::BindGroupLayoutEntry { - binding: 1, - visibility, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }; - let indirect_draw_slab = wgpu::BindGroupLayoutEntry { - binding: 2, - visibility, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }; - let entries = vec![stage_slab, unit_slab, indirect_draw_slab]; - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("stage slab buffers"), - entries: &entries, - }) - } + fn buffers_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { + let visibility = + wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE; + let stage_slab = wgpu::BindGroupLayoutEntry { + binding: 0, + visibility, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }; + let entries = vec![stage_slab]; + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("stage slab buffers"), + entries: &entries, + }) + } + fn textures_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { fn image2d_entry(binding: u32) -> (wgpu::BindGroupLayoutEntry, wgpu::BindGroupLayoutEntry) { let img = wgpu::BindGroupLayoutEntry { binding, @@ -252,26 +260,101 @@ impl Stage { (img, sampler) } - fn textures_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { - let (atlas, atlas_sampler) = image2d_entry(0); - let (irradiance, irradiance_sampler) = cubemap_entry(2); - let (prefilter, prefilter_sampler) = cubemap_entry(4); - let (brdf, brdf_sampler) = image2d_entry(6); - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("stage textures"), - entries: &[ - atlas, - atlas_sampler, - irradiance, - irradiance_sampler, - prefilter, - prefilter_sampler, - brdf, - brdf_sampler, - ], + let (atlas, atlas_sampler) = image2d_entry(0); + let (irradiance, irradiance_sampler) = cubemap_entry(2); + let (prefilter, prefilter_sampler) = cubemap_entry(4); + let (brdf, brdf_sampler) = image2d_entry(6); + let (environment, environment_sampler) = cubemap_entry(8); + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("stage textures"), + entries: &[ + atlas, + atlas_sampler, + irradiance, + irradiance_sampler, + prefilter, + prefilter_sampler, + brdf, + brdf_sampler, + environment, + environment_sampler, + ], + }) + } + + /// Return the skybox render pipeline, creating it if necessary. + pub fn get_skybox_pipeline(&self) -> Arc { + fn create_skybox_render_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { + log::trace!("creating stage's skybox render pipeline"); + let vertex_shader = device + .create_shader_module(wgpu::include_spirv!("linkage/skybox-slabbed_vertex.spv")); + let fragment_shader = device.create_shader_module(wgpu::include_spirv!( + "linkage/skybox-stage_skybox_cubemap.spv" + )); + let stage_slab_buffers_layout = Stage::buffers_bindgroup_layout(&device); + let textures_layout = Stage::textures_bindgroup_layout(&device); + let label = Some("stage skybox"); + let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label, + bind_group_layouts: &[&stage_slab_buffers_layout, &textures_layout], + push_constant_ranges: &[], + }); + + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("skybox pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &vertex_shader, + entry_point: "skybox::vertex", + buffers: &[], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + unclipped_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + mask: !0, + alpha_to_coverage_enabled: false, + count: 1, + }, + fragment: Some(wgpu::FragmentState { + module: &fragment_shader, + entry_point: "skybox::fragment_cubemap", + targets: &[Some(wgpu::ColorTargetState { + format: crate::hdr::HdrSurface::TEXTURE_FORMAT, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, }) } + // UNWRAP: safe because we're only ever called from the render thread. + let mut pipeline = self.skybox_pipeline.write().unwrap(); + if let Some(pipeline) = pipeline.as_ref() { + pipeline.clone() + } else { + let p = Arc::new(create_skybox_render_pipeline(&self.device)); + *pipeline = Some(p.clone()); + p + } + } + + /// Return the main render pipeline, creating it if necessary. + pub fn get_pipeline(&self) -> Arc { fn create_stage_render_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { log::trace!("creating stage render pipeline"); let label = Some("stage render pipeline"); @@ -279,8 +362,8 @@ impl Stage { .create_shader_module(wgpu::include_spirv!("linkage/stage-new_stage_vertex.spv")); let fragment_shader = device .create_shader_module(wgpu::include_spirv!("linkage/stage-stage_fragment.spv")); - let stage_slab_buffers_layout = buffers_bindgroup_layout(device); - let textures_layout = textures_bindgroup_layout(device); + let stage_slab_buffers_layout = Stage::buffers_bindgroup_layout(device); + let textures_layout = Stage::textures_bindgroup_layout(device); let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label, bind_group_layouts: &[&stage_slab_buffers_layout, &textures_layout], @@ -320,15 +403,15 @@ impl Stage { entry_point: "stage::stage_fragment", targets: &[ Some(wgpu::ColorTargetState { - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format: wgpu::TextureFormat::Rgba16Float, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + }), + Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba16Float, blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, }), - //Some(wgpu::ColorTargetState { - // format: wgpu::TextureFormat::Rgba16Float, - // blend: Some(wgpu::BlendState::ALPHA_BLENDING), - // write_mask: wgpu::ColorWrites::ALL, - //}), ], }), multiview: None, @@ -352,43 +435,29 @@ impl Stage { device: &wgpu::Device, pipeline: &wgpu::RenderPipeline, stage_slab: &SlabBuffer, - render_unit_slab: &SlabBuffer, - indirect_draws: &SlabBuffer, ) -> wgpu::BindGroup { - let label = Some("stage slab buffers"); + let label = Some("stage slab buffer"); let stage_slab_buffers_bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { label, layout: &pipeline.get_bind_group_layout(0), - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: stage_slab.get_buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: render_unit_slab.get_buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: indirect_draws.get_buffer().as_entire_binding(), - }, - ], + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: stage_slab.get_buffer().as_entire_binding(), + }], }); stage_slab_buffers_bindgroup } // UNWRAP: safe because we're only ever called from the render thread. - let mut bindgroup = self.slab_buffers_bindgroup.lock().unwrap(); + let mut bindgroup = self.buffers_bindgroup.lock().unwrap(); if let Some(bindgroup) = bindgroup.as_ref() { bindgroup.clone() } else { let b = Arc::new(create_slab_buffers_bindgroup( &self.device, &self.get_pipeline(), - &self.stage_slab, - &self.render_unit_slab, - &self.indirect_draws, + &self.slab, )); *bindgroup = Some(b.clone()); b @@ -447,6 +516,18 @@ impl Stage { binding: 7, resource: wgpu::BindingResource::Sampler(&skybox.brdf_lut.sampler), }, + wgpu::BindGroupEntry { + binding: 8, + resource: wgpu::BindingResource::TextureView( + &skybox.environment_cubemap.view, + ), + }, + wgpu::BindGroupEntry { + binding: 9, + resource: wgpu::BindingResource::Sampler( + &skybox.environment_cubemap.sampler, + ), + }, ], }); textures_bindgroup @@ -460,8 +541,8 @@ impl Stage { let b = Arc::new(create_textures_bindgroup( &self.device, &self.get_pipeline(), - // UNWRAP: we can't acquire locks we want to panic - &self.atlas.lock().unwrap(), + // UNWRAP: if we can't acquire locks we want to panic + &self.atlas.read().unwrap(), &self.skybox.lock().unwrap(), )); *bindgroup = Some(b.clone()); @@ -469,26 +550,151 @@ impl Stage { } } - pub fn number_of_indirect_draws(&self) -> u32 { - (self.indirect_draws.len() / DrawIndirect::slab_size()) as u32 + /// Append the [`RenderUnit`] and return a [`DrawUnit`] that can be used + /// to draw it. + pub fn draw_unit(&self, unit: &RenderUnit) -> Id { + let id = self.slab.append(&self.device, &self.queue, unit); + let draw = DrawUnit { + id, + vertex_count: unit.vertices.len() as u32, + }; + // UNWRAP: if we can't acquire the lock we want to panic. + let mut draws = self.draws.write().unwrap(); + match draws.deref_mut() { + StageDrawStrategy::Direct(units) => { + units.push(draw); + } + } + id + } + + /// Configure [`Renderling`] to render this stage. + pub fn configure_graph(&self, r: &mut crate::Renderling, should_copy_frame_to_post: bool) { + // set up the render graph + use crate::{ + frame::{copy_frame_to_post, create_frame, present}, + graph::{graph, Graph}, + hdr::{clear_surface_hdr_and_depth, create_hdr_render_surface, hdr_surface_update}, + scene::tonemapping, + }; + + let (hdr_surface,) = r.graph.visit(create_hdr_render_surface).unwrap().unwrap(); + r.graph.add_resource(hdr_surface); + r.graph.add_resource(self.clone()); + + // pre-render + r.graph + .add_subgraph(graph!( + create_frame, + clear_surface_hdr_and_depth, + hdr_surface_update + )) + .add_barrier(); + + // render + if should_copy_frame_to_post { + r.graph.add_subgraph(graph!( + stage_render + < tonemapping + < copy_frame_to_post + < present + )); + } else { + r.graph.add_subgraph(graph!( + stage_render + < tonemapping + < present + )); + } } +} + +/// A unit of work to be drawn. +pub(crate) struct DrawUnit { + pub id: Id, + pub vertex_count: u32, +} + +/// Provides a way to communicate with the stage about how you'd like your objects drawn. +pub(crate) enum StageDrawStrategy { + Direct(Vec), +} - pub fn number_of_render_units(&self) -> u32 { - (self.render_unit_slab.len() / RenderUnit::slab_size()) as u32 +/// Render the stage. +pub fn stage_render( + (stage, hdr_frame, depth): (ViewMut, View, View), +) -> Result<(BloomResult,), SlabError> { + let label = Some("stage render"); + let pipeline = stage.get_pipeline(); + let slab_buffers_bindgroup = stage.get_slab_buffers_bindgroup(); + let textures_bindgroup = stage.get_textures_bindgroup(); + let may_skybox_pipeline = if stage.has_skybox.load(std::sync::atomic::Ordering::Relaxed) { + Some(stage.get_skybox_pipeline()) + } else { + None + }; + let mut may_bloom_filter = if stage.has_bloom.load(std::sync::atomic::Ordering::Relaxed) { + // UNWRAP: if we can't acquire the lock we want to panic. + Some(stage.bloom.write().unwrap()) + } else { + None + }; + // UNWRAP: if we can't read we want to panic. + let draws = stage.draws.read().unwrap(); + + let mut encoder = stage + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label, + color_attachments: &hdr_frame.color_attachments(), + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &depth.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }), + stencil_ops: None, + }), + }); + render_pass.set_pipeline(&pipeline); + render_pass.set_bind_group(0, &slab_buffers_bindgroup, &[]); + render_pass.set_bind_group(1, &textures_bindgroup, &[]); + match draws.deref() { + StageDrawStrategy::Direct(units) => { + for unit in units { + render_pass.draw(0..unit.vertex_count, unit.id.inner()..unit.id.inner() + 1); + } + } //render_pass.multi_draw_indirect(&indirect_buffer, 0, stage.number_of_indirect_draws()); + } + + if let Some(pipeline) = may_skybox_pipeline.as_ref() { + render_pass.set_pipeline(pipeline); + render_pass.set_bind_group(0, &textures_bindgroup, &[]); + render_pass.draw(0..36, 0..1); + } } + stage.queue.submit(std::iter::once(encoder.finish())); + + let bloom_result = BloomResult( + may_bloom_filter + .as_mut() + .map(|bloom| bloom.run(&stage.device, &stage.queue, &hdr_frame)), + ); + Ok((bloom_result,)) } #[cfg(test)] mod test { use glam::Vec3; - use moongraph::{graph, View, ViewMut}; - use renderling_shader::{ + + use crate::{ + default_ortho2d, + shader::stage::{Camera, RenderUnit, Vertex}, slab::Slab, - stage::{Camera, RenderUnit, Vertex}, + Renderling, }; - use wgpu::util::DeviceExt; - - use crate::{default_ortho2d, frame::FrameTextureView, DepthTexture, HdrSurface, Renderling}; use super::*; @@ -506,204 +712,15 @@ mod test { ] } - #[cfg(feature = "nene")] - #[test] - fn slab_shader_sanity() { - let r = Renderling::headless(100, 100).unwrap(); - let (device, queue) = r.get_device_and_queue_owned(); - let slab = SlabBuffer::new(&device, 256); - let vertices = slab.append_slice(&device, &queue, &right_tri_vertices()); - let (projection, view) = default_ortho2d(100.0, 100.0); - let camera = slab.append( - &device, - &queue, - &Camera { - projection, - view, - ..Default::default() - }, - ); - let unit = slab.append( - &device, - &queue, - &RenderUnit { - camera, - vertices, - ..Default::default() - }, - ); - - //// Create a bindgroup for the slab so our shader can read out the types. - //let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - // label: Some("slab shader sanity"), - // entries: &[wgpu::BindGroupLayoutEntry { - // binding: 0, - // visibility: wgpu::ShaderStages::VERTEX, - // ty: wgpu::BindingType::Buffer { - // ty: wgpu::BufferBindingType::Storage { read_only: true }, - // has_dynamic_offset: false, - // min_binding_size: None, - // }, - // count: None, - // }], - //}); - //let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - // label: Some("slab shader sanity"), - // bind_group_layouts: &[&bindgroup_layout], - // push_constant_ranges: &[], - //}); - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("slab shader sanity"), - layout: None, //Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &device - .create_shader_module(wgpu::include_spirv!("linkage/stage-simple_vertex.spv")), - entry_point: "stage::simple_vertex", - buffers: &[], - }, - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: None, - unclipped_depth: false, - polygon_mode: wgpu::PolygonMode::Fill, - conservative: false, - }, - depth_stencil: Some(wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: wgpu::MultisampleState { - mask: !0, - alpha_to_coverage_enabled: false, - count: 1, - }, - fragment: Some(wgpu::FragmentState { - module: &device.create_shader_module(wgpu::include_spirv!( - "linkage/stage-simple_fragment.spv" - )), - entry_point: "stage::simple_fragment", - targets: &[Some(wgpu::ColorTargetState { - format: wgpu::TextureFormat::Rgba8UnormSrgb, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - multiview: None, - }); - - //let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { - // label: Some("slab shader sanity"), - // layout: &bindgroup_layout, - // entries: &[wgpu::BindGroupEntry { - // binding: 0, - // resource: slab.get_buffer().as_entire_binding(), - // }], - //}); - let depth = crate::texture::Texture::create_depth_texture(&device, 100, 100); - let frame = device.create_texture(&wgpu::TextureDescriptor { - label: Some("slab shader sanity"), - size: wgpu::Extent3d { - width: 100, - height: 100, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - let (frame_view, _) = crate::frame::default_frame_texture_view(&frame); - - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("slab shader sanity"), - }); - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("slab shader sanity"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &frame_view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), - store: true, - }, - })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &depth.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }), - stencil_ops: None, - }), - }); - render_pass.set_pipeline(&pipeline); - //render_pass.set_bind_group(0, &bindgroup, &[]); - render_pass.draw(0..3, 0..1); - } - queue.submit(std::iter::once(encoder.finish())); - - let buffer = crate::Texture::read(&frame, &device, &queue, 100, 100, 4, 1); - let img = buffer.into_rgba(&device).unwrap(); - img_diff::save("stage/slab_shader_sanity.png", img); - } - - fn stage_render( - (stage, frame_view, depth): (ViewMut, View, View), - ) -> Result<(), StageError> { - let label = Some("stage render"); - let pipeline = stage.get_pipeline(); - let slab_buffers_bindgroup = stage.get_slab_buffers_bindgroup(); - let textures_bindgroup = stage.get_textures_bindgroup(); - let _indirect_buffer = stage.indirect_draws.get_buffer(); - let mut encoder = stage - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &frame_view.view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &depth.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }), - stencil_ops: None, - }), - }); - render_pass.set_pipeline(&pipeline); - render_pass.set_bind_group(0, &slab_buffers_bindgroup, &[]); - render_pass.set_bind_group(1, &textures_bindgroup, &[]); - //render_pass.multi_draw_indirect(&indirect_buffer, 0, stage.number_of_indirect_draws()); - render_pass.draw(0..3, 0..1); - } - stage.queue.submit(std::iter::once(encoder.finish())); - Ok(()) - } - - #[cfg(feature = "none")] #[test] fn stage_new() { let mut r = Renderling::headless(100, 100) .unwrap() .with_background_color(glam::Vec4::splat(1.0)); let (device, queue) = r.get_device_and_queue_owned(); - let mut stage = Stage::new(device, queue, StageLegend::default()).with_lighting(true); + let stage = Stage::new(device.clone(), queue.clone()) + .with_lighting(true) + .with_bloom(true); let (projection, view) = default_ortho2d(100.0, 100.0); let camera = Camera { projection, @@ -711,76 +728,26 @@ mod test { position: Vec3::ZERO, }; let camera_id = stage.append(&camera); - let vertices = stage.append_slice(&right_tri_vertices()); + let vertices = stage.append_array(&right_tri_vertices()); println!("vertices: {vertices:?}"); - let mut unit = RenderUnit { + let _ = stage.draw_unit(&RenderUnit { camera: camera_id, vertices, ..Default::default() - }; - let unit_id = stage.add_render_unit(unit); - unit.id = unit_id; - assert_eq!(Id::new(0), unit_id); - assert_eq!(1, stage.number_of_render_units()); - assert_eq!(1, stage.number_of_indirect_draws()); - - let stage_slab = futures_lite::future::block_on(stage.stage_slab.read_raw::( + }); + let stage_slab = futures_lite::future::block_on(stage.slab.read_raw( &stage.device, &stage.queue, 0, - stage.stage_slab.len(), + stage.slab.len(), )) .unwrap(); assert_eq!(camera, stage_slab.read(camera_id)); assert_eq!(right_tri_vertices(), stage_slab.read_vec(vertices)); - let render_unit_slab = - futures_lite::future::block_on(stage.render_unit_slab.read_raw::( - &stage.device, - &stage.queue, - 0, - stage.render_unit_slab.len(), - )) - .unwrap(); - assert_eq!(unit, render_unit_slab.read(unit_id)); - let indirect_slab = futures_lite::future::block_on(stage.indirect_draws.read_raw::( - &stage.device, - &stage.queue, - 0, - stage.indirect_draws.len(), - )) - .unwrap(); - assert_eq!( - DrawIndirect { - vertex_count: 3, - instance_count: 1, - base_vertex: 0, - base_instance: 0, - }, - indirect_slab.read(Id::new(0)) - ); - - { - // set up the render graph - use crate::{ - frame::{clear_frame_and_depth, create_frame, present}, - graph::{graph, Graph}, - }; - r.graph.add_resource(stage.clone()); - - let (device, queue) = r.get_device_and_queue_owned(); - - // pre-render passes - r.graph - .add_subgraph(graph!(create_frame < clear_frame_and_depth)) - .add_barrier(); - // render passes - r.graph.add_subgraph(graph!(stage_render)).add_barrier(); - // post-render passes - let copy_frame_to_post = crate::frame::PostRenderBufferCreate::create; - r.graph.add_subgraph(graph!(copy_frame_to_post < present)); - } + + stage.configure_graph(&mut r, true); let img = r.render_image().unwrap(); - img_diff::save("stage/stage_new.png", img); + img_diff::assert_img_eq("stage/stage_cmyk_tri.png", img); } } diff --git a/crates/renderling/src/stage/gltf_support.rs b/crates/renderling/src/stage/gltf_support.rs new file mode 100644 index 00000000..4b2cf8df --- /dev/null +++ b/crates/renderling/src/stage/gltf_support.rs @@ -0,0 +1,672 @@ +//! Gltf support for the [`Stage`](crate::Stage). +use super::*; +use crate::{ + shader::{ + gltf::*, + pbr::PbrMaterial, + stage::{GpuTexture, LightingModel, TextureAddressMode, TextureModes}, + }, + SceneImage, +}; +use glam::{Quat, Vec3, Vec4}; +use snafu::{OptionExt, ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +pub enum StageGltfError { + #[snafu(display("{source}"))] + Atlas { source: crate::atlas::AtlasError }, + + #[snafu(display("Missing image at index {index} atlas offset {offset}"))] + MissingImage { offset: usize, index: usize }, + + #[snafu(display("Wrong image at index {index} atlas offset {offset}"))] + WrongImage { offset: usize, index: usize }, + + #[snafu(display("Missing texture at gltf index {index} slab index {tex_id:?}"))] + MissingTexture { + index: usize, + tex_id: Id, + }, + + #[snafu(display("Unsupported primitive mode: {:?}", mode))] + PrimitiveMode { mode: gltf::mesh::Mode }, + + #[snafu(display("No {} attribute for mesh", attribute.to_string()))] + MissingAttribute { attribute: gltf::Semantic }, + + #[snafu(display("No weights array"))] + MissingWeights, + + #[snafu(display("Missing sampler"))] + MissingSampler, + + #[snafu(display("{source}"))] + Slab { source: crate::slab::SlabError }, +} + +impl From for StageGltfError { + fn from(source: crate::slab::SlabError) -> Self { + Self::Slab { source } + } +} + +impl Stage { + pub fn load_gltf_document( + &self, + document: gltf::Document, + buffer_data: Vec, + images: Vec, + ) -> Result { + log::trace!("Loading buffers into the GPU"); + let buffers = self.allocate_array::(buffer_data.len()); + for (i, buffer) in buffer_data.iter().enumerate() { + log::trace!(" Loading buffer {i} size: {} bytes", buffer.len()); + let slice: &[u32] = bytemuck::cast_slice(&buffer); + let buffer = self.append_array(slice); + self.write(buffers.at(i), &GltfBuffer(buffer))?; + } + + log::trace!("Loading views into the GPU"); + let views = self.allocate_array(document.views().len()); + for view in document.views() { + let buffer = buffers.at(view.buffer().index()); + let offset = view.offset() as u32; + let length = view.length() as u32; + let stride = view.stride().unwrap_or_default() as u32; + self.write( + views.at(view.index()), + &GltfBufferView { + buffer, + offset, + length, + stride, + }, + )?; + } + + log::trace!("Loading accessors into the GPU"); + let accessors = document + .accessors() + .map(|accessor| { + let size = accessor.size() as u32; + let buffer_view = accessor.view().unwrap(); + let view_buffer = buffer_view.buffer(); + let buffer_index = view_buffer.index(); + let buffer = buffers.at(buffer_index); + let count = accessor.count() as u32; + let view_offset = buffer_view.offset() as u32; + let view_stride = buffer_view.stride().unwrap_or(0) as u32; + let component_type = match accessor.data_type() { + gltf::accessor::DataType::I8 => DataType::I8, + gltf::accessor::DataType::U8 => DataType::U8, + gltf::accessor::DataType::I16 => DataType::I16, + gltf::accessor::DataType::U16 => DataType::U16, + gltf::accessor::DataType::U32 => DataType::U32, + gltf::accessor::DataType::F32 => DataType::F32, + }; + let dimensions = match accessor.dimensions() { + gltf::accessor::Dimensions::Scalar => Dimensions::Scalar, + gltf::accessor::Dimensions::Vec2 => Dimensions::Vec2, + gltf::accessor::Dimensions::Vec3 => Dimensions::Vec3, + gltf::accessor::Dimensions::Vec4 => Dimensions::Vec4, + gltf::accessor::Dimensions::Mat2 => Dimensions::Mat2, + gltf::accessor::Dimensions::Mat3 => Dimensions::Mat3, + gltf::accessor::Dimensions::Mat4 => Dimensions::Mat4, + }; + let normalized = accessor.normalized(); + let accessor = GltfAccessor { + size, + count, + buffer, + view_offset, + view_stride, + component_type, + dimensions, + normalized, + }; + accessor + }) + .collect::>(); + let accessors = self.append_array(&accessors); + + log::trace!("Loading cameras into the GPU"); + let cameras = document + .cameras() + .map(|camera| match camera.projection() { + gltf::camera::Projection::Perspective(perspective) => { + let aspect_ratio = perspective.aspect_ratio().unwrap_or(1.0); + let yfov = perspective.yfov(); + let zfar = perspective.zfar().unwrap_or(f32::MAX); + let znear = perspective.znear(); + let camera = GltfCamera::Perspective { + aspect_ratio, + yfov, + zfar, + znear, + }; + camera + } + gltf::camera::Projection::Orthographic(orthographic) => { + let xmag = orthographic.xmag(); + let ymag = orthographic.ymag(); + let zfar = orthographic.zfar(); + let znear = orthographic.znear(); + let camera = GltfCamera::Orthographic { + xmag, + ymag, + zfar, + znear, + }; + camera + } + }) + .collect::>(); + let cameras = self.append_array(&cameras); + + // We need the (re)packing of the atlas before we marshal the images into the GPU + // because we need their frames for textures and materials, but we need to know + // if the materials are require us to apply a linear transfer. So we'll get the + // preview repacking first, then update the frames in the textures. + let (mut repacking, atlas_offset) = { + // UNWRAP: if we can't lock the atlas, we want to panic. + let atlas = self.atlas.read().unwrap(); + let atlas_offset = atlas.rects.len(); + ( + atlas + .repack_preview(&self.device, images.into_iter().map(SceneImage::from)) + .context(AtlasSnafu)?, + atlas_offset, + ) + }; + + log::trace!("Creating GPU textures"); + let mut gpu_textures = vec![]; + for texture in document.textures() { + let image_index = texture.source().index(); + + fn mode(mode: gltf::texture::WrappingMode) -> TextureAddressMode { + match mode { + gltf::texture::WrappingMode::ClampToEdge => TextureAddressMode::CLAMP_TO_EDGE, + gltf::texture::WrappingMode::MirroredRepeat => { + TextureAddressMode::MIRRORED_REPEAT + } + gltf::texture::WrappingMode::Repeat => TextureAddressMode::REPEAT, + } + } + + let mode_s = mode(texture.sampler().wrap_s()); + let mode_t = mode(texture.sampler().wrap_t()); + let (offset_px, size_px) = + repacking + .get_frame(image_index + atlas_offset) + .context(MissingImageSnafu { + index: image_index, + offset: atlas_offset, + })?; + gpu_textures.push(GpuTexture { + offset_px, + size_px, + modes: TextureModes::default() + .with_wrap_s(mode_s) + .with_wrap_t(mode_t), + atlas_index: (image_index + atlas_offset) as u32, + }); + } + let gpu_textures = gpu_textures; + let textures = self.append_array(&gpu_textures); + + log::trace!("Creating materials"); + let mut gpu_materials = vec![]; + for material in document.materials() { + let index = material.index(); + let name = material.name().map(String::from); + log::trace!("loading material {index:?} {name:?}"); + let pbr = material.pbr_metallic_roughness(); + let material = if material.unlit() { + log::trace!(" is unlit"); + let (albedo_texture, albedo_tex_coord) = + if let Some(info) = pbr.base_color_texture() { + let texture = info.texture(); + let index = texture.index(); + let tex_id = textures.at(index); + // The index of the image in the original gltf document + let image_index = texture.source().index(); + let image = repacking + .get_mut(atlas_offset + image_index) + .context(MissingImageSnafu { + index: image_index, + offset: atlas_offset, + })? + .as_scene_img_mut() + .context(WrongImageSnafu { + index: image_index, + offset: atlas_offset, + })?; + image.apply_linear_transfer = true; + (tex_id, info.tex_coord()) + } else { + (Id::NONE, 0) + }; + + PbrMaterial { + albedo_texture, + albedo_tex_coord, + albedo_factor: pbr.base_color_factor().into(), + ..Default::default() + } + } else { + log::trace!(" is pbr"); + let albedo_factor: Vec4 = pbr.base_color_factor().into(); + let (albedo_texture, albedo_tex_coord) = + if let Some(info) = pbr.base_color_texture() { + let texture = info.texture(); + let index = texture.index(); + let tex_id = textures.at(index); + let image_index = texture.source().index(); + let image = repacking + .get_mut(image_index + atlas_offset) + .context(MissingImageSnafu { + index: image_index, + offset: atlas_offset, + })? + .as_scene_img_mut() + .context(WrongImageSnafu { + index: image_index, + offset: atlas_offset, + })?; + image.apply_linear_transfer = true; + (tex_id, info.tex_coord()) + } else { + (Id::NONE, 0) + }; + + let ( + metallic_factor, + roughness_factor, + metallic_roughness_texture, + metallic_roughness_tex_coord, + ) = if let Some(info) = pbr.metallic_roughness_texture() { + let index = info.texture().index(); + let tex_id = textures.at(index); + (1.0, 1.0, tex_id, info.tex_coord()) + } else { + (pbr.metallic_factor(), pbr.roughness_factor(), Id::NONE, 0) + }; + + let (normal_texture, normal_tex_coord) = + if let Some(norm_tex) = material.normal_texture() { + let tex_id = textures.at(norm_tex.texture().index()); + (tex_id, norm_tex.tex_coord()) + } else { + (Id::NONE, 0) + }; + + let (ao_strength, ao_texture, ao_tex_coord) = + if let Some(occlusion_tex) = material.occlusion_texture() { + let tex_id = textures.at(occlusion_tex.texture().index()); + (occlusion_tex.strength(), tex_id, occlusion_tex.tex_coord()) + } else { + (0.0, Id::NONE, 0) + }; + + let (emissive_texture, emissive_tex_coord) = + if let Some(emissive_tex) = material.emissive_texture() { + let texture = emissive_tex.texture(); + let index = texture.index(); + let tex_id = textures.at(index); + let image_index = texture.source().index(); + let image = repacking + .get_mut(image_index + atlas_offset) + .context(MissingImageSnafu { + index: image_index, + offset: atlas_offset, + })? + .as_scene_img_mut() + .context(WrongImageSnafu { + index: image_index, + offset: atlas_offset, + })?; + image.apply_linear_transfer = true; + (tex_id, emissive_tex.tex_coord()) + } else { + (Id::NONE, 0) + }; + let emissive_factor = Vec3::from(material.emissive_factor()) + .extend(material.emissive_strength().unwrap_or(1.0)); + + PbrMaterial { + albedo_factor, + metallic_factor, + roughness_factor, + albedo_texture, + metallic_roughness_texture, + normal_texture, + ao_texture, + albedo_tex_coord, + metallic_roughness_tex_coord, + normal_tex_coord, + ao_tex_coord, + ao_strength, + emissive_factor, + emissive_texture, + emissive_tex_coord, + lighting_model: LightingModel::PBR_LIGHTING, + ..Default::default() + } + }; + gpu_materials.push(material); + } + let gpu_materials = gpu_materials; + let materials = self.append_array(&gpu_materials); + + log::trace!("Packing the atlas"); + { + // UNWRAP: if we can't lock the atlas, we want to panic. + let mut atlas = self.atlas.write().unwrap(); + let new_atlas = atlas + .commit_repack_preview(&self.device, &self.queue, repacking) + .context(AtlasSnafu)?; + *atlas = new_atlas; + } + + log::trace!("Loading meshes"); + let meshes = self.allocate_array::(document.meshes().len()); + for mesh in document.meshes() { + let primitives = self.allocate_array::(mesh.primitives().len()); + for (j, primitive) in mesh.primitives().enumerate() { + debug_assert_eq!(j, primitive.index()); + let material = primitive + .material() + .index() + .map(|i| materials.at(i)) + .unwrap_or(Id::NONE); + let indices = primitive + .indices() + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let positions = primitive + .get(&gltf::Semantic::Positions) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let normals = primitive + .get(&gltf::Semantic::Normals) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let tangents = primitive + .get(&gltf::Semantic::Tangents) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let colors = primitive + .get(&gltf::Semantic::Colors(0)) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let tex_coords0 = primitive + .get(&gltf::Semantic::TexCoords(0)) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let tex_coords1 = primitive + .get(&gltf::Semantic::TexCoords(1)) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let joints = primitive + .get(&gltf::Semantic::Joints(0)) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let weights = primitive + .get(&gltf::Semantic::Weights(0)) + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + + self.write( + primitives.at(primitive.index()), + &GltfPrimitive { + material, + indices, + positions, + normals, + tangents, + colors, + tex_coords0, + tex_coords1, + joints, + weights, + }, + )?; + } + let weights = mesh.weights().unwrap_or(&[]); + let weights = self.append_array(weights); + self.write( + meshes.at(mesh.index()), + &GltfMesh { + primitives, + weights, + }, + )?; + } + log::trace!("Loading lights"); + let lights_array = self.allocate_array::( + document + .lights() + .map(|lights| lights.len()) + .unwrap_or_default(), + ); + if let Some(lights) = document.lights() { + for light in lights { + let light_index = light.index(); + let color = Vec3::from(light.color()); + let range = light.range().unwrap_or(f32::MAX); + let intensity = light.intensity(); + let kind = match light.kind() { + gltf::khr_lights_punctual::Kind::Directional => GltfLightKind::Directional, + gltf::khr_lights_punctual::Kind::Point => GltfLightKind::Point, + gltf::khr_lights_punctual::Kind::Spot { + inner_cone_angle, + outer_cone_angle, + } => GltfLightKind::Spot { + inner_cone_angle, + outer_cone_angle, + }, + }; + self.write( + lights_array.at(light_index), + &GltfLight { + color, + range, + intensity, + kind, + }, + )?; + } + } + let lights = lights_array; + + // Preallocate nodes and skins so we can reference their ids before + // we write them to the slab. + let nodes = self.allocate_array::(document.nodes().len()); + let skins = self.allocate_array::(document.skins().len()); + + log::trace!("Loading nodes"); + for (i, node) in document.nodes().enumerate() { + let node_index = node.index(); + debug_assert_eq!(i, node_index); + let children = node + .children() + .map(|node| nodes.at(node.index())) + .collect::>(); + let children = self.append_array(&children); + let (translation, rotation, scale) = node.transform().decomposed(); + let translation = Vec3::from(translation); + let rotation = Quat::from_array(rotation); + let scale = Vec3::from(scale); + let mesh = node + .mesh() + .map(|mesh| meshes.at(mesh.index())) + .unwrap_or_default(); + let skin = node + .skin() + .map(|skin| skins.at(skin.index())) + .unwrap_or_default(); + let camera = node + .camera() + .map(|camera| cameras.at(camera.index())) + .unwrap_or_default(); + let light = node + .light() + .map(|light| lights.at(light.index())) + .unwrap_or_default(); + let weights = if let Some(weights) = node.weights() { + self.append_array(weights) + } else { + Array::default() + }; + self.write( + nodes.at(node_index), + &GltfNode { + children, + translation, + rotation, + scale, + mesh, + camera, + weights, + light, + skin, + }, + )?; + } + + log::trace!("Loading skins"); + for skin in document.skins() { + let skin_index = skin.index(); + let joints = skin + .joints() + .map(|node| nodes.at(node.index())) + .collect::>(); + let joints = self.append_array(&joints); + let inverse_bind_matrices = skin + .inverse_bind_matrices() + .map(|acc| accessors.at(acc.index())) + .unwrap_or_default(); + let skeleton = skin + .skeleton() + .map(|node| nodes.at(node.index())) + .unwrap_or_default(); + let _ = self.write( + skins.at(skin_index), + &GltfSkin { + joints, + inverse_bind_matrices, + skeleton, + }, + ); + } + + log::trace!("Loading animations"); + let animations = self.allocate_array::(document.animations().count()); + for animation in document.animations() { + let samplers = + self.allocate_array::(animation.samplers().count()); + fn create_sampler( + accessors: Array, + sampler: gltf::animation::Sampler<'_>, + ) -> GltfAnimationSampler { + let interpolation = match sampler.interpolation() { + gltf::animation::Interpolation::Linear => GltfInterpolation::Linear, + gltf::animation::Interpolation::Step => GltfInterpolation::Step, + gltf::animation::Interpolation::CubicSpline => GltfInterpolation::CubicSpline, + }; + let input = accessors.at(sampler.input().index()); + let output = accessors.at(sampler.output().index()); + GltfAnimationSampler { + interpolation, + input, + output, + } + } + let mut stored_samplers = vec![]; + for (i, sampler) in animation.samplers().enumerate() { + let sampler = create_sampler(accessors, sampler); + self.write(samplers.at(i), &sampler)?; + // Store it later so we can figure out the index of the sampler + // used by the channel. + stored_samplers.push(sampler); + } + let channels = self.allocate_array::(animation.channels().count()); + for (i, channel) in animation.channels().enumerate() { + let target = channel.target(); + let node = nodes.at(target.node().index()); + let property = match target.property() { + gltf::animation::Property::Translation => GltfTargetProperty::Translation, + gltf::animation::Property::Rotation => GltfTargetProperty::Rotation, + gltf::animation::Property::Scale => GltfTargetProperty::Scale, + gltf::animation::Property::MorphTargetWeights => { + GltfTargetProperty::MorphTargetWeights + } + }; + let target = GltfTarget { node, property }; + let sampler = create_sampler(accessors, channel.sampler()); + let index = stored_samplers + .iter() + .position(|s| s == &sampler) + .context(MissingSamplerSnafu)?; + let sampler = samplers.at(index); + self.write(channels.at(i), &GltfChannel { target, sampler })?; + } + self.write( + animations.at(animation.index()), + &GltfAnimation { channels, samplers }, + )?; + } + + log::trace!("Loading scenes"); + let scenes = self.allocate_array::(document.scenes().len()); + for scene in document.scenes() { + let nodes = scene + .nodes() + .map(|node| nodes.at(node.index())) + .collect::>(); + let nodes = self.append_array(&nodes); + self.write(scenes.at(scene.index()), &GltfScene { nodes })?; + } + + log::trace!("Done loading gltf"); + + Ok(GltfDocument { + accessors, + animations, + buffers, + cameras, + materials, + meshes, + nodes, + scenes, + skins, + textures, + views, + }) + } +} + +#[cfg(test)] +mod test { + use glam::Vec3; + + use crate::{Renderling, Stage}; + + #[test] + fn stage_normal_mapping_brick_sphere() { + crate::init_logging(); + let size = 600; + let mut r = Renderling::headless(size, size) + .unwrap() + .with_background_color(Vec3::splat(1.0).extend(1.0)); + let (device, queue) = r.get_device_and_queue_owned(); + let stage = Stage::new(device, queue); + stage.configure_graph(&mut r, true); + + log::trace!("Reading gltf"); + let (document, buffers, images) = gltf::import("../../gltf/red_brick_03_1k.glb").unwrap(); + log::trace!("Loading gltf"); + let _doc = stage.load_gltf_document(document, buffers, images).unwrap(); + } +} diff --git a/crates/renderling/src/stage/light.rs b/crates/renderling/src/stage/light.rs index 217ae0b1..5a6f57cc 100644 --- a/crates/renderling/src/stage/light.rs +++ b/crates/renderling/src/stage/light.rs @@ -1,7 +1,7 @@ //! Light builders for the stage. +use glam::{Vec3, Vec4}; use renderling_shader::id::Id; use renderling_shader::stage::{GpuLight, LightType}; -use glam::{Vec3, Vec4}; use crate::Stage; @@ -26,25 +26,22 @@ pub fn gltf_light_intensity_units(kind: gltf::khr_lights_punctual::Kind) -> &'st /// A builder for a spot light. pub struct GpuSpotLightBuilder<'a> { inner: GpuLight, - stage: &'a mut Stage, + stage: &'a Stage, } impl<'a> GpuSpotLightBuilder<'a> { - pub fn new(stage: &'a mut Stage) -> GpuSpotLightBuilder<'a> { + pub fn new(stage: &'a Stage) -> GpuSpotLightBuilder<'a> { let inner = GpuLight { light_type: LightType::SPOT_LIGHT, ..Default::default() }; let white = Vec4::splat(1.0); - Self { - inner, - stage, - } - .with_cutoff(std::f32::consts::PI / 3.0, std::f32::consts::PI / 2.0) - .with_attenuation(1.0, 0.014, 0.007) - .with_direction(Vec3::new(0.0, -1.0, 0.0)) - .with_color(white) - .with_intensity(1.0) + Self { inner, stage } + .with_cutoff(std::f32::consts::PI / 3.0, std::f32::consts::PI / 2.0) + .with_attenuation(1.0, 0.014, 0.007) + .with_direction(Vec3::new(0.0, -1.0, 0.0)) + .with_color(white) + .with_intensity(1.0) } pub fn with_position(mut self, position: impl Into) -> Self { @@ -91,22 +88,19 @@ impl<'a> GpuSpotLightBuilder<'a> { /// This is like the sun, or the moon. pub struct GpuDirectionalLightBuilder<'a> { inner: GpuLight, - stage: &'a mut Stage, + stage: &'a Stage, } impl<'a> GpuDirectionalLightBuilder<'a> { - pub fn new(stage: &'a mut Stage) -> GpuDirectionalLightBuilder<'a> { + pub fn new(stage: &'a Stage) -> GpuDirectionalLightBuilder<'a> { let inner = GpuLight { light_type: LightType::DIRECTIONAL_LIGHT, ..Default::default() }; - Self { - inner, - stage, - } - .with_direction(Vec3::new(0.0, -1.0, 0.0)) - .with_color(Vec4::splat(1.0)) - .with_intensity(1.0) + Self { inner, stage } + .with_direction(Vec3::new(0.0, -1.0, 0.0)) + .with_color(Vec4::splat(1.0)) + .with_intensity(1.0) } pub fn with_direction(mut self, direction: impl Into) -> Self { @@ -131,23 +125,20 @@ impl<'a> GpuDirectionalLightBuilder<'a> { pub struct GpuPointLightBuilder<'a> { inner: GpuLight, - stage: &'a mut Stage, + stage: &'a Stage, } impl<'a> GpuPointLightBuilder<'a> { - pub fn new(stage: &mut Stage) -> GpuPointLightBuilder<'_> { + pub fn new(stage: &Stage) -> GpuPointLightBuilder<'_> { let inner = GpuLight { light_type: LightType::POINT_LIGHT, ..Default::default() }; let white = Vec4::splat(1.0); - GpuPointLightBuilder { - stage, - inner, - } - .with_attenuation(1.0, 0.14, 0.07) - .with_color(white) - .with_intensity(1.0) + GpuPointLightBuilder { stage, inner } + .with_attenuation(1.0, 0.14, 0.07) + .with_color(white) + .with_intensity(1.0) } pub fn with_position(mut self, position: impl Into) -> Self { diff --git a/crates/renderling/src/state.rs b/crates/renderling/src/state.rs index dc2eb935..47d895b3 100644 --- a/crates/renderling/src/state.rs +++ b/crates/renderling/src/state.rs @@ -242,11 +242,9 @@ pub async fn new_adapter_device_queue_and_target<'a>( | wgpu::Features::MULTI_DRAW_INDIRECT // this one is a funny requirement, it seems it is needed if using storage buffers in // vertex shaders, even if those shaders are read-only - | wgpu::Features::VERTEX_WRITABLE_STORAGE - //| wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES - //// when debugging rust-gpu shader miscompilation it's nice to have this - //| wgpu::Features::SPIRV_SHADER_PASSTHROUGH - , + | wgpu::Features::VERTEX_WRITABLE_STORAGE, //| wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES + //// when debugging rust-gpu shader miscompilation it's nice to have this + //| wgpu::Features::SPIRV_SHADER_PASSTHROUGH limits: limits(&adapter), label: None, }, @@ -274,7 +272,7 @@ pub async fn new_adapter_device_queue_and_target<'a>( .unwrap_or(surface_caps.formats[0]) }; let view_fmts = if fmt.is_srgb() { - vec![] + vec![] } else { vec![fmt.add_srgb_suffix()] }; diff --git a/crates/renderling/src/text.rs b/crates/renderling/src/text.rs index e0a4845a..267a6ec2 100644 --- a/crates/renderling/src/text.rs +++ b/crates/renderling/src/text.rs @@ -50,7 +50,7 @@ impl Cache { mipmap_filter: wgpu::FilterMode::Nearest, ..Default::default() }), - None + None, ); Cache { texture } diff --git a/crates/renderling/src/tutorial.rs b/crates/renderling/src/tutorial.rs index e230ee5c..bb817691 100644 --- a/crates/renderling/src/tutorial.rs +++ b/crates/renderling/src/tutorial.rs @@ -8,10 +8,8 @@ mod test { frame::FrameTextureView, graph::{graph, Graph, GraphError, View}, shader::{ - self, array::Array, id::Id, - slab::{Slab, Slabbed}, stage::{Camera, RenderUnit, Vertex}, }, DepthTexture, Device, Queue, Renderling, @@ -128,7 +126,7 @@ mod test { // Create our geometry on the slab. // Don't worry too much about capacity, it can grow. let slab = crate::slab::SlabBuffer::new(&device, 16); - let vertices = slab.append_slice( + let vertices = slab.append_array( &device, &queue, &[ @@ -332,7 +330,7 @@ mod test { ..Default::default() }, ]; - let vertices = slab.append_slice(&device, &queue, &geometry); + let vertices = slab.append_array(&device, &queue, &geometry); let vertices_id = slab.append(&device, &queue, &vertices); // Create a bindgroup for the slab so our shader can read out the types. @@ -521,7 +519,7 @@ mod test { ..Default::default() }, ]; - let vertices = slab.append_slice(&device, &queue, &geometry); + let vertices = slab.append_array(&device, &queue, &geometry); let unit = RenderUnit { vertices, ..Default::default() @@ -714,7 +712,7 @@ mod test { ..Default::default() }, ]; - let vertices = slab.append_slice(&device, &queue, &geometry); + let vertices = slab.append_array(&device, &queue, &geometry); let (projection, view) = crate::default_ortho2d(100.0, 100.0); let camera_id = slab.append( &device, diff --git a/crates/sandbox/src/main.rs b/crates/sandbox/src/main.rs index d3f60f6e..e16aa78c 100644 --- a/crates/sandbox/src/main.rs +++ b/crates/sandbox/src/main.rs @@ -22,145 +22,6 @@ fn main() { //let (projection, view) = renderling::default_ortho2d(100.0, 100.0); let (device, _queue) = r.get_device_and_queue_owned(); - //let slab = SlabBuffer::new(&device, 256); - //let vertices = slab.append_slice(&device, &queue, &right_tri_vertices()); - //let (projection, view) = default_ortho2d(100.0, 100.0); - //let camera = slab.append( - // &device, - // &queue, - // &Camera { - // projection, - // view, - // ..Default::default() - // }, - //); - //let unit = slab.append( - // &device, - // &queue, - // &RenderUnit { - // camera, - // vertices, - // ..Default::default() - // }, - //); - - //// Create a bindgroup for the slab so our shader can read out the types. - //let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - // label: Some("slab shader sanity"), - // entries: &[wgpu::BindGroupLayoutEntry { - // binding: 0, - // visibility: wgpu::ShaderStages::VERTEX, - // ty: wgpu::BindingType::Buffer { - // ty: wgpu::BufferBindingType::Storage { read_only: true }, - // has_dynamic_offset: false, - // min_binding_size: None, - // }, - // count: None, - // }], - //}); - //let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - // label: Some("slab shader sanity"), - // bind_group_layouts: &[&bindgroup_layout], - // push_constant_ranges: &[], - //}); - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("slab shader sanity"), - layout: None, //Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &device.create_shader_module(wgpu::include_spirv!( - "../../renderling/src/linkage/stage-simple_vertex.spv" - )), - entry_point: "stage::simple_vertex", - buffers: &[], - }, - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: None, - unclipped_depth: false, - polygon_mode: wgpu::PolygonMode::Fill, - conservative: false, - }, - depth_stencil: Some(wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: wgpu::MultisampleState { - mask: !0, - alpha_to_coverage_enabled: false, - count: 1, - }, - fragment: Some(wgpu::FragmentState { - module: &device.create_shader_module(wgpu::include_spirv!( - "../../renderling/src/linkage/stage-simple_fragment.spv" - )), - entry_point: "stage::simple_fragment", - targets: &[Some(wgpu::ColorTargetState { - format: wgpu::TextureFormat::Bgra8UnormSrgb, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - multiview: None, - }); - - //let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { - // label: Some("slab shader sanity"), - // layout: &bindgroup_layout, - // entries: &[wgpu::BindGroupEntry { - // binding: 0, - // resource: slab.get_buffer().as_entire_binding(), - // }], - //}); - - fn render( - (device, queue, pipeline, frame, depth): ( - View, - View, - View, - View, - View, - ), - ) -> Result<(), GraphError> { - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("slab shader sanity"), - }); - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("slab shader sanity"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &frame.view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), - store: true, - }, - })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &depth.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }), - stencil_ops: None, - }), - }); - render_pass.set_pipeline(&pipeline); - //render_pass.set_bind_group(0, &bindgroup, &[]); - render_pass.draw(0..3, 0..1); - } - queue.submit(std::iter::once(encoder.finish())); - Ok(()) - } - - r.graph.add_resource(pipeline); - r.graph - .add_subgraph(graph!(create_frame < clear_frame_and_depth < render < present)); - event_loop.run(move |event, _target, control_flow| { *control_flow = winit::event_loop::ControlFlow::Poll; diff --git a/rustfmt.toml b/rustfmt.toml index fcb4fe46..e8294147 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,7 @@ -wrap_comments = true +blank_lines_upper_bound = 2 +combine_control_expr = false format_code_in_doc_comments = true -normalize_comments = true format_strings = true +max_width = 100 +imports_granularity= "crate" +wrap_comments = true \ No newline at end of file diff --git a/shaders/shader-crate/Cargo.toml b/shaders/shader-crate/Cargo.toml index 84c83e9e..5173cb9a 100644 --- a/shaders/shader-crate/Cargo.toml +++ b/shaders/shader-crate/Cargo.toml @@ -13,5 +13,5 @@ crate-type = ["dylib"] [dependencies] renderling-shader = { path = "../../crates/renderling-shader" } -spirv-std = "^0.9" +spirv-std = "0.9" glam = { version = "0.24.2", default-features = false, features = ["libm"]} diff --git a/test_img/atlas/merge3.png b/test_img/atlas/merge3.png new file mode 100644 index 00000000..f5bbce2c Binary files /dev/null and b/test_img/atlas/merge3.png differ diff --git a/test_img/srgb_pixel.png b/test_img/srgb_pixel.png index a09aa8a0..67671ce0 100644 Binary files a/test_img/srgb_pixel.png and b/test_img/srgb_pixel.png differ diff --git a/test_img/stage/stage_cmyk_tri.png b/test_img/stage/stage_cmyk_tri.png new file mode 100644 index 00000000..fdaad299 Binary files /dev/null and b/test_img/stage/stage_cmyk_tri.png differ