1use core::ops::Deref;
3use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
4use craballoc::prelude::*;
5use crabslab::Id;
6use glam::{Mat4, UVec2, Vec4};
7use snafu::{ResultExt, Snafu};
8use std::sync::{atomic::AtomicBool, Arc, Mutex, RwLock};
9
10use crate::atlas::AtlasTexture;
11use crate::camera::Camera;
12use crate::geometry::{shader::GeometryDescriptor, MorphTarget, Vertex};
13#[cfg(gltf)]
14use crate::gltf::GltfDocument;
15use crate::light::{DirectionalLight, IsLight, Light, PointLight, SpotLight};
16use crate::material::Material;
17use crate::pbr::brdf::BrdfLut;
18use crate::pbr::ibl::Ibl;
19use crate::primitive::Primitive;
20use crate::{
21 atlas::{AtlasError, AtlasImage, AtlasImageError},
22 bindgroup::ManagedBindGroup,
23 bloom::Bloom,
24 camera::shader::CameraDescriptor,
25 debug::DebugOverlay,
26 draw::DrawCalls,
27 geometry::{Geometry, Indices, MorphTargetWeights, MorphTargets, Skin, SkinJoint, Vertices},
28 light::{
29 AnalyticalLight, LightTiling, LightTilingConfig, Lighting, LightingBindGroupLayoutEntries,
30 LightingError, ShadowMap,
31 },
32 material::Materials,
33 pbr::debug::DebugChannel,
34 skybox::{Skybox, SkyboxRenderPipeline},
35 texture::{DepthTexture, Texture},
36 tonemapping::Tonemapping,
37 transform::{NestedTransform, Transform},
38};
39
40#[derive(Debug, Snafu)]
42pub enum StageError {
43 #[snafu(display("{source}"))]
44 Atlas { source: AtlasError },
45
46 #[snafu(display("{source}"))]
47 Lighting { source: LightingError },
48
49 #[cfg(gltf)]
50 #[snafu(display("{source}"))]
51 Gltf { source: crate::gltf::StageGltfError },
52}
53
54impl From<AtlasError> for StageError {
55 fn from(source: AtlasError) -> Self {
56 Self::Atlas { source }
57 }
58}
59
60impl From<LightingError> for StageError {
61 fn from(source: LightingError) -> Self {
62 Self::Lighting { source }
63 }
64}
65
66#[cfg(gltf)]
67impl From<crate::gltf::StageGltfError> for StageError {
68 fn from(source: crate::gltf::StageGltfError) -> Self {
69 Self::Gltf { source }
70 }
71}
72
73fn create_msaa_textureview(
74 device: &wgpu::Device,
75 width: u32,
76 height: u32,
77 format: wgpu::TextureFormat,
78 sample_count: u32,
79) -> wgpu::TextureView {
80 device
81 .create_texture(&wgpu::TextureDescriptor {
82 label: Some("stage msaa render target"),
83 size: wgpu::Extent3d {
84 width,
85 height,
86 depth_or_array_layers: 1,
87 },
88 mip_level_count: 1,
89 sample_count,
90 dimension: wgpu::TextureDimension::D2,
91 format,
92 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
93 view_formats: &[],
94 })
95 .create_view(&wgpu::TextureViewDescriptor::default())
96}
97
98pub struct StageCommitResult {
100 pub(crate) geometry_buffer: SlabBuffer<wgpu::Buffer>,
101 pub(crate) lighting_buffer: SlabBuffer<wgpu::Buffer>,
102 pub(crate) materials_buffer: SlabBuffer<wgpu::Buffer>,
103}
104
105impl StageCommitResult {
106 pub(crate) fn latest_creation_time(&self) -> usize {
108 [
109 &self.geometry_buffer,
110 &self.materials_buffer,
111 &self.lighting_buffer,
112 ]
113 .iter()
114 .map(|buffer| buffer.creation_time())
115 .max()
116 .unwrap_or_default()
117 }
118
119 pub(crate) fn should_invalidate(&self, previous_creation_time: usize) -> bool {
122 let mut should = false;
123 if self.geometry_buffer.is_new_this_commit() {
124 log::trace!("geometry buffer is new this frame");
125 should = true;
126 }
127 if self.materials_buffer.is_new_this_commit() {
128 log::trace!("materials buffer is new this frame");
129 should = true;
130 }
131 if self.lighting_buffer.is_new_this_commit() {
132 log::trace!("lighting buffer is new this frame");
133 should = true;
134 }
135 let current = self.latest_creation_time();
136 if current > previous_creation_time {
137 log::trace!(
138 "current latest buffer creation time {current} > previous {previous_creation_time}"
139 );
140 should = true;
141 }
142 should
143 }
144}
145
146struct PrimitiveBindGroup<'a> {
152 device: &'a wgpu::Device,
153 layout: &'a wgpu::BindGroupLayout,
154 geometry_buffer: &'a wgpu::Buffer,
155 material_buffer: &'a wgpu::Buffer,
156 light_buffer: &'a wgpu::Buffer,
157 atlas_texture_view: &'a wgpu::TextureView,
158 atlas_texture_sampler: &'a wgpu::Sampler,
159 irradiance_texture_view: &'a wgpu::TextureView,
160 irradiance_texture_sampler: &'a wgpu::Sampler,
161 prefiltered_texture_view: &'a wgpu::TextureView,
162 prefiltered_texture_sampler: &'a wgpu::Sampler,
163 brdf_texture_view: &'a wgpu::TextureView,
164 brdf_texture_sampler: &'a wgpu::Sampler,
165 shadow_map_texture_view: &'a wgpu::TextureView,
166 shadow_map_texture_sampler: &'a wgpu::Sampler,
167}
168
169impl PrimitiveBindGroup<'_> {
170 pub fn create(self) -> wgpu::BindGroup {
171 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
172 label: Some("primitive"),
173 layout: self.layout,
174 entries: &[
175 wgpu::BindGroupEntry {
176 binding: 0,
177 resource: self.geometry_buffer.as_entire_binding(),
178 },
179 wgpu::BindGroupEntry {
180 binding: 1,
181 resource: self.material_buffer.as_entire_binding(),
182 },
183 wgpu::BindGroupEntry {
184 binding: 2,
185 resource: wgpu::BindingResource::TextureView(self.atlas_texture_view),
186 },
187 wgpu::BindGroupEntry {
188 binding: 3,
189 resource: wgpu::BindingResource::Sampler(self.atlas_texture_sampler),
190 },
191 wgpu::BindGroupEntry {
192 binding: 4,
193 resource: wgpu::BindingResource::TextureView(self.irradiance_texture_view),
194 },
195 wgpu::BindGroupEntry {
196 binding: 5,
197 resource: wgpu::BindingResource::Sampler(self.irradiance_texture_sampler),
198 },
199 wgpu::BindGroupEntry {
200 binding: 6,
201 resource: wgpu::BindingResource::TextureView(self.prefiltered_texture_view),
202 },
203 wgpu::BindGroupEntry {
204 binding: 7,
205 resource: wgpu::BindingResource::Sampler(self.prefiltered_texture_sampler),
206 },
207 wgpu::BindGroupEntry {
208 binding: 8,
209 resource: wgpu::BindingResource::TextureView(self.brdf_texture_view),
210 },
211 wgpu::BindGroupEntry {
212 binding: 9,
213 resource: wgpu::BindingResource::Sampler(self.brdf_texture_sampler),
214 },
215 wgpu::BindGroupEntry {
216 binding: 10,
217 resource: self.light_buffer.as_entire_binding(),
218 },
219 wgpu::BindGroupEntry {
220 binding: 11,
221 resource: wgpu::BindingResource::TextureView(self.shadow_map_texture_view),
222 },
223 wgpu::BindGroupEntry {
224 binding: 12,
225 resource: wgpu::BindingResource::Sampler(self.shadow_map_texture_sampler),
226 },
227 ],
228 })
229 }
230}
231
232pub(crate) struct StageRendering<'a> {
234 pub stage: &'a Stage,
236 pub pipeline: &'a wgpu::RenderPipeline,
237 pub color_attachment: wgpu::RenderPassColorAttachment<'a>,
238 pub depth_stencil_attachment: wgpu::RenderPassDepthStencilAttachment<'a>,
239}
240
241impl StageRendering<'_> {
242 pub fn run(self) -> (wgpu::SubmissionIndex, Option<SlabBuffer<wgpu::Buffer>>) {
246 let commit_result = self.stage.commit();
247 let current_primitive_bind_group_creation_time = commit_result.latest_creation_time();
248 log::trace!("current_primitive_bind_group_creation_time: {current_primitive_bind_group_creation_time}");
249 let previous_primitive_bind_group_creation_time =
250 self.stage.primitive_bind_group_created.swap(
251 current_primitive_bind_group_creation_time,
252 std::sync::atomic::Ordering::Relaxed,
253 );
254 let should_invalidate_primitive_bind_group =
255 commit_result.should_invalidate(previous_primitive_bind_group_creation_time);
256 log::trace!(
257 "should_invalidate_primitive_bind_group: {should_invalidate_primitive_bind_group}"
258 );
259 let primitive_bind_group =
260 self.stage
261 .primitive_bind_group
262 .get(should_invalidate_primitive_bind_group, || {
263 log::trace!("recreating primitive bind group");
264 let atlas_texture = self.stage.materials.atlas().get_texture();
265 let ibl = self.stage.ibl.read().unwrap();
266 let shadow_map = self.stage.lighting.shadow_map_atlas.get_texture();
267 PrimitiveBindGroup {
268 device: self.stage.device(),
269 layout: &Stage::primitive_pipeline_bindgroup_layout(self.stage.device()),
270 geometry_buffer: &commit_result.geometry_buffer,
271 material_buffer: &commit_result.materials_buffer,
272 light_buffer: &commit_result.lighting_buffer,
273 atlas_texture_view: &atlas_texture.view,
274 atlas_texture_sampler: &atlas_texture.sampler,
275 irradiance_texture_view: &ibl.irradiance_cubemap.view,
276 irradiance_texture_sampler: &ibl.irradiance_cubemap.sampler,
277 prefiltered_texture_view: &ibl.prefiltered_environment_cubemap.view,
278 prefiltered_texture_sampler: &ibl.prefiltered_environment_cubemap.sampler,
279 brdf_texture_view: &self.stage.brdf_lut.inner.view,
280 brdf_texture_sampler: &self.stage.brdf_lut.inner.sampler,
281 shadow_map_texture_view: &shadow_map.view,
282 shadow_map_texture_sampler: &shadow_map.sampler,
283 }
284 .create()
285 });
286
287 let mut draw_calls = self.stage.draw_calls.write().unwrap();
288 let depth_texture = self.stage.depth_texture.read().unwrap();
289 let maybe_indirect_buffer = draw_calls.pre_draw(&depth_texture).unwrap();
291
292 log::trace!("rendering");
293 let label = Some("stage render");
294
295 let mut encoder = self
296 .stage
297 .device()
298 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label });
299 {
300 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
301 label,
302 color_attachments: &[Some(self.color_attachment)],
303 depth_stencil_attachment: Some(self.depth_stencil_attachment),
304 ..Default::default()
305 });
306
307 render_pass.set_pipeline(self.pipeline);
308 render_pass.set_bind_group(0, Some(primitive_bind_group.as_ref()), &[]);
309 draw_calls.draw(&mut render_pass);
310
311 let has_skybox = self.stage.has_skybox.load(Ordering::Relaxed);
312 if has_skybox {
313 let (pipeline, bindgroup) = self
314 .stage
315 .get_skybox_pipeline_and_bindgroup(&commit_result.geometry_buffer);
316 render_pass.set_pipeline(&pipeline.pipeline);
317 render_pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]);
318 let camera_id = self.stage.geometry.descriptor().get().camera_id.inner();
319 render_pass.draw(0..36, camera_id..camera_id + 1);
320 }
321 }
322 let sindex = self.stage.queue().submit(std::iter::once(encoder.finish()));
323 (sindex, maybe_indirect_buffer)
324 }
325}
326
327#[derive(Clone)]
382pub struct Stage {
383 pub(crate) geometry: Geometry,
384 pub(crate) materials: Materials,
385 pub(crate) lighting: Lighting,
386
387 pub(crate) primitive_pipeline: Arc<RwLock<wgpu::RenderPipeline>>,
388 pub(crate) primitive_bind_group: ManagedBindGroup,
389 pub(crate) primitive_bind_group_created: Arc<AtomicUsize>,
390
391 pub(crate) skybox_pipeline: Arc<RwLock<Option<Arc<SkyboxRenderPipeline>>>>,
392
393 pub(crate) hdr_texture: Arc<RwLock<Texture>>,
394 pub(crate) depth_texture: Arc<RwLock<Texture>>,
395 pub(crate) msaa_render_target: Arc<RwLock<Option<wgpu::TextureView>>>,
396 pub(crate) msaa_sample_count: Arc<AtomicU32>,
397 pub(crate) clear_color_attachments: Arc<AtomicBool>,
398 pub(crate) clear_depth_attachments: Arc<AtomicBool>,
399
400 pub(crate) bloom: Bloom,
401
402 pub(crate) tonemapping: Tonemapping,
403 pub(crate) debug_overlay: DebugOverlay,
404 pub(crate) background_color: Arc<RwLock<wgpu::Color>>,
405
406 pub(crate) brdf_lut: BrdfLut,
407
408 pub(crate) ibl: Arc<RwLock<Ibl>>,
409
410 pub(crate) skybox: Arc<RwLock<Skybox>>,
411 pub(crate) skybox_bindgroup: Arc<Mutex<Option<Arc<wgpu::BindGroup>>>>,
412 pub(crate) has_skybox: Arc<AtomicBool>,
414
415 pub(crate) has_bloom: Arc<AtomicBool>,
416 pub(crate) has_debug_overlay: Arc<AtomicBool>,
417
418 pub(crate) stage_slab_buffer: Arc<RwLock<SlabBuffer<wgpu::Buffer>>>,
419
420 pub(crate) textures_bindgroup: Arc<Mutex<Option<Arc<wgpu::BindGroup>>>>,
421
422 pub(crate) draw_calls: Arc<RwLock<DrawCalls>>,
423}
424
425impl AsRef<WgpuRuntime> for Stage {
426 fn as_ref(&self) -> &WgpuRuntime {
427 self.geometry.as_ref()
428 }
429}
430
431impl AsRef<Geometry> for Stage {
432 fn as_ref(&self) -> &Geometry {
433 &self.geometry
434 }
435}
436
437impl AsRef<Materials> for Stage {
438 fn as_ref(&self) -> &Materials {
439 &self.materials
440 }
441}
442
443impl AsRef<Lighting> for Stage {
444 fn as_ref(&self) -> &Lighting {
445 &self.lighting
446 }
447}
448
449#[cfg(gltf)]
450impl Stage {
452 pub fn load_gltf_document_from_path(
453 &self,
454 path: impl AsRef<std::path::Path>,
455 ) -> Result<GltfDocument, StageError> {
456 use snafu::ResultExt;
457
458 let (document, buffers, images) =
459 gltf::import(&path).with_context(|_| crate::gltf::GltfSnafu {
460 path: Some(path.as_ref().to_path_buf()),
461 })?;
462 GltfDocument::from_gltf(self, &document, buffers, images)
463 }
464
465 pub fn load_gltf_document_from_bytes(
466 &self,
467 bytes: impl AsRef<[u8]>,
468 ) -> Result<GltfDocument, StageError> {
469 let (document, buffers, images) =
470 gltf::import_slice(bytes).context(crate::gltf::GltfSnafu { path: None })?;
471 GltfDocument::from_gltf(self, &document, buffers, images)
472 }
473}
474
475impl Stage {
477 pub fn default_vertices(&self) -> &Vertices {
481 self.geometry.default_vertices()
482 }
483
484 pub fn new_camera(&self) -> Camera {
489 self.geometry.new_camera()
490 }
491
492 pub fn use_camera(&self, camera: impl AsRef<Camera>) {
494 self.geometry.use_camera(camera.as_ref());
495 }
496
497 pub fn used_camera_id(&self) -> Id<CameraDescriptor> {
499 self.geometry.descriptor().get().camera_id
500 }
501
502 pub fn use_camera_id(&self, camera_id: Id<CameraDescriptor>) {
504 self.geometry
505 .descriptor()
506 .modify(|desc| desc.camera_id = camera_id);
507 }
508
509 pub fn new_transform(&self) -> Transform {
511 self.geometry.new_transform()
512 }
513
514 pub fn new_vertices(&self, data: impl IntoIterator<Item = Vertex>) -> Vertices {
516 self.geometry.new_vertices(data)
517 }
518
519 pub fn new_indices(&self, data: impl IntoIterator<Item = u32>) -> Indices {
521 self.geometry.new_indices(data)
522 }
523
524 pub fn new_morph_targets(
526 &self,
527 data: impl IntoIterator<Item = Vec<MorphTarget>>,
528 ) -> MorphTargets {
529 self.geometry.new_morph_targets(data)
530 }
531
532 pub fn new_morph_target_weights(
534 &self,
535 data: impl IntoIterator<Item = f32>,
536 ) -> MorphTargetWeights {
537 self.geometry.new_morph_target_weights(data)
538 }
539
540 pub fn new_skin(
542 &self,
543 joints: impl IntoIterator<Item = impl Into<SkinJoint>>,
544 inverse_bind_matrices: impl IntoIterator<Item = impl Into<Mat4>>,
545 ) -> Skin {
546 self.geometry.new_skin(joints, inverse_bind_matrices)
547 }
548
549 pub fn new_primitive(&self) -> Primitive {
560 Primitive::new(self)
561 }
562
563 pub fn geometry_descriptor(&self) -> &Hybrid<GeometryDescriptor> {
566 self.geometry.descriptor()
567 }
568}
569
570impl Stage {
572 pub fn default_material(&self) -> &Material {
576 self.materials.default_material()
577 }
578
579 pub fn new_material(&self) -> Material {
583 self.materials.new_material()
584 }
585
586 pub fn set_atlas_size(&self, size: wgpu::Extent3d) -> Result<(), StageError> {
590 log::info!("resizing atlas to {size:?}");
591 self.materials.atlas().resize(self.runtime(), size)?;
592 Ok(())
593 }
594
595 pub fn add_images(
606 &self,
607 images: impl IntoIterator<Item = impl Into<AtlasImage>>,
608 ) -> Result<Vec<AtlasTexture>, StageError> {
609 let images = images.into_iter().map(|i| i.into()).collect::<Vec<_>>();
610 let frames = self.materials.atlas().add_images(&images)?;
611
612 let _ = self.textures_bindgroup.lock().unwrap().take();
614
615 Ok(frames)
616 }
617
618 pub fn clear_images(&self) -> Result<(), StageError> {
623 let none = Option::<AtlasImage>::None;
624 let _ = self.set_images(none)?;
625 Ok(())
626 }
627
628 pub fn set_images(
636 &self,
637 images: impl IntoIterator<Item = impl Into<AtlasImage>>,
638 ) -> Result<Vec<AtlasTexture>, StageError> {
639 let images = images.into_iter().map(|i| i.into()).collect::<Vec<_>>();
640 let frames = self.materials.atlas().set_images(&images)?;
641
642 let _ = self.textures_bindgroup.lock().unwrap().take();
644
645 Ok(frames)
646 }
647}
648
649impl Stage {
651 pub fn new_directional_light(&self) -> AnalyticalLight<DirectionalLight> {
653 self.lighting.new_directional_light()
654 }
655
656 pub fn new_point_light(&self) -> AnalyticalLight<PointLight> {
658 self.lighting.new_point_light()
659 }
660
661 pub fn new_spot_light(&self) -> AnalyticalLight<SpotLight> {
663 self.lighting.new_spot_light()
664 }
665
666 pub fn add_light<T>(&self, bundle: &AnalyticalLight<T>)
673 where
674 T: IsLight,
675 Light: From<T>,
676 {
677 self.lighting.add_light(bundle)
678 }
679
680 pub fn remove_light<T: IsLight>(&self, bundle: &AnalyticalLight<T>) {
686 self.lighting.remove_light(bundle);
687 }
688
689 pub fn new_shadow_map<T>(
706 &self,
707 analytical_light: &AnalyticalLight<T>,
708 size: UVec2,
710 z_near: f32,
714 z_far: f32,
718 ) -> Result<ShadowMap, StageError>
719 where
720 T: IsLight,
721 Light: From<T>,
722 {
723 Ok(self
724 .lighting
725 .new_shadow_map(analytical_light, size, z_near, z_far)?)
726 }
727
728 pub fn new_light_tiling(&self, config: LightTilingConfig) -> LightTiling {
730 let lighting = self.as_ref();
731 let multisampled = self.get_msaa_sample_count() > 1;
732 let depth_texture_size = self.get_depth_texture().size();
733 LightTiling::new(
734 lighting,
735 multisampled,
736 UVec2::new(depth_texture_size.width, depth_texture_size.height),
737 config,
738 )
739 }
740}
741
742impl Stage {
744 fn get_skybox_pipeline_and_bindgroup(
746 &self,
747 geometry_slab_buffer: &wgpu::Buffer,
748 ) -> (Arc<SkyboxRenderPipeline>, Arc<wgpu::BindGroup>) {
749 let msaa_sample_count = self.msaa_sample_count.load(Ordering::Relaxed);
750 let mut pipeline_guard = self.skybox_pipeline.write().unwrap();
752 let pipeline = if let Some(pipeline) = pipeline_guard.as_mut() {
753 if pipeline.msaa_sample_count() != msaa_sample_count {
754 *pipeline = Arc::new(crate::skybox::create_skybox_render_pipeline(
755 self.device(),
756 Texture::HDR_TEXTURE_FORMAT,
757 Some(msaa_sample_count),
758 ));
759 }
760 pipeline.clone()
761 } else {
762 let pipeline = Arc::new(crate::skybox::create_skybox_render_pipeline(
763 self.device(),
764 Texture::HDR_TEXTURE_FORMAT,
765 Some(msaa_sample_count),
766 ));
767 *pipeline_guard = Some(pipeline.clone());
768 pipeline
769 };
770 let mut bindgroup = self.skybox_bindgroup.lock().unwrap();
772 let bindgroup = if let Some(bindgroup) = bindgroup.as_ref() {
773 bindgroup.clone()
774 } else {
775 let bg = Arc::new(crate::skybox::create_skybox_bindgroup(
776 self.device(),
777 geometry_slab_buffer,
778 self.skybox.read().unwrap().environment_cubemap_texture(),
779 ));
780 *bindgroup = Some(bg.clone());
781 bg
782 };
783 (pipeline, bindgroup)
784 }
785
786 pub fn use_skybox(&self, skybox: &Skybox) -> &Self {
790 let mut guard = self.skybox.write().unwrap();
792 *guard = skybox.clone();
793 self.has_skybox
794 .store(true, std::sync::atomic::Ordering::Relaxed);
795 *self.skybox_bindgroup.lock().unwrap() = None;
796 *self.textures_bindgroup.lock().unwrap() = None;
797 self
798 }
799
800 pub fn remove_skybox(&self) -> Option<Skybox> {
807 let mut guard = self.skybox.write().unwrap();
808 if guard.is_empty() {
809 None
811 } else {
812 let skybox = guard.clone();
813 *guard = Skybox::empty(self.runtime());
814 self.skybox_bindgroup.lock().unwrap().take();
815 self.textures_bindgroup.lock().unwrap().take();
816 Some(skybox)
817 }
818 }
819
820 pub fn new_skybox_from_path(
824 &self,
825 path: impl AsRef<std::path::Path>,
826 ) -> Result<Skybox, AtlasImageError> {
827 let hdr = AtlasImage::from_hdr_path(path)?;
828 Ok(Skybox::new(self.runtime(), hdr))
829 }
830
831 pub fn new_skybox_from_bytes(&self, bytes: &[u8]) -> Result<Skybox, AtlasImageError> {
835 let hdr = AtlasImage::from_hdr_bytes(bytes)?;
836 Ok(Skybox::new(self.runtime(), hdr))
837 }
838}
839
840impl Stage {
842 pub fn new_ibl(&self, skybox: &Skybox) -> Ibl {
844 Ibl::new(self.runtime(), skybox)
845 }
846
847 pub fn use_ibl(&self, ibl: &Ibl) -> &Self {
851 let mut guard = self.ibl.write().unwrap();
852 *guard = ibl.clone();
853 self.primitive_bind_group.invalidate();
854 self
855 }
856
857 pub fn remove_ibl(&self) -> Option<Ibl> {
859 let mut guard = self.ibl.write().unwrap();
860 if guard.is_empty() {
861 None
863 } else {
864 let ibl = guard.clone();
865 *guard = Ibl::new(self.runtime(), &Skybox::empty(self.runtime()));
866 self.primitive_bind_group.invalidate();
867 Some(ibl)
868 }
869 }
870}
871
872impl Stage {
873 pub fn runtime(&self) -> &WgpuRuntime {
875 self.as_ref()
876 }
877
878 pub fn device(&self) -> &wgpu::Device {
879 &self.runtime().device
880 }
881
882 pub fn queue(&self) -> &wgpu::Queue {
883 &self.runtime().queue
884 }
885
886 pub fn brdf_lut(&self) -> &BrdfLut {
890 &self.brdf_lut
891 }
892
893 pub fn used_gpu_buffer_byte_size(&self) -> usize {
901 let num_u32s = self.geometry.slab_allocator().len()
902 + self.lighting.slab_allocator().len()
903 + self.materials.slab_allocator().len()
904 + self.bloom.slab_allocator().len()
905 + self.tonemapping.slab_allocator().len()
906 + self
907 .draw_calls
908 .read()
909 .unwrap()
910 .drawing_strategy()
911 .as_indirect()
912 .map(|draws| draws.slab_allocator().len())
913 .unwrap_or_default();
914 4 * num_u32s
915 }
916
917 pub fn hdr_texture(&self) -> impl Deref<Target = crate::texture::Texture> + '_ {
918 self.hdr_texture.read().unwrap()
919 }
920
921 #[must_use]
927 pub fn commit(&self) -> StageCommitResult {
928 let (materials_atlas_texture_was_recreated, materials_buffer) = self.materials.commit();
929 if materials_atlas_texture_was_recreated {
930 self.primitive_bind_group.invalidate();
931 }
932 let geometry_buffer = self.geometry.commit();
933 let lighting_buffer = self.lighting.commit();
934 StageCommitResult {
935 geometry_buffer,
936 lighting_buffer,
937 materials_buffer,
938 }
939 }
940
941 fn primitive_pipeline_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
942 let geometry_slab = wgpu::BindGroupLayoutEntry {
943 binding: 0,
944 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
945 ty: wgpu::BindingType::Buffer {
946 ty: wgpu::BufferBindingType::Storage { read_only: true },
947 has_dynamic_offset: false,
948 min_binding_size: None,
949 },
950 count: None,
951 };
952 let material_slab = wgpu::BindGroupLayoutEntry {
953 binding: 1,
954 visibility: wgpu::ShaderStages::FRAGMENT,
955 ty: wgpu::BindingType::Buffer {
956 ty: wgpu::BufferBindingType::Storage { read_only: true },
957 has_dynamic_offset: false,
958 min_binding_size: None,
959 },
960 count: None,
961 };
962
963 fn image2d_entry(binding: u32) -> (wgpu::BindGroupLayoutEntry, wgpu::BindGroupLayoutEntry) {
964 let img = wgpu::BindGroupLayoutEntry {
965 binding,
966 visibility: wgpu::ShaderStages::FRAGMENT,
967 ty: wgpu::BindingType::Texture {
968 sample_type: wgpu::TextureSampleType::Float { filterable: true },
969 view_dimension: wgpu::TextureViewDimension::D2,
970 multisampled: false,
971 },
972 count: None,
973 };
974 let sampler = wgpu::BindGroupLayoutEntry {
975 binding: binding + 1,
976 visibility: wgpu::ShaderStages::FRAGMENT,
977 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
978 count: None,
979 };
980 (img, sampler)
981 }
982
983 fn cubemap_entry(binding: u32) -> (wgpu::BindGroupLayoutEntry, wgpu::BindGroupLayoutEntry) {
984 let img = wgpu::BindGroupLayoutEntry {
985 binding,
986 visibility: wgpu::ShaderStages::FRAGMENT,
987 ty: wgpu::BindingType::Texture {
988 sample_type: wgpu::TextureSampleType::Float { filterable: true },
989 view_dimension: wgpu::TextureViewDimension::Cube,
990 multisampled: false,
991 },
992 count: None,
993 };
994 let sampler = wgpu::BindGroupLayoutEntry {
995 binding: binding + 1,
996 visibility: wgpu::ShaderStages::FRAGMENT,
997 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
998 count: None,
999 };
1000 (img, sampler)
1001 }
1002
1003 let atlas = wgpu::BindGroupLayoutEntry {
1004 binding: 2,
1005 visibility: wgpu::ShaderStages::FRAGMENT,
1006 ty: wgpu::BindingType::Texture {
1007 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1008 view_dimension: wgpu::TextureViewDimension::D2Array,
1009 multisampled: false,
1010 },
1011 count: None,
1012 };
1013 let atlas_sampler = wgpu::BindGroupLayoutEntry {
1014 binding: 3,
1015 visibility: wgpu::ShaderStages::FRAGMENT,
1016 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1017 count: None,
1018 };
1019 let (irradiance, irradiance_sampler) = cubemap_entry(4);
1020 let (prefilter, prefilter_sampler) = cubemap_entry(6);
1021 let (brdf, brdf_sampler) = image2d_entry(8);
1022
1023 let LightingBindGroupLayoutEntries {
1024 light_slab,
1025 shadow_map_image,
1026 shadow_map_sampler,
1027 } = LightingBindGroupLayoutEntries::new(10);
1028
1029 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1030 label: Some("primitive"),
1031 entries: &[
1032 geometry_slab,
1033 material_slab,
1034 atlas,
1035 atlas_sampler,
1036 irradiance,
1037 irradiance_sampler,
1038 prefilter,
1039 prefilter_sampler,
1040 brdf,
1041 brdf_sampler,
1042 light_slab,
1043 shadow_map_image,
1044 shadow_map_sampler,
1045 ],
1046 })
1047 }
1048
1049 pub fn create_primitive_pipeline(
1050 device: &wgpu::Device,
1051 fragment_color_format: wgpu::TextureFormat,
1052 multisample_count: u32,
1053 ) -> wgpu::RenderPipeline {
1054 log::trace!("creating stage render pipeline");
1055 let label = Some("primitive");
1056 let vertex_linkage = crate::linkage::primitive_vertex::linkage(device);
1057 let fragment_linkage = crate::linkage::primitive_fragment::linkage(device);
1058
1059 let bind_group_layout = Self::primitive_pipeline_bindgroup_layout(device);
1060 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1061 label,
1062 bind_group_layouts: &[&bind_group_layout],
1063 push_constant_ranges: &[],
1064 });
1065
1066 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1067 label,
1068 layout: Some(&layout),
1069 vertex: wgpu::VertexState {
1070 module: &vertex_linkage.module,
1071 entry_point: Some(vertex_linkage.entry_point),
1072 buffers: &[],
1073 compilation_options: Default::default(),
1074 },
1075 primitive: wgpu::PrimitiveState {
1076 topology: wgpu::PrimitiveTopology::TriangleList,
1077 strip_index_format: None,
1078 front_face: wgpu::FrontFace::Ccw,
1079 cull_mode: None,
1080 unclipped_depth: false,
1081 polygon_mode: wgpu::PolygonMode::Fill,
1082 conservative: false,
1083 },
1084 depth_stencil: Some(wgpu::DepthStencilState {
1085 format: wgpu::TextureFormat::Depth32Float,
1086 depth_write_enabled: true,
1087 depth_compare: wgpu::CompareFunction::Less,
1088 stencil: wgpu::StencilState::default(),
1089 bias: wgpu::DepthBiasState::default(),
1090 }),
1091 multisample: wgpu::MultisampleState {
1092 mask: !0,
1093 alpha_to_coverage_enabled: false,
1094 count: multisample_count,
1095 },
1096 fragment: Some(wgpu::FragmentState {
1097 module: &fragment_linkage.module,
1098 entry_point: Some(fragment_linkage.entry_point),
1099 targets: &[Some(wgpu::ColorTargetState {
1100 format: fragment_color_format,
1101 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1102 write_mask: wgpu::ColorWrites::ALL,
1103 })],
1104 compilation_options: Default::default(),
1105 }),
1106 multiview: None,
1107 cache: None,
1108 })
1109 }
1110
1111 pub fn new(ctx: &crate::context::Context) -> Self {
1113 let runtime = ctx.runtime();
1114 let device = &runtime.device;
1115 let resolution @ UVec2 { x: w, y: h } = ctx.get_size();
1116 let stage_config = *ctx.stage_config.read().unwrap();
1117 let geometry = Geometry::new(
1118 ctx,
1119 resolution,
1120 UVec2::new(
1121 stage_config.atlas_size.width,
1122 stage_config.atlas_size.height,
1123 ),
1124 );
1125 let materials = Materials::new(runtime, stage_config.atlas_size);
1126 let multisample_count = 1;
1127 let hdr_texture = Arc::new(RwLock::new(Texture::create_hdr_texture(
1128 device,
1129 w,
1130 h,
1131 multisample_count,
1132 )));
1133 let depth_texture =
1134 Texture::create_depth_texture(device, w, h, multisample_count, Some("stage-depth"));
1135 let msaa_render_target = Default::default();
1136 let bloom = Bloom::new(ctx, &hdr_texture.read().unwrap());
1138 let tonemapping = Tonemapping::new(
1139 runtime,
1140 ctx.get_render_target().format().add_srgb_suffix(),
1141 &bloom.get_mix_texture(),
1142 );
1143 let stage_pipeline = Self::create_primitive_pipeline(
1144 device,
1145 wgpu::TextureFormat::Rgba16Float,
1146 multisample_count,
1147 );
1148 let geometry_buffer = geometry.slab_allocator().commit();
1149 let lighting = Lighting::new(stage_config.shadow_map_atlas_size, &geometry);
1150
1151 let brdf_lut = BrdfLut::new(runtime);
1152 let skybox = Skybox::empty(runtime);
1153 let ibl = Ibl::new(runtime, &skybox);
1154
1155 Self {
1156 materials,
1157 draw_calls: Arc::new(RwLock::new(DrawCalls::new(
1158 ctx,
1159 ctx.get_use_direct_draw(),
1160 &geometry_buffer,
1161 &depth_texture,
1162 ))),
1163 lighting,
1164 depth_texture: Arc::new(RwLock::new(depth_texture)),
1165 stage_slab_buffer: Arc::new(RwLock::new(geometry_buffer)),
1166 geometry,
1167
1168 primitive_pipeline: Arc::new(RwLock::new(stage_pipeline)),
1169 primitive_bind_group: ManagedBindGroup::default(),
1170 primitive_bind_group_created: Arc::new(0.into()),
1171
1172 ibl: Arc::new(RwLock::new(ibl)),
1173 skybox: Arc::new(RwLock::new(skybox)),
1174 skybox_bindgroup: Default::default(),
1175 skybox_pipeline: Default::default(),
1176 has_skybox: Arc::new(AtomicBool::new(false)),
1177 brdf_lut,
1178 bloom,
1179 tonemapping,
1180 has_bloom: AtomicBool::from(true).into(),
1181 textures_bindgroup: Default::default(),
1182 debug_overlay: DebugOverlay::new(device, ctx.get_render_target().format()),
1183 has_debug_overlay: Arc::new(false.into()),
1184 hdr_texture,
1185 msaa_render_target,
1186 msaa_sample_count: Arc::new(multisample_count.into()),
1187 clear_color_attachments: Arc::new(true.into()),
1188 clear_depth_attachments: Arc::new(true.into()),
1189 background_color: Arc::new(RwLock::new(wgpu::Color::TRANSPARENT)),
1190 }
1191 }
1192
1193 pub fn set_background_color(&self, color: impl Into<Vec4>) {
1194 let color = color.into();
1195 *self.background_color.write().unwrap() = wgpu::Color {
1196 r: color.x as f64,
1197 g: color.y as f64,
1198 b: color.z as f64,
1199 a: color.w as f64,
1200 };
1201 }
1202
1203 pub fn with_background_color(self, color: impl Into<Vec4>) -> Self {
1204 self.set_background_color(color);
1205 self
1206 }
1207
1208 pub fn get_msaa_sample_count(&self) -> u32 {
1210 self.msaa_sample_count
1211 .load(std::sync::atomic::Ordering::Relaxed)
1212 }
1213
1214 pub fn set_msaa_sample_count(&self, multisample_count: u32) {
1219 let multisample_count = multisample_count.max(1);
1220 let prev_multisample_count = self
1221 .msaa_sample_count
1222 .swap(multisample_count, Ordering::Relaxed);
1223 if prev_multisample_count == multisample_count {
1224 log::warn!("set_multisample_count: multisample count is unchanged, noop");
1225 return;
1226 }
1227
1228 log::debug!("setting multisample count to {multisample_count}");
1229 *self.primitive_pipeline.write().unwrap() = Self::create_primitive_pipeline(
1231 self.device(),
1232 wgpu::TextureFormat::Rgba16Float,
1233 multisample_count,
1234 );
1235 let size = self.get_size();
1236 *self.depth_texture.write().unwrap() = Texture::create_depth_texture(
1238 self.device(),
1239 size.x,
1240 size.y,
1241 multisample_count,
1242 Some("stage-depth"),
1243 );
1244 let format = self.hdr_texture.read().unwrap().texture.format();
1246 *self.msaa_render_target.write().unwrap() = if multisample_count == 1 {
1247 None
1248 } else {
1249 Some(create_msaa_textureview(
1250 self.device(),
1251 size.x,
1252 size.y,
1253 format,
1254 multisample_count,
1255 ))
1256 };
1257
1258 let _ = self.textures_bindgroup.lock().unwrap().take();
1260 }
1261
1262 pub fn with_msaa_sample_count(self, multisample_count: u32) -> Self {
1267 self.set_msaa_sample_count(multisample_count);
1268 self
1269 }
1270
1271 pub fn set_clear_color_attachments(&self, should_clear: bool) {
1273 self.clear_color_attachments
1274 .store(should_clear, Ordering::Relaxed);
1275 }
1276
1277 pub fn with_clear_color_attachments(self, should_clear: bool) -> Self {
1279 self.set_clear_color_attachments(should_clear);
1280 self
1281 }
1282
1283 pub fn set_clear_depth_attachments(&self, should_clear: bool) {
1285 self.clear_depth_attachments
1286 .store(should_clear, Ordering::Relaxed);
1287 }
1288
1289 pub fn with_clear_depth_attachments(self, should_clear: bool) -> Self {
1291 self.set_clear_depth_attachments(should_clear);
1292 self
1293 }
1294
1295 pub fn set_debug_mode(&self, debug_mode: DebugChannel) {
1297 self.geometry
1298 .descriptor()
1299 .modify(|cfg| cfg.debug_channel = debug_mode);
1300 }
1301
1302 pub fn with_debug_mode(self, debug_mode: DebugChannel) -> Self {
1304 self.set_debug_mode(debug_mode);
1305 self
1306 }
1307
1308 pub fn set_use_debug_overlay(&self, use_debug_overlay: bool) {
1310 self.has_debug_overlay
1311 .store(use_debug_overlay, std::sync::atomic::Ordering::Relaxed);
1312 }
1313
1314 pub fn with_debug_overlay(self, use_debug_overlay: bool) -> Self {
1316 self.set_use_debug_overlay(use_debug_overlay);
1317 self
1318 }
1319
1320 pub fn set_use_frustum_culling(&self, use_frustum_culling: bool) {
1324 self.geometry
1325 .descriptor()
1326 .modify(|cfg| cfg.perform_frustum_culling = use_frustum_culling);
1327 }
1328
1329 pub fn with_frustum_culling(self, use_frustum_culling: bool) -> Self {
1331 self.set_use_frustum_culling(use_frustum_culling);
1332 self
1333 }
1334
1335 pub fn set_use_occlusion_culling(&self, use_occlusion_culling: bool) {
1343 self.geometry
1344 .descriptor()
1345 .modify(|cfg| cfg.perform_occlusion_culling = use_occlusion_culling);
1346 }
1347
1348 pub fn with_occlusion_culling(self, use_occlusion_culling: bool) -> Self {
1350 self.set_use_occlusion_culling(use_occlusion_culling);
1351 self
1352 }
1353
1354 pub fn set_has_lighting(&self, use_lighting: bool) {
1356 self.geometry
1357 .descriptor()
1358 .modify(|cfg| cfg.has_lighting = use_lighting);
1359 }
1360
1361 pub fn with_lighting(self, use_lighting: bool) -> Self {
1363 self.set_has_lighting(use_lighting);
1364 self
1365 }
1366
1367 pub fn set_has_vertex_skinning(&self, use_skinning: bool) {
1369 self.geometry
1370 .descriptor()
1371 .modify(|cfg| cfg.has_skinning = use_skinning);
1372 }
1373
1374 pub fn with_vertex_skinning(self, use_skinning: bool) -> Self {
1376 self.set_has_vertex_skinning(use_skinning);
1377 self
1378 }
1379
1380 pub fn get_size(&self) -> UVec2 {
1381 let hdr = self.hdr_texture.read().unwrap();
1383 let w = hdr.width();
1384 let h = hdr.height();
1385 UVec2::new(w, h)
1386 }
1387
1388 pub fn set_size(&self, size: UVec2) {
1389 if size == self.get_size() {
1390 return;
1391 }
1392
1393 self.geometry
1394 .descriptor()
1395 .modify(|cfg| cfg.resolution = size);
1396 let hdr_texture = Texture::create_hdr_texture(self.device(), size.x, size.y, 1);
1397 let sample_count = self.msaa_sample_count.load(Ordering::Relaxed);
1398 if let Some(msaa_view) = self.msaa_render_target.write().unwrap().as_mut() {
1399 *msaa_view = create_msaa_textureview(
1400 self.device(),
1401 size.x,
1402 size.y,
1403 hdr_texture.texture.format(),
1404 sample_count,
1405 );
1406 }
1407
1408 *self.depth_texture.write().unwrap() = Texture::create_depth_texture(
1410 self.device(),
1411 size.x,
1412 size.y,
1413 sample_count,
1414 Some("stage-depth"),
1415 );
1416 self.bloom.set_hdr_texture(self.runtime(), &hdr_texture);
1417 self.tonemapping
1418 .set_hdr_texture(self.device(), &hdr_texture);
1419 *self.hdr_texture.write().unwrap() = hdr_texture;
1420
1421 let _ = self.skybox_bindgroup.lock().unwrap().take();
1422 let _ = self.textures_bindgroup.lock().unwrap().take();
1423 }
1424
1425 pub fn with_size(self, size: UVec2) -> Self {
1426 self.set_size(size);
1427 self
1428 }
1429
1430 pub fn set_has_bloom(&self, has_bloom: bool) {
1432 self.has_bloom
1433 .store(has_bloom, std::sync::atomic::Ordering::Relaxed);
1434 }
1435
1436 pub fn with_bloom(self, has_bloom: bool) -> Self {
1438 self.set_has_bloom(has_bloom);
1439 self
1440 }
1441
1442 pub fn set_bloom_mix_strength(&self, strength: f32) {
1446 self.bloom.set_mix_strength(strength);
1447 }
1448
1449 pub fn with_bloom_mix_strength(self, strength: f32) -> Self {
1450 self.set_bloom_mix_strength(strength);
1451 self
1452 }
1453
1454 pub fn set_bloom_filter_radius(&self, filter_radius: f32) {
1458 self.bloom.set_filter_radius(filter_radius);
1459 }
1460
1461 pub fn with_bloom_filter_radius(self, filter_radius: f32) -> Self {
1465 self.set_bloom_filter_radius(filter_radius);
1466 self
1467 }
1468
1469 pub fn add_primitive(&self, primitive: &Primitive) -> usize {
1478 let mut draws = self.draw_calls.write().unwrap();
1480 draws.add_primitive(primitive)
1481 }
1482
1483 pub fn remove_primitive(&self, primitive: &Primitive) -> usize {
1488 let mut draws = self.draw_calls.write().unwrap();
1489 draws.remove_primitive(primitive)
1490 }
1491
1492 pub fn sort_primitive(&self, f: impl Fn(&Primitive, &Primitive) -> std::cmp::Ordering) {
1496 let mut guard = self.draw_calls.write().unwrap();
1498 guard.sort_primitives(f);
1499 }
1500
1501 pub fn get_depth_texture(&self) -> DepthTexture {
1503 DepthTexture {
1504 runtime: self.runtime().clone(),
1505 texture: self.depth_texture.read().unwrap().texture.clone(),
1506 }
1507 }
1508
1509 pub fn new_nested_transform(&self) -> NestedTransform {
1511 NestedTransform::new(self.geometry.slab_allocator())
1512 }
1513
1514 pub fn render(&self, view: &wgpu::TextureView) {
1516 let background_color = *self.background_color.read().unwrap();
1518 let msaa_target = self.msaa_render_target.read().unwrap();
1520 let clear_colors = self.clear_color_attachments.load(Ordering::Relaxed);
1521 let hdr_texture = self.hdr_texture.read().unwrap();
1522
1523 let mk_ops = |store| wgpu::Operations {
1524 load: if clear_colors {
1525 wgpu::LoadOp::Clear(background_color)
1526 } else {
1527 wgpu::LoadOp::Load
1528 },
1529 store,
1530 };
1531 let render_pass_color_attachment = if let Some(msaa_view) = msaa_target.as_ref() {
1532 wgpu::RenderPassColorAttachment {
1533 ops: mk_ops(wgpu::StoreOp::Discard),
1534 view: msaa_view,
1535 resolve_target: Some(&hdr_texture.view),
1536 depth_slice: None,
1537 }
1538 } else {
1539 wgpu::RenderPassColorAttachment {
1540 ops: mk_ops(wgpu::StoreOp::Store),
1541 view: &hdr_texture.view,
1542 resolve_target: None,
1543 depth_slice: None,
1544 }
1545 };
1546
1547 let depth_texture = self.depth_texture.read().unwrap();
1548 let clear_depth = self.clear_depth_attachments.load(Ordering::Relaxed);
1549 let render_pass_depth_attachment = wgpu::RenderPassDepthStencilAttachment {
1550 view: &depth_texture.view,
1551 depth_ops: Some(wgpu::Operations {
1552 load: if clear_depth {
1553 wgpu::LoadOp::Clear(1.0)
1554 } else {
1555 wgpu::LoadOp::Load
1556 },
1557 store: wgpu::StoreOp::Store,
1558 }),
1559 stencil_ops: None,
1560 };
1561 let pipeline_guard = self.primitive_pipeline.read().unwrap();
1562 let (_submission_index, maybe_indirect_buffer) = StageRendering {
1563 pipeline: &pipeline_guard,
1564 stage: self,
1565 color_attachment: render_pass_color_attachment,
1566 depth_stencil_attachment: render_pass_depth_attachment,
1567 }
1568 .run();
1569
1570 if self.has_bloom.load(Ordering::Relaxed) {
1572 self.bloom.bloom(self.device(), self.queue());
1573 } else {
1574 let mut encoder =
1576 self.device()
1577 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1578 label: Some("no bloom copy"),
1579 });
1580 let bloom_mix_texture = self.bloom.get_mix_texture();
1581 encoder.copy_texture_to_texture(
1582 wgpu::TexelCopyTextureInfo {
1583 texture: &self.hdr_texture.read().unwrap().texture,
1584 mip_level: 0,
1585 origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
1586 aspect: wgpu::TextureAspect::All,
1587 },
1588 wgpu::TexelCopyTextureInfo {
1589 texture: &bloom_mix_texture.texture,
1590 mip_level: 0,
1591 origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
1592 aspect: wgpu::TextureAspect::All,
1593 },
1594 wgpu::Extent3d {
1595 width: bloom_mix_texture.width(),
1596 height: bloom_mix_texture.height(),
1597 depth_or_array_layers: 1,
1598 },
1599 );
1600 self.queue().submit(std::iter::once(encoder.finish()));
1601 }
1602
1603 self.tonemapping.render(self.device(), self.queue(), view);
1605
1606 if self.has_debug_overlay.load(Ordering::Relaxed) {
1608 if let Some(indirect_draw_buffer) = maybe_indirect_buffer {
1609 self.debug_overlay.render(
1610 self.device(),
1611 self.queue(),
1612 view,
1613 &self.stage_slab_buffer.read().unwrap(),
1614 &indirect_draw_buffer,
1615 );
1616 }
1617 }
1618 }
1619}
1620
1621#[cfg(test)]
1622mod test {
1623 use craballoc::{runtime::CpuRuntime, slab::SlabAllocator};
1624 use crabslab::{Array, Id, Slab};
1625 use glam::{Mat4, Vec2, Vec3, Vec4};
1626
1627 use crate::{
1628 context::Context,
1629 geometry::{shader::GeometryDescriptor, Geometry, Vertex},
1630 test::BlockOnFuture,
1631 transform::NestedTransform,
1632 };
1633
1634 #[test]
1635 fn vertex_slab_roundtrip() {
1636 let initial_vertices = {
1637 let tl = Vertex::default()
1638 .with_position(Vec3::ZERO)
1639 .with_uv0(Vec2::ZERO);
1640 let tr = Vertex::default()
1641 .with_position(Vec3::new(1.0, 0.0, 0.0))
1642 .with_uv0(Vec2::new(1.0, 0.0));
1643 let bl = Vertex::default()
1644 .with_position(Vec3::new(0.0, 1.0, 0.0))
1645 .with_uv0(Vec2::new(0.0, 1.0));
1646 let br = Vertex::default()
1647 .with_position(Vec3::new(1.0, 1.0, 0.0))
1648 .with_uv0(Vec2::splat(1.0));
1649 vec![tl, bl, br, tl, br, tr]
1650 };
1651 let mut slab = [0u32; 256];
1652 slab.write_indexed_slice(&initial_vertices, 0);
1653 let vertices = slab.read_vec(Array::<Vertex>::new(0, initial_vertices.len() as u32));
1654 pretty_assertions::assert_eq!(initial_vertices, vertices);
1655 }
1656
1657 #[test]
1658 fn matrix_subtraction_sanity() {
1659 let m = Mat4::IDENTITY - Mat4::IDENTITY;
1660 assert_eq!(Mat4::ZERO, m);
1661 }
1662
1663 #[test]
1664 fn can_global_transform_calculation() {
1665 #[expect(
1666 clippy::needless_borrows_for_generic_args,
1667 reason = "This is just riff-raff, as it doesn't compile without the borrow."
1668 )]
1669 let slab = SlabAllocator::<CpuRuntime>::new(&CpuRuntime, "transform", ());
1670 let root = NestedTransform::new(&slab);
1672 let child = NestedTransform::new(&slab).with_local_translation(Vec3::new(1.0, 0.0, 0.0));
1673 let grandchild =
1674 NestedTransform::new(&slab).with_local_translation(Vec3::new(1.0, 0.0, 0.0));
1675 log::info!("hierarchy");
1676 root.add_child(&child);
1678 child.add_child(&grandchild);
1679
1680 log::info!("get_global_transform");
1681 let grandchild_global_transform = grandchild.global_descriptor();
1683
1684 assert_eq!(
1686 grandchild_global_transform.translation.x, 2.0,
1687 "Grandchild's global translation should 2.0 along the x-axis"
1688 );
1689 }
1690
1691 #[test]
1692 fn can_msaa() {
1693 let ctx = Context::headless(100, 100).block();
1694 let stage = ctx
1695 .new_stage()
1696 .with_background_color([1.0, 1.0, 1.0, 1.0])
1697 .with_lighting(false);
1698 let (projection, view) = crate::camera::default_ortho2d(100.0, 100.0);
1699 let _camera = stage
1700 .new_camera()
1701 .with_projection_and_view(projection, view);
1702 let _triangle_rez = stage.new_primitive().with_vertices(
1703 stage.new_vertices([
1704 Vertex::default()
1705 .with_position([10.0, 10.0, 0.0])
1706 .with_color([0.0, 1.0, 1.0, 1.0]),
1707 Vertex::default()
1708 .with_position([10.0, 90.0, 0.0])
1709 .with_color([1.0, 1.0, 0.0, 1.0]),
1710 Vertex::default()
1711 .with_position([90.0, 10.0, 0.0])
1712 .with_color([1.0, 0.0, 1.0, 1.0]),
1713 ]),
1714 );
1715
1716 log::debug!("rendering without msaa");
1717 let frame = ctx.get_next_frame().unwrap();
1718 stage.render(&frame.view());
1719 let img = frame.read_image().block().unwrap();
1720 img_diff::assert_img_eq_cfg(
1721 "msaa/without.png",
1722 img,
1723 img_diff::DiffCfg {
1724 pixel_threshold: img_diff::LOW_PIXEL_THRESHOLD,
1725 ..Default::default()
1726 },
1727 );
1728 frame.present();
1729 log::debug!(" all good!");
1730
1731 stage.set_msaa_sample_count(4);
1732 log::debug!("rendering with msaa");
1733 let frame = ctx.get_next_frame().unwrap();
1734 stage.render(&frame.view());
1735 let img = frame.read_image().block().unwrap();
1736 img_diff::assert_img_eq_cfg(
1737 "msaa/with.png",
1738 img,
1739 img_diff::DiffCfg {
1740 pixel_threshold: img_diff::LOW_PIXEL_THRESHOLD,
1741 ..Default::default()
1742 },
1743 );
1744 frame.present();
1745 }
1746
1747 #[test]
1748 fn stage_geometry_desc_sanity() {
1751 let ctx = Context::headless(100, 100).block();
1752 let stage = ctx.new_stage();
1753 let _ = stage.commit();
1754
1755 let slab = futures_lite::future::block_on({
1756 let geometry: &Geometry = stage.as_ref();
1757 geometry.slab_allocator().read(..)
1758 })
1759 .unwrap();
1760 let pbr_desc = slab.read_unchecked(Id::<GeometryDescriptor>::new(0));
1761 pretty_assertions::assert_eq!(stage.geometry_descriptor().get(), pbr_desc);
1762 }
1763
1764 #[test]
1765 fn slabbed_vertices_native() {
1766 let ctx = Context::headless(100, 100).block();
1767 let runtime = ctx.as_ref();
1768
1769 let slab = SlabAllocator::new(
1771 runtime,
1772 "slabbed_isosceles_triangle",
1773 wgpu::BufferUsages::empty(),
1774 );
1775
1776 let geometry = vec![
1777 (Vec3::new(0.5, -0.5, 0.0), Vec4::new(1.0, 0.0, 0.0, 1.0)),
1778 (Vec3::new(0.0, 0.5, 0.0), Vec4::new(0.0, 1.0, 0.0, 1.0)),
1779 (Vec3::new(-0.5, -0.5, 0.0), Vec4::new(0.0, 0.0, 1.0, 1.0)),
1780 (Vec3::new(-1.0, 1.0, 0.0), Vec4::new(1.0, 0.0, 0.0, 1.0)),
1781 (Vec3::new(-1.0, 0.0, 0.0), Vec4::new(0.0, 1.0, 0.0, 1.0)),
1782 (Vec3::new(0.0, 1.0, 0.0), Vec4::new(0.0, 0.0, 1.0, 1.0)),
1783 ];
1784 let vertices = slab.new_array(geometry);
1785 let array = slab.new_value(vertices.array());
1786
1787 let bindgroup_layout =
1789 runtime
1790 .device
1791 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1792 label: None,
1793 entries: &[wgpu::BindGroupLayoutEntry {
1794 binding: 0,
1795 visibility: wgpu::ShaderStages::VERTEX,
1796 ty: wgpu::BindingType::Buffer {
1797 ty: wgpu::BufferBindingType::Storage { read_only: true },
1798 has_dynamic_offset: false,
1799 min_binding_size: None,
1800 },
1801 count: None,
1802 }],
1803 });
1804 let pipeline_layout =
1805 runtime
1806 .device
1807 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1808 label: None,
1809 bind_group_layouts: &[&bindgroup_layout],
1810 push_constant_ranges: &[],
1811 });
1812
1813 let vertex = crate::linkage::slabbed_vertices::linkage(&runtime.device);
1814 let fragment = crate::linkage::passthru_fragment::linkage(&runtime.device);
1815 let pipeline = runtime
1816 .device
1817 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1818 label: None,
1819 cache: None,
1820 layout: Some(&pipeline_layout),
1821 vertex: wgpu::VertexState {
1822 compilation_options: wgpu::PipelineCompilationOptions::default(),
1823 module: &vertex.module,
1824 entry_point: Some(vertex.entry_point),
1825 buffers: &[],
1826 },
1827 primitive: wgpu::PrimitiveState {
1828 topology: wgpu::PrimitiveTopology::TriangleList,
1829 strip_index_format: None,
1830 front_face: wgpu::FrontFace::Ccw,
1831 cull_mode: None,
1832 unclipped_depth: false,
1833 polygon_mode: wgpu::PolygonMode::Fill,
1834 conservative: false,
1835 },
1836 depth_stencil: None,
1837 multisample: wgpu::MultisampleState {
1838 mask: !0,
1839 alpha_to_coverage_enabled: false,
1840 count: 1,
1841 },
1842 fragment: Some(wgpu::FragmentState {
1843 compilation_options: Default::default(),
1844 module: &fragment.module,
1845 entry_point: Some(fragment.entry_point),
1846 targets: &[Some(wgpu::ColorTargetState {
1847 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1848 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1849 write_mask: wgpu::ColorWrites::ALL,
1850 })],
1851 }),
1852 multiview: None,
1853 });
1854 let slab_buffer = slab.commit();
1855
1856 let bindgroup = runtime
1857 .device
1858 .create_bind_group(&wgpu::BindGroupDescriptor {
1859 label: None,
1860 layout: &bindgroup_layout,
1861 entries: &[wgpu::BindGroupEntry {
1862 binding: 0,
1863 resource: slab_buffer.as_entire_binding(),
1864 }],
1865 });
1866
1867 let frame = ctx.get_next_frame().unwrap();
1868 let mut encoder = runtime.device.create_command_encoder(&Default::default());
1869 {
1870 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1871 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1872 view: &frame.view(),
1873 resolve_target: None,
1874 ops: wgpu::Operations {
1875 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
1876 store: wgpu::StoreOp::Store,
1877 },
1878 depth_slice: None,
1879 })],
1880 ..Default::default()
1881 });
1882 render_pass.set_pipeline(&pipeline);
1883 render_pass.set_bind_group(0, &bindgroup, &[]);
1884 let id = array.id().inner();
1885 render_pass.draw(0..vertices.len() as u32, id..id + 1);
1886 }
1887 runtime.queue.submit(std::iter::once(encoder.finish()));
1888
1889 let img = frame
1890 .read_linear_image()
1891 .block()
1892 .expect("could not read frame");
1893 img_diff::assert_img_eq("tutorial/slabbed_isosceles_triangle.png", img);
1894 }
1895}