diff --git a/Cargo.toml b/Cargo.toml index f613c64f..3cd198ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "crates/example", + "crates/example-debugging", "crates/example-wasm", "crates/loading-bytes", "crates/renderling", @@ -16,6 +17,7 @@ resolver = "2" [workspace.dependencies] bytemuck = { version = "1.13.0", features = ["derive"] } +futures-lite = "^1.13" gltf = { git = 'https://github.com/gltf-rs/gltf.git', features = ["KHR_lights_punctual", "KHR_materials_unlit", "KHR_materials_emissive_strength", "extras"] } image = "^0.24" log = "^0.4" diff --git a/NOTES.md b/NOTES.md index 419d1e22..828e181c 100644 --- a/NOTES.md +++ b/NOTES.md @@ -16,6 +16,8 @@ Just pro-cons on tech choices and little things I don't want to forget whil impl * it's Rust - using cargo and Rust module system - expressions! + - type checking! + - editor tooling! ## cons / limititions diff --git a/crates/example/Cargo.toml b/crates/example/Cargo.toml index 0d1daf86..e471f4ff 100644 --- a/crates/example/Cargo.toml +++ b/crates/example/Cargo.toml @@ -12,7 +12,7 @@ renderling-gpui = { path = "../renderling-gpui" } anyhow = "^1.0" clap = { version = "^4.3", features = ["derive"] } env_logger = "0.10.0" -futures-lite = "^1.13" +futures-lite = {workspace=true} icosahedron = "^0.1" instant = "^0.1" loading-bytes = { path = "../loading-bytes" } diff --git a/crates/renderling-shader/src/array.rs b/crates/renderling-shader/src/array.rs index 17911185..73c57dac 100644 --- a/crates/renderling-shader/src/array.rs +++ b/crates/renderling-shader/src/array.rs @@ -34,7 +34,7 @@ impl Slabbed for Array { } fn read_slab(&mut self, index: usize, slab: &[u32]) -> usize { - if index + Self::slab_size() >= slab.len() { + if index + Self::slab_size() > slab.len() { index } else { let index = self.index.read_slab(index, slab); @@ -44,7 +44,7 @@ impl Slabbed for Array { } fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize { - if index + Self::slab_size() >= slab.len() { + if index + Self::slab_size() > slab.len() { index } else { let index = self.index.write_slab(index, slab); @@ -72,6 +72,7 @@ impl Array { _phantom: PhantomData, } } + pub fn len(&self) -> usize { self.len as usize } @@ -88,19 +89,7 @@ impl Array { if index >= self.len() { Id::NONE } else { - Id::new(self.index + index as u32) + Id::new(self.index + (T::slab_size() * index) as u32) } } } - -impl Array { - fn slab_size() -> usize { - 2 - } - - pub fn read(&self, item: &mut T, item_index: usize, slab: &[u32]) { - let size = T::slab_size(); - let start = self.index as usize + size * item_index; - let _ = item.read_slab(start, slab); - } -} diff --git a/crates/renderling-shader/src/id.rs b/crates/renderling-shader/src/id.rs index c36dff76..f7a2d5f4 100644 --- a/crates/renderling-shader/src/id.rs +++ b/crates/renderling-shader/src/id.rs @@ -69,11 +69,11 @@ impl Default for Id { } } -#[cfg(not(target_arch = "spirv"))] -impl std::fmt::Debug for Id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple(&format!("Id<{}>", std::any::type_name::())) - .field(&self.0) +impl core::fmt::Debug for Id { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Id") + .field("type", &core::any::type_name::()) + .field("index", &self.0) .finish() } } @@ -122,6 +122,11 @@ impl Id { self.0 as usize } + /// The raw u32 value of this id. + pub fn inner(&self) -> u32 { + self.0 + } + pub fn is_none(&self) -> bool { *self == Id::NONE } diff --git a/crates/renderling-shader/src/lib.rs b/crates/renderling-shader/src/lib.rs index 9285abf1..b567b566 100644 --- a/crates/renderling-shader/src/lib.rs +++ b/crates/renderling-shader/src/lib.rs @@ -4,10 +4,10 @@ use core::ops::Mul; -use glam::{Vec4Swizzles, Vec3, Quat}; -use spirv_std::num_traits::Zero; +use glam::{Quat, Vec3, Vec4Swizzles}; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::Float; +use spirv_std::num_traits::Zero; pub mod array; pub mod bits; @@ -16,10 +16,11 @@ pub mod debug; pub mod id; pub mod math; pub mod pbr; -pub mod stage; pub mod skybox; pub mod slab; +pub mod stage; pub mod tonemapping; +pub mod tutorial; pub mod ui; /// Additional methods for vector types. @@ -138,11 +139,7 @@ impl IsMatrix for glam::Mat4 { return srt_id(); } - let det_sign = if det >= 0.0 { - 1.0 - } else { - -1.0 - }; + let det_sign = if det >= 0.0 { 1.0 } else { -1.0 }; let scale = glam::Vec3::new( self.x_axis.length() * det_sign, diff --git a/crates/renderling-shader/src/slab.rs b/crates/renderling-shader/src/slab.rs index bf9a918a..b60034ac 100644 --- a/crates/renderling-shader/src/slab.rs +++ b/crates/renderling-shader/src/slab.rs @@ -357,9 +357,27 @@ impl Slabbed for PhantomData { } pub trait Slab { + /// Return the number of u32 elements in the slab. + fn len(&self) -> usize; + + /// Returns whether the slab may contain the value with the given id. + fn contains(&self, id: Id) -> bool { + id.index() + T::slab_size() <= self.len() + } + /// Read the type from the slab using the Id as the index. fn read(&self, id: Id) -> T; + #[cfg(not(target_arch = "spirv"))] + fn read_vec(&self, array: crate::array::Array) -> Vec { + let mut vec = Vec::with_capacity(array.len()); + for i in 0..array.len() { + let id = array.at(i); + vec.push(self.read(id)); + } + vec + } + /// Write the type into the slab at the index. /// /// Return the next index, or the same index if writing would overlap the slab. @@ -372,6 +390,10 @@ pub trait Slab { } impl Slab for [u32] { + fn len(&self) -> usize { + self.len() + } + fn read(&self, id: Id) -> T { let mut t = T::default(); let _ = t.read_slab(id.index(), self); @@ -391,8 +413,31 @@ impl Slab for [u32] { } } +#[cfg(not(target_arch = "spirv"))] +impl Slab for Vec { + fn len(&self) -> usize { + self.len() + } + + fn read(&self, id: Id) -> T { + self.as_slice().read(id) + } + + fn write(&mut self, t: &T, index: usize) -> usize { + self.as_mut_slice().write(t, index) + } + + fn write_slice(&mut self, t: &[T], index: usize) -> usize { + self.as_mut_slice().write_slice(t, index) + } +} + #[cfg(test)] mod test { + use glam::Vec4; + + use crate::{array::Array, stage::Vertex}; + use super::*; #[test] @@ -402,5 +447,64 @@ mod test { slab.write(&666, 1); let t = slab.read(Id::<[u32; 2]>::new(0)); assert_eq!([42, 666], t); + let t: Vec = slab.read_vec(Array::new(0, 2)); + assert_eq!([42, 666], t[..]); + slab.write_slice(&[1, 2, 3, 4], 2); + let t: Vec = slab.read_vec(Array::new(2, 4)); + assert_eq!([1, 2, 3, 4], t[..]); + slab.write_slice(&[[1.0, 2.0, 3.0, 4.0], [5.5, 6.5, 7.5, 8.5]], 0); + + let arr = Array::<[f32; 4]>::new(0, 2); + assert_eq!(Id::new(0), arr.at(0)); + assert_eq!(Id::new(4), arr.at(1)); + assert_eq!([1.0, 2.0, 3.0, 4.0], slab.read(arr.at(0))); + assert_eq!([5.5, 6.5, 7.5, 8.5], slab.read(arr.at(1))); + + let geometry = vec![ + Vertex { + position: Vec4::new(0.5, -0.5, 0.0, 1.0), + color: Vec4::new(1.0, 0.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(0.0, 0.5, 0.0, 1.0), + color: Vec4::new(0.0, 1.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-0.5, -0.5, 0.0, 1.0), + color: Vec4::new(0.0, 0.0, 1.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-1.0, 1.0, 0.0, 1.0), + color: Vec4::new(1.0, 0.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-1.0, 0.0, 0.0, 1.0), + color: Vec4::new(0.0, 1.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(0.0, 1.0, 0.0, 1.0), + color: Vec4::new(0.0, 0.0, 1.0, 1.0), + ..Default::default() + }, + ]; + let geometry_slab_size = Vertex::slab_size() * geometry.len(); + let mut slab = vec![0u32; geometry_slab_size + Array::::slab_size()]; + let index = 0usize; + let vertices = Array::::new(index as u32, geometry.len() as u32); + let index = slab.write_slice(&geometry, index); + assert_eq!(geometry_slab_size, index); + let vertices_id = Id::>::from(index); + let index = slab.write(&vertices, index); + assert_eq!(geometry_slab_size + Array::::slab_size(), index); + assert_eq!(Vertex::slab_size() * 6, vertices_id.index()); + assert!(slab.contains(vertices_id),); + + let array = slab.read(vertices_id); + assert_eq!(vertices, array); } } diff --git a/crates/renderling-shader/src/stage.rs b/crates/renderling-shader/src/stage.rs index 8f884387..c7c02815 100644 --- a/crates/renderling-shader/src/stage.rs +++ b/crates/renderling-shader/src/stage.rs @@ -1,4 +1,4 @@ -//! Types used to store and update an entire 3d scene on the GPU. +//! Types used to store and update an entire scene on the GPU. //! //! This is roughly what the [vulkan guide](https://vkguide.dev/docs/gpudriven) //! calls "gpu driven rendering". @@ -897,10 +897,8 @@ pub struct StageLegend { /// transformations. #[cfg_attr(not(target_arch = "spirv"), derive(Debug))] #[repr(C)] -#[derive(Default, Clone, Copy, PartialEq, Slabbed)] +#[derive(Clone, Copy, PartialEq, Slabbed)] pub struct RenderUnit { - // Points to an index in the `RenderUnit` slab. - pub id: Id, // Points to an array of `Vertex` in the stage's slab. pub vertices: Array, // Points to a `PbrMaterial` in the stage's slab. @@ -913,14 +911,26 @@ pub struct RenderUnit { pub scale: Vec3, } +impl Default for RenderUnit { + fn default() -> Self { + Self { + vertices: Default::default(), + material: Default::default(), + camera: Default::default(), + position: Vec3::ZERO, + rotation: Quat::IDENTITY, + scale: Vec3::ONE, + } + } +} + #[spirv(vertex)] pub fn new_stage_vertex( // Which render unit are we rendering #[spirv(instance_index)] instance_index: u32, // Which vertex within the render unit are we rendering #[spirv(vertex_index)] vertex_index: u32, - #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] unit_slab: &[u32], - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] stage_slab: &[u32], + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32], #[spirv(flat)] out_camera: &mut u32, #[spirv(flat)] out_material: &mut u32, out_color: &mut Vec4, @@ -934,8 +944,8 @@ pub fn new_stage_vertex( #[spirv(position)] gl_pos: &mut Vec4, ) { let unit_id: Id = Id::from(instance_index); - let unit = unit_slab.read(unit_id); - let vertex = stage_slab.read(unit.vertices.at(vertex_index as usize)); + let unit = slab.read(unit_id); + let vertex = slab.read(unit.vertices.at(vertex_index as usize)); let model_matrix = Mat4::from_scale_rotation_translation(unit.scale, unit.rotation, unit.position); *out_material = unit.material.into(); @@ -957,30 +967,31 @@ pub fn new_stage_vertex( *out_norm = normal_w; let view_pos = model_matrix * vertex.position.xyz().extend(1.0); *out_pos = view_pos.xyz(); - let camera = stage_slab.read(unit.camera); + let camera = slab.read(unit.camera); *out_camera = unit.camera.into(); *gl_pos = camera.projection * camera.view * view_pos; } #[allow(clippy::too_many_arguments)] +#[spirv(fragment)] /// Scene fragment shader. pub fn stage_fragment( - atlas: &Image2d, - atlas_sampler: &Sampler, + #[spirv(descriptor_set = 1, binding = 0)] atlas: &Image2d, + #[spirv(descriptor_set = 1, binding = 1)] atlas_sampler: &Sampler, - irradiance: &Cubemap, - irradiance_sampler: &Sampler, + #[spirv(descriptor_set = 1, binding = 2)] irradiance: &Cubemap, + #[spirv(descriptor_set = 1, binding = 3)] irradiance_sampler: &Sampler, - prefiltered: &Cubemap, - prefiltered_sampler: &Sampler, + #[spirv(descriptor_set = 1, binding = 4)] prefiltered: &Cubemap, + #[spirv(descriptor_set = 1, binding = 5)] prefiltered_sampler: &Sampler, - brdf: &Image2d, - brdf_sampler: &Sampler, + #[spirv(descriptor_set = 1, binding = 6)] brdf: &Image2d, + #[spirv(descriptor_set = 1, binding = 7)] brdf_sampler: &Sampler, - slab: &[u32], + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32], - in_camera: u32, - in_material: u32, + #[spirv(flat)] in_camera: u32, + #[spirv(flat)] in_material: u32, in_color: Vec4, in_uv0: Vec2, in_uv1: Vec2, diff --git a/crates/renderling-shader/src/tutorial.rs b/crates/renderling-shader/src/tutorial.rs new file mode 100644 index 00000000..5bbe64cd --- /dev/null +++ b/crates/renderling-shader/src/tutorial.rs @@ -0,0 +1,108 @@ +//! Shaders used in the intro tutorial. +use glam::{Mat4, Vec4, Vec4Swizzles}; +use spirv_std::spirv; + +use crate::{ + array::Array, + id::Id, + slab::{Slab, Slabbed}, + stage::{RenderUnit, Vertex}, +}; + +/// Simple fragment shader that writes the input color to the output color. +#[spirv(fragment)] +pub fn passthru_fragment(in_color: Vec4, output: &mut Vec4) { + *output = in_color; +} + +fn implicit_isosceles_triangle(vertex_index: u32) -> Vec4 { + let x = (1 - vertex_index as i32) as f32 * 0.5; + let y = ((vertex_index & 1) as f32 * 2.0 - 1.0) * 0.5; + Vec4::new(x, y, 0.0, 1.0) +} + +/// Simple vertex shader with an implicit isosceles triangle. +#[spirv(vertex)] +pub fn implicit_isosceles_vertex( + // Which vertex within the render unit are we rendering + #[spirv(vertex_index)] vertex_index: u32, + + //#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32], + out_color: &mut Vec4, + #[spirv(position)] clip_pos: &mut Vec4, +) { + let pos = implicit_isosceles_triangle(vertex_index); + *out_color = Vec4::new(1.0, 0.0, 0.0, 1.0); + *clip_pos = pos; +} + +/// This shader uses the vertex index as a slab [`Id`]. The [`Id`] is used to +/// read the vertex from the slab. The vertex's position and color are written +/// to the output. +#[spirv(vertex)] +pub fn slabbed_vertices_no_instance( + // Which vertex within the render unit are we rendering + #[spirv(vertex_index)] vertex_index: u32, + + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32], + + out_color: &mut Vec4, + #[spirv(position)] clip_pos: &mut Vec4, +) { + let vertex_id = Id::::from(vertex_index as usize * Vertex::slab_size()); + let vertex = slab.read(vertex_id); + *clip_pos = vertex.position; + *out_color = vertex.color; +} + +/// This shader uses the `instance_index` as a slab [`Id`]. +/// The `instance_index` is the [`Id`] of an [`Array`] of [`Vertex`]s. The +/// `vertex_index` is the index of a [`Vertex`] within the [`Array`]. +#[spirv(vertex)] +pub fn slabbed_vertices( + // Id of the array of vertices we are rendering + #[spirv(instance_index)] instance_index: u32, + // Which vertex within the render unit are we rendering + #[spirv(vertex_index)] vertex_index: u32, + + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32], + + out_color: &mut Vec4, + #[spirv(position)] clip_pos: &mut Vec4, +) { + let array_id = Id::>::from(instance_index); + let array = slab.read(array_id); + let vertex_id = array.at(vertex_index as usize); + let vertex = slab.read(vertex_id); + *clip_pos = vertex.position; + *out_color = vertex.color; +} + +/// This shader uses the `instance_index` as a slab [`Id`]. +/// The `instance_index` is the [`Id`] of a [`RenderUnit`]. +/// The [`RenderUnit`] contains an [`Array`] of [`Vertex`]s +/// as its mesh, the [`Id`]s of a [`Material`] and [`Camera`], +/// and TRS transforms. +/// The `vertex_index` is the index of a [`Vertex`] within the +/// [`RenderUnit`]'s `vertices` [`Array`]. +#[spirv(vertex)] +pub fn slabbed_render_unit( + // Id of the array of vertices we are rendering + #[spirv(instance_index)] instance_index: u32, + // Which vertex within the render unit are we rendering + #[spirv(vertex_index)] vertex_index: u32, + + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32], + + out_color: &mut Vec4, + #[spirv(position)] clip_pos: &mut Vec4, +) { + let unit_id = Id::::from(instance_index); + let unit = slab.read(unit_id); + let vertex_id = unit.vertices.at(vertex_index as usize); + let vertex = slab.read(vertex_id); + let camera = slab.read(unit.camera); + let model = Mat4::from_scale_rotation_translation(unit.scale, unit.rotation, unit.position); + *clip_pos = camera.projection * camera.view * model * vertex.position.xyz().extend(1.0); + *out_color = vertex.color; +} diff --git a/crates/renderling/Cargo.toml b/crates/renderling/Cargo.toml index dd362edf..949a06e6 100644 --- a/crates/renderling/Cargo.toml +++ b/crates/renderling/Cargo.toml @@ -17,19 +17,19 @@ text = ["ab_glyph", "glyph_brush"] wasm = ["wgpu/fragile-send-sync-non-atomic-wasm"] [dependencies] -ab_glyph = { version = "^0.2", optional = true } -any_vec = "^0.13" -async-channel = "^1.8" -crunch = "^0.5" -futures-lite = "^1.12" -glyph_brush = { version = "^0.7", optional = true } -half = "^2.3" -moongraph = { version = "^0.3.4", features = ["dot"] } -raw-window-handle = { version = "^0.5", optional = true } +ab_glyph = { version = "0.2", optional = true } +any_vec = "0.13" +async-channel = "1.8" +crunch = "0.5" +futures-lite = {workspace=true} +glyph_brush = { version = "0.7", optional = true } +half = "2.3" +moongraph = { version = "0.3.5", features = ["dot"] } +raw-window-handle = { version = "0.5", optional = true } renderling-shader = { path = "../renderling-shader" } -rustc-hash = "^1.1" +rustc-hash = "1.1" send_wrapper = "0.6" -snafu = "^0.7" +snafu = "0.7" image = { workspace = true, features = ["hdr"] } gltf = { workspace = true, optional = true } @@ -45,7 +45,7 @@ features = ["gltf", "text", "raw-window-handle", "winit"] [dev-dependencies] ctor = "0.2.2" env_logger = "0.10.0" -icosahedron = "^0.1" +icosahedron = "0.1" img-diff = { path = "../img-diff" } -naga = { version = "^0.13", features = ["spv-in", "wgsl-out", "wgsl-in", "msl-out"] } +naga = { version = "0.13", features = ["spv-in", "wgsl-out", "wgsl-in", "msl-out"] } pretty_assertions = "1.4.0" diff --git a/crates/renderling/src/frame.rs b/crates/renderling/src/frame.rs index f95ed089..d48c7d5f 100644 --- a/crates/renderling/src/frame.rs +++ b/crates/renderling/src/frame.rs @@ -1,4 +1,7 @@ //! Frame creation and clearing. +//! +//! Contains graph nodes for creating and clearing frames, as well as a +//! `PostRenderBuffer` resource that holds a copy of the last frame's buffer. use std::{ops::Deref, sync::Arc}; use moongraph::*; @@ -8,7 +11,7 @@ use crate::{ RenderTarget, ScreenSize, WgpuStateError, }; -fn default_frame_texture_view( +pub fn default_frame_texture_view( frame_texture: &wgpu::Texture, ) -> (wgpu::TextureView, wgpu::TextureFormat) { let format = frame_texture.format().add_srgb_suffix(); @@ -95,7 +98,8 @@ pub fn conduct_clear_pass( queue.submit(std::iter::once(encoder.finish())); } -/// Conduct a clear pass on the global frame and depth textures. +/// Render graph node to conduct a clear pass on the global frame and depth +/// textures. pub fn clear_frame_and_depth( (device, queue, frame_view, depth, color): ( View, @@ -160,18 +164,19 @@ pub struct PostRenderBufferCreate { frame: View, } -impl PostRenderBufferCreate { - /// Copies the current frame into a `PostRenderBuffer` resource. - /// - /// If rendering to a window surface, this should be called after rendering, - /// before presentation. - pub fn create(self) -> Result<(PostRenderBuffer,), WgpuStateError> { - let ScreenSize { width, height } = *self.size; - let copied_texture_buffer = - self.frame - .copy_to_buffer(&self.device, &self.queue, width, height); - Ok((PostRenderBuffer(copied_texture_buffer),)) - } +/// Copies the current frame into a `PostRenderBuffer` resource. +/// +/// If rendering to a window surface, this should be called after rendering, +/// before presentation. +pub fn copy_frame_to_post( + create: PostRenderBufferCreate, +) -> Result<(PostRenderBuffer,), WgpuStateError> { + let ScreenSize { width, height } = *create.size; + let copied_texture_buffer = + create + .frame + .copy_to_buffer(&create.device, &create.queue, width, height); + Ok((PostRenderBuffer(copied_texture_buffer),)) } /// Consume and present the screen frame to the screen. diff --git a/crates/renderling/src/lib.rs b/crates/renderling/src/lib.rs index d5b6abe3..6bffba7c 100644 --- a/crates/renderling/src/lib.rs +++ b/crates/renderling/src/lib.rs @@ -53,13 +53,14 @@ pub mod math; pub mod mesh; mod renderer; mod scene; -mod slab; mod skybox; +mod slab; mod stage; mod state; #[cfg(feature = "text")] mod text; mod texture; +mod tutorial; mod ui; mod uniform; @@ -69,8 +70,8 @@ pub use camera::*; pub use hdr::*; pub use renderer::*; pub use scene::*; -pub use slab::*; pub use skybox::*; +pub use slab::*; pub use stage::*; pub use state::*; #[cfg(feature = "text")] @@ -96,6 +97,10 @@ pub mod graph { pub use graph::{graph, Graph, GraphError, Move, View, ViewMut}; pub use renderling_shader::id::{Id, ID_NONE}; +pub mod shader { + //! Re-exports of [`renderling_shader`]. + pub use renderling_shader::*; +} /// Set up the render graph, including: /// * 3d scene objects @@ -173,7 +178,7 @@ pub fn setup_render_graph( scene_render < skybox_render < bloom_filter - < scene_tonemapping + < tonemapping < clear_depth < ui_scene_render )) @@ -181,7 +186,7 @@ pub fn setup_render_graph( // post-render subgraph r.graph.add_subgraph(if with_screen_capture { - let copy_frame_to_post = crate::frame::PostRenderBufferCreate::create; + use crate::frame::copy_frame_to_post; graph!(copy_frame_to_post < present) } else { graph!(present) diff --git a/crates/renderling/src/linkage/skybox-vertex.spv b/crates/renderling/src/linkage/skybox-vertex.spv index 5a049b9f..779c8041 100644 Binary files a/crates/renderling/src/linkage/skybox-vertex.spv and b/crates/renderling/src/linkage/skybox-vertex.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 b18725f9..7152a723 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 8614ec38..04e87513 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 new file mode 100644 index 00000000..bb08524f Binary files /dev/null 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 new file mode 100644 index 00000000..aaa2cfd3 Binary files /dev/null and b/crates/renderling/src/linkage/tutorial-implicit_isosceles_vertex.spv differ diff --git a/crates/renderling/src/linkage/tutorial-passthru_fragment.spv b/crates/renderling/src/linkage/tutorial-passthru_fragment.spv new file mode 100644 index 00000000..1169e630 Binary files /dev/null and b/crates/renderling/src/linkage/tutorial-passthru_fragment.spv differ diff --git a/crates/renderling/src/linkage/tutorial-slabbed_render_unit.spv b/crates/renderling/src/linkage/tutorial-slabbed_render_unit.spv new file mode 100644 index 00000000..db213e69 Binary files /dev/null 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 new file mode 100644 index 00000000..21f23995 Binary files /dev/null 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 new file mode 100644 index 00000000..b4182e97 Binary files /dev/null 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 2eb44998..304e102a 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/scene.rs b/crates/renderling/src/scene.rs index c63ee4cb..0ddcb7b5 100644 --- a/crates/renderling/src/scene.rs +++ b/crates/renderling/src/scene.rs @@ -373,6 +373,7 @@ pub struct Scene { pub indirect_draws: MutableBufferArray, pub constants: Uniform, pub skybox: Skybox, + pub environment_bindgroup: wgpu::BindGroup, pub atlas: Atlas, skybox_update: Option>, cull_bindgroup: wgpu::BindGroup, @@ -469,21 +470,21 @@ impl Scene { | wgpu::BufferUsages::COPY_SRC, wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ); - let skybox = if let Some(mut skybox) = skybox { + let skybox = if let Some(skybox) = skybox { log::trace!("scene has skybox"); - skybox.environment_bindgroup = crate::skybox::create_skybox_bindgroup( - &device, - &constants, - &skybox.environment_cubemap, - ); skybox } else if let Some(skybox_img) = skybox_image { log::trace!("scene will build a skybox"); - Skybox::new(&device, &queue, &constants, skybox_img) + Skybox::new(&device, &queue, skybox_img) } else { log::trace!("skybox is empty"); - Skybox::empty(&device, &queue, &constants) + Skybox::empty(&device, &queue) }; + let environment_bindgroup = crate::skybox::create_skybox_bindgroup( + &device, + &constants, + &skybox.environment_cubemap, + ); let cull_bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("Scene::new cull_bindgroup"), @@ -538,6 +539,7 @@ impl Scene { lights, skybox, skybox_update: None, + environment_bindgroup, }; scene.update(&device, &queue); Ok(scene) @@ -556,6 +558,7 @@ impl Scene { atlas: _, skybox, skybox_update, + environment_bindgroup, vertices, entities, materials, @@ -580,7 +583,12 @@ impl Scene { // skybox should change image log::trace!("skybox changed"); constants.toggles.set_has_skybox(true); - *skybox = Skybox::new(device, queue, constants, img); + *skybox = Skybox::new(device, queue, img); + *environment_bindgroup = crate::skybox::create_skybox_bindgroup( + device, + constants, + &skybox.environment_cubemap, + ); // we also have to create a new render buffers bindgroup because irradiance is // part of that *render_buffers_bindgroup = create_scene_buffers_bindgroup( @@ -1041,7 +1049,7 @@ pub fn skybox_render( }), }); render_pass.set_pipeline(&pipeline.0); - render_pass.set_bind_group(0, &scene.skybox.environment_bindgroup, &[]); + render_pass.set_bind_group(0, &scene.environment_bindgroup, &[]); render_pass.draw(0..36, 0..1); drop(render_pass); @@ -1108,7 +1116,7 @@ pub fn scene_render( /// Conducts the HDR tone mapping, writing the HDR surface texture to the (most /// likely) sRGB window surface. -pub fn scene_tonemapping( +pub fn tonemapping( (device, queue, frame, hdr_frame, bloom_result): ( View, View, @@ -1117,7 +1125,7 @@ pub fn scene_tonemapping( Move, ), ) -> Result<(), SceneError> { - let label = Some("scene tonemapping"); + let label = Some("tonemapping"); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label }); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label, diff --git a/crates/renderling/src/skybox.rs b/crates/renderling/src/skybox.rs index fd0ac397..6c752cb3 100644 --- a/crates/renderling/src/skybox.rs +++ b/crates/renderling/src/skybox.rs @@ -127,12 +127,12 @@ pub fn create_skybox_render_pipeline( } /// An HDR skybox that also provides IBL cubemaps and lookups. -#[derive(Debug)] +/// +/// A clone of a skybox is a reference to the same skybox. +#[derive(Debug, Clone)] pub struct Skybox { // Cubemap texture of the environment cubemap pub environment_cubemap: crate::Texture, - // Bindgroup to use with the default skybox shader - pub environment_bindgroup: wgpu::BindGroup, // Cubemap texture of the pre-computed irradiance cubemap pub irradiance_cubemap: crate::Texture, // Cubemap texture and mip maps of the specular highlights, @@ -144,11 +144,7 @@ pub struct Skybox { impl Skybox { /// Create an empty, transparent skybox. - pub fn empty( - device: &wgpu::Device, - queue: &wgpu::Queue, - constants: &Uniform, - ) -> Self { + pub fn empty(device: &wgpu::Device, queue: &wgpu::Queue) -> Self { log::trace!("creating empty skybox"); let hdr_img = SceneImage { pixels: vec![0u8; 4 * 4], @@ -157,16 +153,11 @@ impl Skybox { format: crate::SceneImageFormat::R32G32B32A32FLOAT, apply_linear_transfer: false, }; - Self::new(device, queue, constants, hdr_img) + Self::new(device, queue, hdr_img) } /// Create a new `Skybox`. - pub fn new( - device: &wgpu::Device, - queue: &wgpu::Queue, - constants: &Uniform, - hdr_img: SceneImage, - ) -> Self { + pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, hdr_img: SceneImage) -> Self { log::trace!("creating skybox"); let equirectangular_texture = Skybox::hdr_texture_from_scene_image(device, queue, hdr_img); let proj = Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, 10.0); @@ -265,11 +256,6 @@ impl Skybox { let brdf_lut = Skybox::create_precomputed_brdf_texture(device, queue); Skybox { - environment_bindgroup: crate::skybox::create_skybox_bindgroup( - &device, - &constants, - &environment_cubemap, - ), environment_cubemap, irradiance_cubemap, prefiltered_environment_cubemap, @@ -744,7 +730,8 @@ mod test { for i in 0..6 { // save out the irradiance face - let copied_buffer = scene.skybox.irradiance_cubemap.read_from( + let copied_buffer = crate::Texture::read_from( + &scene.skybox.irradiance_cubemap.texture, r.get_device(), r.get_queue(), 32, @@ -767,7 +754,8 @@ mod test { for mip_level in 0..5 { let mip_size = 128u32 >> mip_level; // save out the prefiltered environment faces' mips - let copied_buffer = scene.skybox.prefiltered_environment_cubemap.read_from( + let copied_buffer = crate::Texture::read_from( + &scene.skybox.prefiltered_environment_cubemap.texture, r.get_device(), r.get_queue(), mip_size as usize, @@ -810,7 +798,8 @@ mod test { let (device, queue) = r.get_device_and_queue_owned(); let brdf_lut = Skybox::create_precomputed_brdf_texture(&device, &queue); assert_eq!(wgpu::TextureFormat::Rg16Float, brdf_lut.texture.format()); - let copied_buffer = brdf_lut.read(&device, &queue, 512, 512, 2, 2); + let copied_buffer = + crate::Texture::read(&brdf_lut.texture, &device, &queue, 512, 512, 2, 2); let pixels = copied_buffer.pixels(&device); let pixels: Vec = bytemuck::cast_slice::(pixels.as_slice()) .iter() diff --git a/crates/renderling/src/slab.rs b/crates/renderling/src/slab.rs index 698514ae..f12b9bee 100644 --- a/crates/renderling/src/slab.rs +++ b/crates/renderling/src/slab.rs @@ -1,5 +1,8 @@ -//! CPU side of the slab storage. -use std::ops::Deref; +//! CPU side of slab storage. +use std::{ + ops::Deref, + sync::{atomic::AtomicUsize, Arc, RwLock}, +}; use renderling_shader::{ array::Array, @@ -24,48 +27,66 @@ pub enum SlabError { Async { source: wgpu::BufferAsyncError }, } -/// A slab buffer used by the stage. +/// A slab buffer used by the stage to store heterogeneous objects. +/// +/// A clone of a buffer is a reference to the same buffer. +#[derive(Clone)] pub struct SlabBuffer { - buffer: wgpu::Buffer, + pub(crate) buffer: Arc>, // The number of u32 elements currently stored in the buffer. // // This is the next index to write into. - len: usize, + len: Arc, // The total number of u32 elements that can be stored in the buffer. - capacity: usize, + capacity: Arc, } impl SlabBuffer { - pub fn new(device: &wgpu::Device, capacity: usize) -> Self { - let buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("stage buffer"), + fn new_buffer( + device: &wgpu::Device, + capacity: usize, + usage: wgpu::BufferUsages, + ) -> wgpu::Buffer { + device.create_buffer(&wgpu::BufferDescriptor { + label: Some("SlabBuffer"), size: (capacity * std::mem::size_of::()) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST - | wgpu::BufferUsages::COPY_SRC, + | wgpu::BufferUsages::COPY_SRC + | usage, mapped_at_creation: false, - }); + }) + } + /// Create a new slab buffer with a capacity of `capacity` u32 elements. + pub fn new(device: &wgpu::Device, capacity: usize) -> Self { + Self::new_usage(device, capacity, wgpu::BufferUsages::empty()) + } + + /// Create a new slab buffer with a capacity of `capacity` u32 elements. + pub fn new_usage(device: &wgpu::Device, capacity: usize, usage: wgpu::BufferUsages) -> Self { Self { - buffer, - len: 0, - capacity, + buffer: RwLock::new(Self::new_buffer(device, capacity, usage)).into(), + len: AtomicUsize::new(0).into(), + capacity: AtomicUsize::new(capacity).into(), } } + /// The number of u32 elements currently stored in the buffer. pub fn len(&self) -> usize { - self.len + self.len.load(std::sync::atomic::Ordering::Relaxed) } + /// The total number of u32 elements that can be stored in the buffer. pub fn capacity(&self) -> usize { - self.capacity + self.capacity.load(std::sync::atomic::Ordering::Relaxed) } /// Write into the slab buffer, modifying in place. /// /// NOTE: This has no effect on the length of the buffer. pub fn write( - &mut self, + &self, device: &wgpu::Device, queue: &wgpu::Queue, id: Id, @@ -75,17 +96,16 @@ impl SlabBuffer { let size = T::slab_size(); let mut bytes = vec![0u32; size]; let _ = bytes.write(data, 0); + let capacity = self.capacity(); snafu::ensure!( - id.index() + size <= self.capacity, - CapacitySnafu { - id, - capacity: self.capacity - } + id.index() + size <= capacity, + CapacitySnafu { id, capacity } ); let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); queue.write_buffer( - &self.buffer, + // UNWRAP: if we can't lock we want to panic + &self.buffer.read().unwrap(), byte_offset as u64, bytemuck::cast_slice(bytes.as_slice()), ); @@ -93,14 +113,18 @@ impl SlabBuffer { Ok(()) } - pub async fn read( + /// Read from the slab buffer. + /// + /// `T` is only for the error message. + pub async fn read_raw( &self, device: &wgpu::Device, queue: &wgpu::Queue, - id: Id, - ) -> Result> { - let byte_offset = id.index() * std::mem::size_of::(); - let length = T::slab_size() * std::mem::size_of::(); + start: usize, + len: usize, + ) -> 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::())), @@ -116,7 +140,8 @@ impl SlabBuffer { output_buffer_size:{output_buffer_size}", ); encoder.copy_buffer_to_buffer( - &self.buffer, + // UNWRAP: if we can't lock we want to panic + &self.buffer.read().unwrap(), byte_offset as u64, &output_buffer, 0, @@ -133,70 +158,115 @@ impl SlabBuffer { .context(AsyncRecvSnafu)? .context(AsyncSnafu)?; let bytes = buffer_slice.get_mapped_range(); - let t = Slab::read(bytemuck::cast_slice(bytes.deref()), Id::::new(0)); + Ok(bytemuck::cast_slice(bytes.deref()).to_vec()) + } + + /// Read from the slab buffer. + pub async fn read( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + id: Id, + ) -> Result> { + let vec = self + .read_raw(device, queue, id.index(), T::slab_size()) + .await?; + let t = Slab::read(vec.as_slice(), Id::::new(0)); Ok(t) } /// Append to the end of the buffer. pub fn append( - &mut self, + &self, device: &wgpu::Device, queue: &wgpu::Queue, t: &T, ) -> Id { - if T::slab_size() + self.len > self.capacity { - self.resize(device, queue, self.capacity * 2); + let len = self.len(); + let capacity = self.capacity(); + if T::slab_size() + len > capacity { + self.resize(device, queue, capacity * 2); } - let id = Id::::from(self.len); + 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 += T::slab_size(); + self.len + .store(len + T::slab_size(), std::sync::atomic::Ordering::Relaxed); id } /// Append a slice to the end of the buffer, returning a slab array. pub fn append_slice( - &mut self, + &self, device: &wgpu::Device, queue: &wgpu::Queue, ts: &[T], ) -> Array { - let len = ts.len(); + let ts_len = ts.len(); let size = T::slab_size(); - if size * len + self.len > self.capacity { - self.resize(device, queue, self.capacity * 2); + 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 index = self.len as u32; - for t in ts.iter() { + 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(self.len), t) - .unwrap(); + self.write( + device, + queue, + Id::::from(starting_index + (size * i) as u32), + t, + ) + .unwrap(); } - self.len += size * len; - Array::new(index, len as u32) + self.len + .store(len + size * ts_len, std::sync::atomic::Ordering::Relaxed); + Array::new(starting_index, ts_len as u32) } /// Resize the slab buffer. /// /// This creates a new buffer and writes the data from the old into the new. - pub fn resize(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, capacity: usize) { - if capacity > self.capacity { - let new_buffer = Self::new(device, capacity); + pub fn resize(&self, device: &wgpu::Device, queue: &wgpu::Queue, new_capacity: usize) { + let capacity = self.capacity(); + if new_capacity > capacity { + log::trace!("resizing buffer from {capacity} to {new_capacity}"); + let len = self.len(); + let mut buffer = self.buffer.write().unwrap(); + let new_buffer = Self::new_buffer(device, new_capacity, buffer.usage()); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + // UNWRAP: if we can't lock we want to panic encoder.copy_buffer_to_buffer( - &self.buffer, + &buffer, 0, - &new_buffer.buffer, + &new_buffer, 0, - (self.len * std::mem::size_of::()) as u64, + (len * std::mem::size_of::()) as u64, ); queue.submit(std::iter::once(encoder.finish())); - self.buffer = new_buffer.buffer; - self.capacity = capacity; + *buffer = new_buffer; + self.capacity + .store(new_capacity, std::sync::atomic::Ordering::Relaxed); } } + + /// Get the underlying buffer. + pub fn get_buffer(&self) -> impl Deref + '_ { + // UNWRAP: if we can't lock we want to panic + self.buffer.read().unwrap() + } } #[cfg(test)] @@ -207,26 +277,52 @@ mod test { #[test] fn slab_buffer_roundtrip() { + println!("write"); let _ = env_logger::builder().is_test(true).try_init(); let r = Renderling::headless(10, 10).unwrap(); let device = r.get_device(); let queue = r.get_queue(); - let mut slab = SlabBuffer::new(device, 2); + let slab = SlabBuffer::new(device, 2); slab.append(device, queue, &42); slab.append(device, queue, &1); let id = Id::<[u32; 2]>::new(0); let t = futures_lite::future::block_on(slab.read(device, queue, id)).unwrap(); assert_eq!([42, 1], t, "read back what we wrote"); + + println!("overflow"); let id = Id::::new(2); let err = slab.write(device, queue, id, &666).unwrap_err(); assert_eq!( "Out of capacity. Tried to write u32(slab size=1) at 2 but capacity is 2", err.to_string() ); - assert_eq!(2, slab.len); + assert_eq!(2, slab.len()); + + println!("append"); slab.append(device, queue, &666); let id = Id::<[u32; 3]>::new(0); let t = futures_lite::future::block_on(slab.read(device, queue, id)).unwrap(); assert_eq!([42, 1, 666], t); + + println!("append slice"); + let a = glam::Vec3::new(0.0, 0.0, 0.0); + 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 slab_u32 = + 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 slab_u32 = + 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 2bf080aa..fce17751 100644 --- a/crates/renderling/src/stage.rs +++ b/crates/renderling/src/stage.rs @@ -1,16 +1,18 @@ //! 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 renderling_shader::{ array::Array, debug::DebugMode, id::Id, slab::Slabbed, - stage::{GpuLight, StageLegend}, + stage::{DrawIndirect, GpuLight, RenderUnit, StageLegend}, }; use snafu::Snafu; -use crate::{Atlas, Device, Queue, SlabBuffer}; +use crate::{Atlas, Device, Queue, Skybox, SlabBuffer}; pub mod light; @@ -27,21 +29,34 @@ pub enum StageError { } /// 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) atlas: Atlas, + pub(crate) indirect_draws: SlabBuffer, + pub(crate) atlas: Arc>, + pub(crate) skybox: Arc>, + pub(crate) pipeline: Arc>>>, + pub(crate) slab_buffers_bindgroup: Arc>>>, + pub(crate) textures_bindgroup: Arc>>>, pub(crate) device: Device, pub(crate) queue: Queue, } impl Stage { - /// Create a new stage slab with `capacity`, which is + /// 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), - //atlas: Atlas::new(&device, &queue, 0, 0), + indirect_draws: SlabBuffer::new_usage(&device, 256, wgpu::BufferUsages::INDIRECT), + pipeline: Default::default(), + atlas: Arc::new(Mutex::new(Atlas::new(&device, &queue, 1, 1))), + skybox: Arc::new(Mutex::new(Skybox::empty(&device, &queue))), + slab_buffers_bindgroup: Default::default(), + textures_bindgroup: Default::default(), device, queue, }; @@ -63,6 +78,26 @@ impl Stage { .append_slice(&self.device, &self.queue, objects) } + ///// 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) + //} + /// Set the debug mode. pub fn set_debug_mode(&mut self, debug_mode: DebugMode) { let id = Id::::from(StageLegend::offset_of_debug_mode()); @@ -133,18 +168,619 @@ impl Stage { 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 image2d_entry(binding: u32) -> (wgpu::BindGroupLayoutEntry, wgpu::BindGroupLayoutEntry) { + let img = wgpu::BindGroupLayoutEntry { + binding, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }; + let sampler = wgpu::BindGroupLayoutEntry { + binding: binding + 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }; + (img, sampler) + } + + fn cubemap_entry(binding: u32) -> (wgpu::BindGroupLayoutEntry, wgpu::BindGroupLayoutEntry) { + let img = wgpu::BindGroupLayoutEntry { + binding, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::Cube, + multisampled: false, + }, + count: None, + }; + let sampler = wgpu::BindGroupLayoutEntry { + binding: binding + 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }; + (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, + ], + }) + } + + fn create_stage_render_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { + log::trace!("creating stage render pipeline"); + let label = Some("stage render pipeline"); + let vertex_shader = device + .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 layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label, + bind_group_layouts: &[&stage_slab_buffers_layout, &textures_layout], + push_constant_ranges: &[], + }); + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label, + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &vertex_shader, + entry_point: "stage::new_stage_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: &fragment_shader, + entry_point: "stage::stage_fragment", + targets: &[ + Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8UnormSrgb, + 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, + }); + pipeline + } + + // UNWRAP: safe because we're only ever called from the render thread. + let mut pipeline = self.pipeline.lock().unwrap(); + if let Some(pipeline) = pipeline.as_ref() { + pipeline.clone() + } else { + let p = Arc::new(create_stage_render_pipeline(&self.device)); + *pipeline = Some(p.clone()); + p + } + } + + pub fn get_slab_buffers_bindgroup(&self) -> Arc { + fn create_slab_buffers_bindgroup( + 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 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(), + }, + ], + }); + 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(); + 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, + )); + *bindgroup = Some(b.clone()); + b + } + } + + pub fn get_textures_bindgroup(&self) -> Arc { + fn create_textures_bindgroup( + device: &wgpu::Device, + pipeline: &wgpu::RenderPipeline, + atlas: &Atlas, + skybox: &Skybox, + ) -> wgpu::BindGroup { + let label = Some("stage textures"); + let textures_bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor { + label, + layout: &pipeline.get_bind_group_layout(1), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&atlas.texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&atlas.texture.sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView( + &skybox.irradiance_cubemap.view, + ), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Sampler( + &skybox.irradiance_cubemap.sampler, + ), + }, + wgpu::BindGroupEntry { + binding: 4, + resource: wgpu::BindingResource::TextureView( + &skybox.prefiltered_environment_cubemap.view, + ), + }, + wgpu::BindGroupEntry { + binding: 5, + resource: wgpu::BindingResource::Sampler( + &skybox.prefiltered_environment_cubemap.sampler, + ), + }, + wgpu::BindGroupEntry { + binding: 6, + resource: wgpu::BindingResource::TextureView(&skybox.brdf_lut.view), + }, + wgpu::BindGroupEntry { + binding: 7, + resource: wgpu::BindingResource::Sampler(&skybox.brdf_lut.sampler), + }, + ], + }); + textures_bindgroup + } + + // UNWRAP: safe because we're only ever called from the render thread. + let mut bindgroup = self.textures_bindgroup.lock().unwrap(); + if let Some(bindgroup) = bindgroup.as_ref() { + bindgroup.clone() + } else { + 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(), + &self.skybox.lock().unwrap(), + )); + *bindgroup = Some(b.clone()); + b + } + } + + pub fn number_of_indirect_draws(&self) -> u32 { + (self.indirect_draws.len() / DrawIndirect::slab_size()) as u32 + } + + pub fn number_of_render_units(&self) -> u32 { + (self.render_unit_slab.len() / RenderUnit::slab_size()) as u32 + } } #[cfg(test)] mod test { - use crate::Renderling; + use glam::Vec3; + use moongraph::{graph, View, ViewMut}; + use renderling_shader::{ + slab::Slab, + stage::{Camera, RenderUnit, Vertex}, + }; + use wgpu::util::DeviceExt; + + use crate::{default_ortho2d, frame::FrameTextureView, DepthTexture, HdrSurface, Renderling}; use super::*; + fn right_tri_vertices() -> Vec { + vec![ + Vertex::default() + .with_position([0.0, 0.0, 0.5]) + .with_color([0.0, 1.0, 1.0, 1.0]), + Vertex::default() + .with_position([0.0, 100.0, 0.5]) + .with_color([1.0, 1.0, 0.0, 1.0]), + Vertex::default() + .with_position([100.0, 0.0, 0.5]) + .with_color([1.0, 0.0, 1.0, 1.0]), + ] + } + + #[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 r = Renderling::headless(10, 10).unwrap(); + 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 _stage = Stage::new(device, queue, StageLegend::default()); + let mut stage = Stage::new(device, queue, StageLegend::default()).with_lighting(true); + let (projection, view) = default_ortho2d(100.0, 100.0); + let camera = Camera { + projection, + view, + position: Vec3::ZERO, + }; + let camera_id = stage.append(&camera); + let vertices = stage.append_slice(&right_tri_vertices()); + println!("vertices: {vertices:?}"); + let mut 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::( + &stage.device, + &stage.queue, + 0, + stage.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)); + } + + let img = r.render_image().unwrap(); + img_diff::save("stage/stage_new.png", img); } } diff --git a/crates/renderling/src/texture.rs b/crates/renderling/src/texture.rs index 9b0a1427..db018d1c 100644 --- a/crates/renderling/src/texture.rs +++ b/crates/renderling/src/texture.rs @@ -470,7 +470,7 @@ impl Texture { /// color/alpha channels and the number of bytes in the underlying /// subpixel type (usually u8=1, u16=2 or f32=4). pub fn read( - &self, + texture: &wgpu::Texture, device: &wgpu::Device, queue: &wgpu::Queue, width: usize, @@ -478,7 +478,8 @@ impl Texture { channels: usize, subpixel_bytes: usize, ) -> CopiedTextureBuffer { - self.read_from( + Self::read_from( + texture, device, queue, width, @@ -496,7 +497,7 @@ impl Texture { /// color/alpha channels and the number of bytes in the underlying /// subpixel type (usually u8=1, u16=2 or f32=4). pub fn read_from( - &self, + texture: &wgpu::Texture, device: &wgpu::Device, queue: &wgpu::Queue, width: usize, @@ -517,7 +518,7 @@ impl Texture { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("post render screen capture encoder"), }); - let mut source = self.texture.as_image_copy(); + let mut source = texture.as_image_copy(); source.mip_level = mip_level; if let Some(origin) = origin { source.origin = origin; @@ -544,7 +545,7 @@ impl Texture { CopiedTextureBuffer { dimensions, buffer, - format: self.texture.format(), + format: texture.format(), } } @@ -876,7 +877,8 @@ mod test { let mip_width = width >> mip_level; let mip_height = height >> mip_level; // save out the mips - let copied_buffer = mip.read_from( + let copied_buffer = crate::Texture::read_from( + &mip.texture, r.get_device(), r.get_queue(), mip_width as usize, diff --git a/crates/renderling/src/tutorial.rs b/crates/renderling/src/tutorial.rs new file mode 100644 index 00000000..e230ee5c --- /dev/null +++ b/crates/renderling/src/tutorial.rs @@ -0,0 +1,882 @@ +//! A tutorial module for the renderling crate. + +#[cfg(test)] +mod test { + use glam::{Vec3, Vec4}; + + use crate::{ + frame::FrameTextureView, + graph::{graph, Graph, GraphError, View}, + shader::{ + self, + array::Array, + id::Id, + slab::{Slab, Slabbed}, + stage::{Camera, RenderUnit, Vertex}, + }, + DepthTexture, Device, Queue, Renderling, + }; + + #[test] + fn implicit_isosceles_triangle() { + let mut r = Renderling::headless(100, 100).unwrap(); + let (device, _queue) = r.get_device_and_queue_owned(); + let label = Some("implicit isosceles triangle"); + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label, + layout: None, + vertex: wgpu::VertexState { + module: &device.create_shader_module(wgpu::include_spirv!( + "linkage/tutorial-implicit_isosceles_vertex.spv" + )), + entry_point: "tutorial::implicit_isosceles_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/tutorial-passthru_fragment.spv" + )), + entry_point: "tutorial::passthru_fragment", + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8UnormSrgb, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + }); + + fn render( + (device, queue, pipeline, frame, depth): ( + View, + View, + View, + View, + View, + ), + ) -> Result<(), GraphError> { + let label = Some("implicit isosceles triangle"); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label }); + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label, + 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.draw(0..3, 0..1); + } + queue.submit(std::iter::once(encoder.finish())); + Ok(()) + } + + use crate::frame::{clear_frame_and_depth, copy_frame_to_post, create_frame, present}; + r.graph.add_resource(pipeline); + r.graph.add_subgraph(graph!( + create_frame + < clear_frame_and_depth + < render + < copy_frame_to_post + < present + )); + + let img = r.render_image().unwrap(); + img_diff::assert_img_eq("tutorial/implicit_isosceles_triangle.png", img); + } + + #[test] + fn slabbed_isosceles_triangle_no_instance() { + let mut r = Renderling::headless(100, 100).unwrap(); + let (device, queue) = r.get_device_and_queue_owned(); + + // 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( + &device, + &queue, + &[ + Vertex { + position: Vec4::new(0.5, -0.5, 0.0, 1.0), + color: Vec4::new(1.0, 0.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(0.0, 0.5, 0.0, 1.0), + color: Vec4::new(0.0, 1.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-0.5, -0.5, 0.0, 1.0), + color: Vec4::new(0.0, 0.0, 1.0, 1.0), + ..Default::default() + }, + ], + ); + assert_eq!(3, vertices.len()); + + // Create a bindgroup for the slab so our shader can read out the types. + let label = Some("slabbed isosceles triangle"); + let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label, + 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, + bind_group_layouts: &[&bindgroup_layout], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &device.create_shader_module(wgpu::include_spirv!( + "linkage/tutorial-slabbed_vertices_no_instance.spv" + )), + entry_point: "tutorial::slabbed_vertices_no_instance", + 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/tutorial-passthru_fragment.spv" + )), + entry_point: "tutorial::passthru_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, + layout: &bindgroup_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: slab.get_buffer().as_entire_binding(), + }], + }); + + struct App { + pipeline: wgpu::RenderPipeline, + bindgroup: wgpu::BindGroup, + vertices: Array, + } + + let app = App { + pipeline, + bindgroup, + vertices, + }; + r.graph.add_resource(app); + + fn render( + (device, queue, app, frame, depth): ( + View, + View, + View, + View, + View, + ), + ) -> Result<(), GraphError> { + let label = Some("slabbed isosceles triangle"); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label }); + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &frame.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), + 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(&app.pipeline); + render_pass.set_bind_group(0, &app.bindgroup, &[]); + render_pass.draw(0..app.vertices.len() as u32, 0..1); + } + queue.submit(std::iter::once(encoder.finish())); + Ok(()) + } + + use crate::frame::{clear_frame_and_depth, copy_frame_to_post, create_frame, present}; + r.graph.add_subgraph(graph!( + create_frame + < clear_frame_and_depth + < render + < copy_frame_to_post + < present + )); + + let img = r.render_image().unwrap(); + img_diff::assert_img_eq("tutorial/slabbed_isosceles_triangle_no_instance.png", img); + } + + #[test] + fn slabbed_isosceles_triangle() { + let mut r = Renderling::headless(100, 100).unwrap(); + let (device, queue) = r.get_device_and_queue_owned(); + + // 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 geometry = vec![ + Vertex { + position: Vec4::new(0.5, -0.5, 0.0, 1.0), + color: Vec4::new(1.0, 0.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(0.0, 0.5, 0.0, 1.0), + color: Vec4::new(0.0, 1.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-0.5, -0.5, 0.0, 1.0), + color: Vec4::new(0.0, 0.0, 1.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-1.0, 1.0, 0.0, 1.0), + color: Vec4::new(1.0, 0.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-1.0, 0.0, 0.0, 1.0), + color: Vec4::new(0.0, 1.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(0.0, 1.0, 0.0, 1.0), + color: Vec4::new(0.0, 0.0, 1.0, 1.0), + ..Default::default() + }, + ]; + let vertices = slab.append_slice(&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. + let label = Some("slabbed isosceles triangle"); + let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label, + 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, + bind_group_layouts: &[&bindgroup_layout], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &device.create_shader_module(wgpu::include_spirv!( + "linkage/tutorial-slabbed_vertices.spv" + )), + entry_point: "tutorial::slabbed_vertices", + 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/tutorial-passthru_fragment.spv" + )), + entry_point: "tutorial::passthru_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, + layout: &bindgroup_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: slab.get_buffer().as_entire_binding(), + }], + }); + + struct App { + pipeline: wgpu::RenderPipeline, + bindgroup: wgpu::BindGroup, + vertices_id: Id>, + vertices: Array, + } + + let app = App { + pipeline, + bindgroup, + vertices_id, + vertices, + }; + r.graph.add_resource(app); + + fn render( + (device, queue, app, frame, depth): ( + View, + View, + View, + View, + View, + ), + ) -> Result<(), GraphError> { + let label = Some("slabbed isosceles triangle"); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label }); + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &frame.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), + 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(&app.pipeline); + render_pass.set_bind_group(0, &app.bindgroup, &[]); + render_pass.draw( + 0..app.vertices.len() as u32, + app.vertices_id.inner()..app.vertices_id.inner() + 1, + ); + } + queue.submit(std::iter::once(encoder.finish())); + Ok(()) + } + + use crate::frame::{clear_frame_and_depth, copy_frame_to_post, create_frame, present}; + r.graph.add_subgraph(graph!( + create_frame + < clear_frame_and_depth + < render + < copy_frame_to_post + < present + )); + + let img = r.render_image().unwrap(); + img_diff::assert_img_eq("tutorial/slabbed_isosceles_triangle.png", img); + } + + #[test] + fn slabbed_render_unit() { + let mut r = Renderling::headless(100, 100).unwrap(); + let (device, queue) = r.get_device_and_queue_owned(); + + // 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 geometry = vec![ + Vertex { + position: Vec4::new(0.5, -0.5, 0.0, 1.0), + color: Vec4::new(1.0, 0.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(0.0, 0.5, 0.0, 1.0), + color: Vec4::new(0.0, 1.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-0.5, -0.5, 0.0, 1.0), + color: Vec4::new(0.0, 0.0, 1.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-1.0, 1.0, 0.0, 1.0), + color: Vec4::new(1.0, 0.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-1.0, 0.0, 0.0, 1.0), + color: Vec4::new(0.0, 1.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(0.0, 1.0, 0.0, 1.0), + color: Vec4::new(0.0, 0.0, 1.0, 1.0), + ..Default::default() + }, + ]; + let vertices = slab.append_slice(&device, &queue, &geometry); + let unit = RenderUnit { + vertices, + ..Default::default() + }; + let unit_id = slab.append(&device, &queue, &unit); + + // Create a bindgroup for the slab so our shader can read out the types. + let label = Some("slabbed isosceles triangle"); + let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label, + 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, + bind_group_layouts: &[&bindgroup_layout], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &device.create_shader_module(wgpu::include_spirv!( + "linkage/tutorial-slabbed_render_unit.spv" + )), + entry_point: "tutorial::slabbed_render_unit", + 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/tutorial-passthru_fragment.spv" + )), + entry_point: "tutorial::passthru_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, + layout: &bindgroup_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: slab.get_buffer().as_entire_binding(), + }], + }); + + struct App { + pipeline: wgpu::RenderPipeline, + bindgroup: wgpu::BindGroup, + unit_id: Id, + unit: RenderUnit, + } + + let app = App { + pipeline, + bindgroup, + unit_id, + unit, + }; + r.graph.add_resource(app); + + fn render( + (device, queue, app, frame, depth): ( + View, + View, + View, + View, + View, + ), + ) -> Result<(), GraphError> { + let label = Some("slabbed isosceles triangle"); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label }); + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &frame.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), + 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(&app.pipeline); + render_pass.set_bind_group(0, &app.bindgroup, &[]); + render_pass.draw( + 0..app.unit.vertices.len() as u32, + app.unit_id.inner()..app.unit_id.inner() + 1, + ); + } + queue.submit(std::iter::once(encoder.finish())); + Ok(()) + } + + use crate::frame::{clear_frame_and_depth, copy_frame_to_post, create_frame, present}; + r.graph.add_subgraph(graph!( + create_frame + < clear_frame_and_depth + < render + < copy_frame_to_post + < present + )); + + let img = r.render_image().unwrap(); + img_diff::assert_img_eq("tutorial/slabbed_render_unit.png", img); + } + + #[test] + fn slabbed_render_unit_camera() { + let mut r = Renderling::headless(100, 100).unwrap(); + let (device, queue) = r.get_device_and_queue_owned(); + + // 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 geometry = vec![ + Vertex { + position: Vec4::new(0.5, -0.5, 0.0, 1.0), + color: Vec4::new(1.0, 0.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(0.0, 0.5, 0.0, 1.0), + color: Vec4::new(0.0, 1.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-0.5, -0.5, 0.0, 1.0), + color: Vec4::new(0.0, 0.0, 1.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-1.0, 1.0, 0.0, 1.0), + color: Vec4::new(1.0, 0.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(-1.0, 0.0, 0.0, 1.0), + color: Vec4::new(0.0, 1.0, 0.0, 1.0), + ..Default::default() + }, + Vertex { + position: Vec4::new(0.0, 1.0, 0.0, 1.0), + color: Vec4::new(0.0, 0.0, 1.0, 1.0), + ..Default::default() + }, + ]; + let vertices = slab.append_slice(&device, &queue, &geometry); + let (projection, view) = crate::default_ortho2d(100.0, 100.0); + let camera_id = slab.append( + &device, + &queue, + &Camera { + projection, + view, + ..Default::default() + }, + ); + let unit = RenderUnit { + vertices, + camera: camera_id, + position: Vec3::new(50.0, 50.0, 0.0), + scale: Vec3::new(50.0, 50.0, 1.0), + ..Default::default() + }; + let unit_id = slab.append(&device, &queue, &unit); + + // Create a bindgroup for the slab so our shader can read out the types. + let label = Some("slabbed isosceles triangle"); + let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label, + 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, + bind_group_layouts: &[&bindgroup_layout], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &device.create_shader_module(wgpu::include_spirv!( + "linkage/tutorial-slabbed_render_unit.spv" + )), + entry_point: "tutorial::slabbed_render_unit", + 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/tutorial-passthru_fragment.spv" + )), + entry_point: "tutorial::passthru_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, + layout: &bindgroup_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: slab.get_buffer().as_entire_binding(), + }], + }); + + struct App { + pipeline: wgpu::RenderPipeline, + bindgroup: wgpu::BindGroup, + unit_id: Id, + unit: RenderUnit, + } + + let app = App { + pipeline, + bindgroup, + unit_id, + unit, + }; + r.graph.add_resource(app); + + fn render( + (device, queue, app, frame, depth): ( + View, + View, + View, + View, + View, + ), + ) -> Result<(), GraphError> { + let label = Some("slabbed isosceles triangle"); + let mut encoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label }); + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &frame.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), + 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(&app.pipeline); + render_pass.set_bind_group(0, &app.bindgroup, &[]); + render_pass.draw( + 0..app.unit.vertices.len() as u32, + app.unit_id.inner()..app.unit_id.inner() + 1, + ); + } + queue.submit(std::iter::once(encoder.finish())); + Ok(()) + } + + use crate::frame::{clear_frame_and_depth, copy_frame_to_post, create_frame, present}; + r.graph.add_subgraph(graph!( + create_frame + < clear_frame_and_depth + < render + < copy_frame_to_post + < present + )); + + let img = r.render_image().unwrap(); + img_diff::assert_img_eq("tutorial/slabbed_render_unit_camera.png", img); + } +} diff --git a/crates/renderling/src/ui.rs b/crates/renderling/src/ui.rs index 485bc0f2..507d0f51 100644 --- a/crates/renderling/src/ui.rs +++ b/crates/renderling/src/ui.rs @@ -568,14 +568,13 @@ pub fn setup_ui_render_graph( r.graph.add_resource(pipeline); use crate::{ - frame::{clear_frame_and_depth, create_frame, present}, + frame::{clear_frame_and_depth, copy_frame_to_post, create_frame, present}, graph, Graph, }; let pre_render = crate::graph!(create_frame, clear_frame_and_depth, ui_scene_update).with_barrier(); let render = crate::graph!(ui_scene_render).with_barrier(); let post_render = if with_screen_capture { - let copy_frame_to_post = crate::frame::PostRenderBufferCreate::create; crate::graph!(copy_frame_to_post < present) } else { crate::graph!(present) diff --git a/crates/sandbox/src/main.rs b/crates/sandbox/src/main.rs index 0e285a3f..d3f60f6e 100644 --- a/crates/sandbox/src/main.rs +++ b/crates/sandbox/src/main.rs @@ -1,6 +1,8 @@ use renderling::{ + frame::{clear_frame_and_depth, create_frame, present, FrameTextureView}, + graph::{graph, Graph}, math::{Vec3, Vec4}, - Vertex, Renderling, RenderGraphConfig, + DepthTexture, Device, GraphError, Queue, Renderling, View, }; fn main() { @@ -17,60 +19,148 @@ fn main() { let mut r = Renderling::try_from_window(&window) .unwrap() .with_background_color(Vec3::splat(0.0).extend(1.0)); - let (projection, view) = renderling::default_ortho2d(100.0, 100.0); - let mut builder = r.new_scene().with_camera(projection, view); - let size = 1.0; - let cyan_tri = builder - .new_entity() - .with_meshlet(vec![ - Vertex { - position: Vec4::new(0.0, 0.0, 0.0, 0.0), - color: Vec4::new(0.0, 1.0, 1.0, 1.0), - ..Default::default() - }, - Vertex { - position: Vec4::new(size, 0.0, 0.0, 0.0), - color: Vec4::new(0.0, 1.0, 1.0, 1.0), - ..Default::default() - }, - Vertex { - position: Vec4::new(size, size, 0.0, 0.0), - color: Vec4::new(0.0, 1.0, 1.0, 1.0), - ..Default::default() - }, - ]) - .with_position(Vec3::new(25.0, 25.0, 0.0)) - .with_scale(Vec3::new(25.0, 25.0, 1.0)) - .build(); - let _yellow_tri = builder - .new_entity() - .with_meshlet(vec![ - Vertex { - position: Vec4::new(0.0, 0.0, 0.0, 0.0), - color: Vec4::new(1.0, 1.0, 0.0, 1.0), - ..Default::default() - }, - Vertex { - position: Vec4::new(size, 0.0, 0.0, 0.0), - color: Vec4::new(1.0, 1.0, 0.0, 1.0), - ..Default::default() - }, - Vertex { - position: Vec4::new(size, size, 0.0, 0.0), - color: Vec4::new(1.0, 1.0, 0.0, 1.0), - ..Default::default() - }, - ]) - .with_position(Vec3::new(25.0, 25.0, 0.1)) - .with_parent(&cyan_tri) - .build(); - let scene = builder.build().unwrap(); - //r.setup_render_graph(Some(scene), None, [], false); - r.setup_render_graph(RenderGraphConfig { - scene: Some(scene), - ..Default::default() + //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; @@ -85,6 +175,9 @@ fn main() { }, .. } => *control_flow = winit::event_loop::ControlFlow::Exit, + winit::event::WindowEvent::Resized(size) => { + r.resize(size.width, size.height); + } _ => {} }, winit::event::Event::MainEventsCleared => { diff --git a/shaders/src/main.rs b/shaders/src/main.rs index f39af2e9..e30416ee 100644 --- a/shaders/src/main.rs +++ b/shaders/src/main.rs @@ -11,7 +11,7 @@ struct Cli { verbosity: u8, /// Path to the output directory for the compiled shaders. - #[clap(long, short, default_value = "shaders")] + #[clap(long, short, default_value = "../crates/renderling/src/linkage")] output_dir: std::path::PathBuf, } diff --git a/test_img/tutorial/implicit_isosceles_triangle.png b/test_img/tutorial/implicit_isosceles_triangle.png new file mode 100644 index 00000000..60ce5ed8 Binary files /dev/null and b/test_img/tutorial/implicit_isosceles_triangle.png differ diff --git a/test_img/tutorial/slabbed_isosceles_triangle.png b/test_img/tutorial/slabbed_isosceles_triangle.png new file mode 100644 index 00000000..521f7313 Binary files /dev/null and b/test_img/tutorial/slabbed_isosceles_triangle.png differ diff --git a/test_img/tutorial/slabbed_isosceles_triangle_no_instance.png b/test_img/tutorial/slabbed_isosceles_triangle_no_instance.png new file mode 100644 index 00000000..eef29c0b Binary files /dev/null and b/test_img/tutorial/slabbed_isosceles_triangle_no_instance.png differ diff --git a/test_img/tutorial/slabbed_render_unit.png b/test_img/tutorial/slabbed_render_unit.png new file mode 100644 index 00000000..521f7313 Binary files /dev/null and b/test_img/tutorial/slabbed_render_unit.png differ diff --git a/test_img/tutorial/slabbed_render_unit_camera.png b/test_img/tutorial/slabbed_render_unit_camera.png new file mode 100644 index 00000000..4ec80811 Binary files /dev/null and b/test_img/tutorial/slabbed_render_unit_camera.png differ