1use core::{
3 ops::Deref,
4 sync::atomic::{AtomicU32, AtomicUsize, Ordering},
5};
6use craballoc::prelude::*;
7use crabslab::Id;
8use glam::{Mat4, UVec2, Vec4};
9use snafu::{ResultExt, Snafu};
10use std::sync::{atomic::AtomicBool, Arc, Mutex, RwLock};
11
12#[cfg(gltf)]
13use crate::gltf::GltfDocument;
14use crate::{
15 atlas::{AtlasError, AtlasImage, AtlasImageError, AtlasTexture},
16 bindgroup::ManagedBindGroup,
17 bloom::Bloom,
18 camera::{shader::CameraDescriptor, Camera},
19 debug::DebugOverlay,
20 draw::DrawCalls,
21 geometry::{
22 shader::GeometryDescriptor, Geometry, Indices, MorphTarget, MorphTargetWeights,
23 MorphTargets, Skin, SkinJoint, Vertex, Vertices,
24 },
25 light::{
26 AnalyticalLight, DirectionalLight, IsLight, Light, LightTiling, LightTilingConfig,
27 Lighting, LightingBindGroupLayoutEntries, LightingError, PointLight, ShadowMap, SpotLight,
28 },
29 material::{Material, Materials},
30 pbr::{brdf::BrdfLut, debug::DebugChannel, ibl::Ibl},
31 primitive::Primitive,
32 skybox::{Skybox, SkyboxRenderPipeline},
33 texture::{DepthTexture, Texture},
34 tonemapping::Tonemapping,
35 transform::{NestedTransform, Transform},
36};
37
38#[derive(Debug, Snafu)]
40pub enum StageError {
41 #[snafu(display("{source}"))]
42 Atlas { source: AtlasError },
43
44 #[snafu(display("{source}"))]
45 Lighting { source: LightingError },
46
47 #[cfg(gltf)]
48 #[snafu(display("{source}"))]
49 Gltf { source: crate::gltf::StageGltfError },
50}
51
52impl From<AtlasError> for StageError {
53 fn from(source: AtlasError) -> Self {
54 Self::Atlas { source }
55 }
56}
57
58impl From<LightingError> for StageError {
59 fn from(source: LightingError) -> Self {
60 Self::Lighting { source }
61 }
62}
63
64#[cfg(gltf)]
65impl From<crate::gltf::StageGltfError> for StageError {
66 fn from(source: crate::gltf::StageGltfError) -> Self {
67 Self::Gltf { source }
68 }
69}
70
71fn create_msaa_textureview(
72 device: &wgpu::Device,
73 width: u32,
74 height: u32,
75 format: wgpu::TextureFormat,
76 sample_count: u32,
77) -> wgpu::TextureView {
78 device
79 .create_texture(&wgpu::TextureDescriptor {
80 label: Some("stage msaa render target"),
81 size: wgpu::Extent3d {
82 width,
83 height,
84 depth_or_array_layers: 1,
85 },
86 mip_level_count: 1,
87 sample_count,
88 dimension: wgpu::TextureDimension::D2,
89 format,
90 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
91 view_formats: &[],
92 })
93 .create_view(&wgpu::TextureViewDescriptor::default())
94}
95
96pub struct StageCommitResult {
98 pub(crate) geometry_buffer: SlabBuffer<wgpu::Buffer>,
99 pub(crate) lighting_buffer: SlabBuffer<wgpu::Buffer>,
100 pub(crate) materials_buffer: SlabBuffer<wgpu::Buffer>,
101}
102
103impl StageCommitResult {
104 pub(crate) fn latest_creation_time(&self) -> usize {
106 [
107 &self.geometry_buffer,
108 &self.materials_buffer,
109 &self.lighting_buffer,
110 ]
111 .iter()
112 .map(|buffer| buffer.creation_time())
113 .max()
114 .unwrap_or_default()
115 }
116
117 pub(crate) fn should_invalidate(&self, previous_creation_time: usize) -> bool {
120 let mut should = false;
121 if self.geometry_buffer.is_new_this_commit() {
122 log::trace!("geometry buffer is new this frame");
123 should = true;
124 }
125 if self.materials_buffer.is_new_this_commit() {
126 log::trace!("materials buffer is new this frame");
127 should = true;
128 }
129 if self.lighting_buffer.is_new_this_commit() {
130 log::trace!("lighting buffer is new this frame");
131 should = true;
132 }
133 let current = self.latest_creation_time();
134 if current > previous_creation_time {
135 log::trace!(
136 "current latest buffer creation time {current} > previous {previous_creation_time}"
137 );
138 should = true;
139 }
140 should
141 }
142}
143
144struct PrimitiveBindGroup<'a> {
150 device: &'a wgpu::Device,
151 layout: &'a wgpu::BindGroupLayout,
152 geometry_buffer: &'a wgpu::Buffer,
153 material_buffer: &'a wgpu::Buffer,
154 light_buffer: &'a wgpu::Buffer,
155 atlas_texture_view: &'a wgpu::TextureView,
156 atlas_texture_sampler: &'a wgpu::Sampler,
157 irradiance_texture_view: &'a wgpu::TextureView,
158 irradiance_texture_sampler: &'a wgpu::Sampler,
159 prefiltered_texture_view: &'a wgpu::TextureView,
160 prefiltered_texture_sampler: &'a wgpu::Sampler,
161 brdf_texture_view: &'a wgpu::TextureView,
162 brdf_texture_sampler: &'a wgpu::Sampler,
163 shadow_map_texture_view: &'a wgpu::TextureView,
164 shadow_map_texture_sampler: &'a wgpu::Sampler,
165}
166
167impl PrimitiveBindGroup<'_> {
168 pub fn create(self) -> wgpu::BindGroup {
169 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
170 label: Some("primitive"),
171 layout: self.layout,
172 entries: &[
173 wgpu::BindGroupEntry {
174 binding: 0,
175 resource: self.geometry_buffer.as_entire_binding(),
176 },
177 wgpu::BindGroupEntry {
178 binding: 1,
179 resource: self.material_buffer.as_entire_binding(),
180 },
181 wgpu::BindGroupEntry {
182 binding: 2,
183 resource: wgpu::BindingResource::TextureView(self.atlas_texture_view),
184 },
185 wgpu::BindGroupEntry {
186 binding: 3,
187 resource: wgpu::BindingResource::Sampler(self.atlas_texture_sampler),
188 },
189 wgpu::BindGroupEntry {
190 binding: 4,
191 resource: wgpu::BindingResource::TextureView(self.irradiance_texture_view),
192 },
193 wgpu::BindGroupEntry {
194 binding: 5,
195 resource: wgpu::BindingResource::Sampler(self.irradiance_texture_sampler),
196 },
197 wgpu::BindGroupEntry {
198 binding: 6,
199 resource: wgpu::BindingResource::TextureView(self.prefiltered_texture_view),
200 },
201 wgpu::BindGroupEntry {
202 binding: 7,
203 resource: wgpu::BindingResource::Sampler(self.prefiltered_texture_sampler),
204 },
205 wgpu::BindGroupEntry {
206 binding: 8,
207 resource: wgpu::BindingResource::TextureView(self.brdf_texture_view),
208 },
209 wgpu::BindGroupEntry {
210 binding: 9,
211 resource: wgpu::BindingResource::Sampler(self.brdf_texture_sampler),
212 },
213 wgpu::BindGroupEntry {
214 binding: 10,
215 resource: self.light_buffer.as_entire_binding(),
216 },
217 wgpu::BindGroupEntry {
218 binding: 11,
219 resource: wgpu::BindingResource::TextureView(self.shadow_map_texture_view),
220 },
221 wgpu::BindGroupEntry {
222 binding: 12,
223 resource: wgpu::BindingResource::Sampler(self.shadow_map_texture_sampler),
224 },
225 ],
226 })
227 }
228}
229
230pub(crate) struct StageRendering<'a> {
232 pub stage: &'a Stage,
234 pub pipeline: &'a wgpu::RenderPipeline,
235 pub color_attachment: wgpu::RenderPassColorAttachment<'a>,
236 pub depth_stencil_attachment: wgpu::RenderPassDepthStencilAttachment<'a>,
237}
238
239impl StageRendering<'_> {
240 pub fn run(self) -> (wgpu::SubmissionIndex, Option<SlabBuffer<wgpu::Buffer>>) {
245 let commit_result = self.stage.commit();
246 let current_primitive_bind_group_creation_time = commit_result.latest_creation_time();
247 log::trace!(
248 "current_primitive_bind_group_creation_time: \
249 {current_primitive_bind_group_creation_time}"
250 );
251 let previous_primitive_bind_group_creation_time =
252 self.stage.primitive_bind_group_created.swap(
253 current_primitive_bind_group_creation_time,
254 std::sync::atomic::Ordering::Relaxed,
255 );
256 let should_invalidate_primitive_bind_group =
257 commit_result.should_invalidate(previous_primitive_bind_group_creation_time);
258 log::trace!(
259 "should_invalidate_primitive_bind_group: {should_invalidate_primitive_bind_group}"
260 );
261 let primitive_bind_group =
262 self.stage
263 .primitive_bind_group
264 .get(should_invalidate_primitive_bind_group, || {
265 log::trace!("recreating primitive bind group");
266 let atlas_texture = self.stage.materials.atlas().get_texture();
267 let ibl = self.stage.ibl.read().expect("ibl read");
268 let shadow_map = self.stage.lighting.shadow_map_atlas.get_texture();
269 PrimitiveBindGroup {
270 device: self.stage.device(),
271 layout: &Stage::primitive_pipeline_bindgroup_layout(self.stage.device()),
272 geometry_buffer: &commit_result.geometry_buffer,
273 material_buffer: &commit_result.materials_buffer,
274 light_buffer: &commit_result.lighting_buffer,
275 atlas_texture_view: &atlas_texture.view,
276 atlas_texture_sampler: &atlas_texture.sampler,
277 irradiance_texture_view: &ibl.irradiance_cubemap.view,
278 irradiance_texture_sampler: &ibl.irradiance_cubemap.sampler,
279 prefiltered_texture_view: &ibl.prefiltered_environment_cubemap.view,
280 prefiltered_texture_sampler: &ibl.prefiltered_environment_cubemap.sampler,
281 brdf_texture_view: &self.stage.brdf_lut.inner.view,
282 brdf_texture_sampler: &self.stage.brdf_lut.inner.sampler,
283 shadow_map_texture_view: &shadow_map.view,
284 shadow_map_texture_sampler: &shadow_map.sampler,
285 }
286 .create()
287 });
288
289 let mut draw_calls = self.stage.draw_calls.write().expect("draw_calls write");
290 let depth_texture = self.stage.depth_texture.read().expect("depth_texture read");
291 let maybe_indirect_buffer = draw_calls.pre_draw(&depth_texture).unwrap();
294
295 log::trace!("rendering");
296 let label = Some("stage render");
297
298 let mut encoder = self
299 .stage
300 .device()
301 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label });
302 {
303 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
304 label,
305 color_attachments: &[Some(self.color_attachment)],
306 depth_stencil_attachment: Some(self.depth_stencil_attachment),
307 ..Default::default()
308 });
309
310 render_pass.set_pipeline(self.pipeline);
311 render_pass.set_bind_group(0, Some(primitive_bind_group.as_ref()), &[]);
312 draw_calls.draw(&mut render_pass);
313
314 let has_skybox = self.stage.has_skybox.load(Ordering::Relaxed);
315 if has_skybox {
316 let (pipeline, bindgroup) = self
317 .stage
318 .get_skybox_pipeline_and_bindgroup(&commit_result.geometry_buffer);
319 render_pass.set_pipeline(&pipeline.pipeline);
320 render_pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]);
321 let camera_id = self.stage.geometry.descriptor().get().camera_id.inner();
322 render_pass.draw(0..36, camera_id..camera_id + 1);
323 }
324 }
325 let sindex = self.stage.queue().submit(std::iter::once(encoder.finish()));
326 (sindex, maybe_indirect_buffer)
327 }
328}
329
330#[derive(Clone)]
397pub struct Stage {
398 pub(crate) geometry: Geometry,
399 pub(crate) materials: Materials,
400 pub(crate) lighting: Lighting,
401
402 pub(crate) primitive_pipeline: Arc<RwLock<wgpu::RenderPipeline>>,
403 pub(crate) primitive_bind_group: ManagedBindGroup,
404 pub(crate) primitive_bind_group_created: Arc<AtomicUsize>,
405
406 pub(crate) skybox_pipeline: Arc<RwLock<Option<Arc<SkyboxRenderPipeline>>>>,
407
408 pub(crate) hdr_texture: Arc<RwLock<Texture>>,
409 pub(crate) depth_texture: Arc<RwLock<Texture>>,
410 pub(crate) msaa_render_target: Arc<RwLock<Option<wgpu::TextureView>>>,
411 pub(crate) msaa_sample_count: Arc<AtomicU32>,
412 pub(crate) clear_color_attachments: Arc<AtomicBool>,
413 pub(crate) clear_depth_attachments: Arc<AtomicBool>,
414
415 pub(crate) bloom: Bloom,
416
417 pub(crate) tonemapping: Tonemapping,
418 pub(crate) debug_overlay: DebugOverlay,
419 pub(crate) background_color: Arc<RwLock<wgpu::Color>>,
420
421 pub(crate) brdf_lut: BrdfLut,
422
423 pub(crate) ibl: Arc<RwLock<Ibl>>,
424
425 pub(crate) skybox: Arc<RwLock<Skybox>>,
426 pub(crate) skybox_bindgroup: Arc<Mutex<Option<Arc<wgpu::BindGroup>>>>,
427 pub(crate) has_skybox: Arc<AtomicBool>,
429
430 pub(crate) has_bloom: Arc<AtomicBool>,
431 pub(crate) has_debug_overlay: Arc<AtomicBool>,
432
433 pub(crate) stage_slab_buffer: Arc<RwLock<SlabBuffer<wgpu::Buffer>>>,
434
435 pub(crate) textures_bindgroup: Arc<Mutex<Option<Arc<wgpu::BindGroup>>>>,
436
437 pub(crate) draw_calls: Arc<RwLock<DrawCalls>>,
438}
439
440impl AsRef<WgpuRuntime> for Stage {
441 fn as_ref(&self) -> &WgpuRuntime {
442 self.geometry.as_ref()
443 }
444}
445
446impl AsRef<Geometry> for Stage {
447 fn as_ref(&self) -> &Geometry {
448 &self.geometry
449 }
450}
451
452impl AsRef<Materials> for Stage {
453 fn as_ref(&self) -> &Materials {
454 &self.materials
455 }
456}
457
458impl AsRef<Lighting> for Stage {
459 fn as_ref(&self) -> &Lighting {
460 &self.lighting
461 }
462}
463
464#[cfg(gltf)]
465impl Stage {
467 pub fn load_gltf_document_from_path(
468 &self,
469 path: impl AsRef<std::path::Path>,
470 ) -> Result<GltfDocument, StageError> {
471 use snafu::ResultExt;
472
473 let (document, buffers, images) =
474 gltf::import(&path).with_context(|_| crate::gltf::GltfSnafu {
475 path: Some(path.as_ref().to_path_buf()),
476 })?;
477 GltfDocument::from_gltf(self, &document, buffers, images)
478 }
479
480 pub fn load_gltf_document_from_bytes(
481 &self,
482 bytes: impl AsRef<[u8]>,
483 ) -> Result<GltfDocument, StageError> {
484 let (document, buffers, images) =
485 gltf::import_slice(bytes).context(crate::gltf::GltfSnafu { path: None })?;
486 GltfDocument::from_gltf(self, &document, buffers, images)
487 }
488}
489
490impl Stage {
492 pub fn default_vertices(&self) -> &Vertices {
497 self.geometry.default_vertices()
498 }
499
500 pub fn new_camera(&self) -> Camera {
505 self.geometry.new_camera()
506 }
507
508 pub fn use_camera(&self, camera: impl AsRef<Camera>) {
510 self.geometry.use_camera(camera.as_ref());
511 }
512
513 pub fn used_camera_id(&self) -> Id<CameraDescriptor> {
515 self.geometry.descriptor().get().camera_id
516 }
517
518 pub fn use_camera_id(&self, camera_id: Id<CameraDescriptor>) {
520 self.geometry
521 .descriptor()
522 .modify(|desc| desc.camera_id = camera_id);
523 }
524
525 pub fn new_transform(&self) -> Transform {
527 self.geometry.new_transform()
528 }
529
530 pub fn new_vertices(&self, data: impl IntoIterator<Item = Vertex>) -> Vertices {
532 self.geometry.new_vertices(data)
533 }
534
535 pub fn new_indices(&self, data: impl IntoIterator<Item = u32>) -> Indices {
537 self.geometry.new_indices(data)
538 }
539
540 pub fn new_morph_targets(
542 &self,
543 data: impl IntoIterator<Item = Vec<MorphTarget>>,
544 ) -> MorphTargets {
545 self.geometry.new_morph_targets(data)
546 }
547
548 pub fn new_morph_target_weights(
550 &self,
551 data: impl IntoIterator<Item = f32>,
552 ) -> MorphTargetWeights {
553 self.geometry.new_morph_target_weights(data)
554 }
555
556 pub fn new_skin(
558 &self,
559 joints: impl IntoIterator<Item = impl Into<SkinJoint>>,
560 inverse_bind_matrices: impl IntoIterator<Item = impl Into<Mat4>>,
561 ) -> Skin {
562 self.geometry.new_skin(joints, inverse_bind_matrices)
563 }
564
565 pub fn new_primitive(&self) -> Primitive {
578 Primitive::new(self)
579 }
580
581 pub fn geometry_descriptor(&self) -> &Hybrid<GeometryDescriptor> {
584 self.geometry.descriptor()
585 }
586}
587
588impl Stage {
590 pub fn default_material(&self) -> &Material {
594 self.materials.default_material()
595 }
596
597 pub fn new_material(&self) -> Material {
601 self.materials.new_material()
602 }
603
604 pub fn set_atlas_size(&self, size: wgpu::Extent3d) -> Result<(), StageError> {
608 log::info!("resizing atlas to {size:?}");
609 self.materials.atlas().resize(self.runtime(), size)?;
610 Ok(())
611 }
612
613 pub fn add_images(
625 &self,
626 images: impl IntoIterator<Item = impl Into<AtlasImage>>,
627 ) -> Result<Vec<AtlasTexture>, StageError> {
628 let images = images.into_iter().map(|i| i.into()).collect::<Vec<_>>();
629 let frames = self.materials.atlas().add_images(&images)?;
630
631 let _ = self
633 .textures_bindgroup
634 .lock()
635 .expect("textures_bindgroup lock")
636 .take();
637
638 Ok(frames)
639 }
640
641 pub fn clear_images(&self) -> Result<(), StageError> {
646 let none = Option::<AtlasImage>::None;
647 let _ = self.set_images(none)?;
648 Ok(())
649 }
650
651 pub fn set_images(
659 &self,
660 images: impl IntoIterator<Item = impl Into<AtlasImage>>,
661 ) -> Result<Vec<AtlasTexture>, StageError> {
662 let images = images.into_iter().map(|i| i.into()).collect::<Vec<_>>();
663 let frames = self.materials.atlas().set_images(&images)?;
664
665 let _ = self
667 .textures_bindgroup
668 .lock()
669 .expect("textures_bindgroup lock")
670 .take();
671
672 Ok(frames)
673 }
674}
675
676impl Stage {
678 pub fn new_directional_light(&self) -> AnalyticalLight<DirectionalLight> {
680 self.lighting.new_directional_light()
681 }
682
683 pub fn new_point_light(&self) -> AnalyticalLight<PointLight> {
685 self.lighting.new_point_light()
686 }
687
688 pub fn new_spot_light(&self) -> AnalyticalLight<SpotLight> {
690 self.lighting.new_spot_light()
691 }
692
693 pub fn add_light<T>(&self, bundle: &AnalyticalLight<T>)
700 where
701 T: IsLight,
702 Light: From<T>,
703 {
704 self.lighting.add_light(bundle)
705 }
706
707 pub fn remove_light<T: IsLight>(&self, bundle: &AnalyticalLight<T>) {
714 self.lighting.remove_light(bundle);
715 }
716
717 pub fn set_ambient_color(&self, color: Vec4) -> &Self {
725 self.lighting.set_ambient_color(color);
726 self
727 }
728
729 pub fn with_ambient_color(self, color: Vec4) -> Self {
731 self.set_ambient_color(color);
732 self
733 }
734
735 pub fn ambient_color(&self) -> Vec4 {
737 self.lighting.ambient_color()
738 }
739
740 pub fn new_shadow_map<T>(
756 &self,
757 analytical_light: &AnalyticalLight<T>,
758 size: UVec2,
760 z_near: f32,
764 z_far: f32,
768 ) -> Result<ShadowMap, StageError>
769 where
770 T: IsLight,
771 Light: From<T>,
772 {
773 Ok(self
774 .lighting
775 .new_shadow_map(analytical_light, size, z_near, z_far)?)
776 }
777
778 pub fn new_light_tiling(&self, config: LightTilingConfig) -> LightTiling {
780 let lighting = self.as_ref();
781 let multisampled = self.get_msaa_sample_count() > 1;
782 let depth_texture_size = self.get_depth_texture().size();
783 LightTiling::new(
784 lighting,
785 multisampled,
786 UVec2::new(depth_texture_size.width, depth_texture_size.height),
787 config,
788 )
789 }
790}
791
792impl Stage {
794 fn get_skybox_pipeline_and_bindgroup(
796 &self,
797 geometry_slab_buffer: &wgpu::Buffer,
798 ) -> (Arc<SkyboxRenderPipeline>, Arc<wgpu::BindGroup>) {
799 let msaa_sample_count = self.msaa_sample_count.load(Ordering::Relaxed);
800 let mut pipeline_guard = self.skybox_pipeline.write().expect("skybox_pipeline write");
802 let pipeline = if let Some(pipeline) = pipeline_guard.as_mut() {
803 if pipeline.msaa_sample_count() != msaa_sample_count {
804 *pipeline = Arc::new(crate::skybox::create_skybox_render_pipeline(
805 self.device(),
806 Texture::HDR_TEXTURE_FORMAT,
807 Some(msaa_sample_count),
808 ));
809 }
810 pipeline.clone()
811 } else {
812 let pipeline = Arc::new(crate::skybox::create_skybox_render_pipeline(
813 self.device(),
814 Texture::HDR_TEXTURE_FORMAT,
815 Some(msaa_sample_count),
816 ));
817 *pipeline_guard = Some(pipeline.clone());
818 pipeline
819 };
820 let mut bindgroup = self.skybox_bindgroup.lock().expect("skybox_bindgroup lock");
822 let bindgroup = if let Some(bindgroup) = bindgroup.as_ref() {
823 bindgroup.clone()
824 } else {
825 let bg = Arc::new(crate::skybox::create_skybox_bindgroup(
826 self.device(),
827 geometry_slab_buffer,
828 self.skybox
829 .read()
830 .expect("skybox read")
831 .environment_cubemap_texture(),
832 ));
833 *bindgroup = Some(bg.clone());
834 bg
835 };
836 (pipeline, bindgroup)
837 }
838
839 pub fn use_skybox(&self, skybox: &Skybox) -> &Self {
843 let mut guard = self.skybox.write().expect("skybox write");
845 *guard = skybox.clone();
846 self.has_skybox
847 .store(true, std::sync::atomic::Ordering::Relaxed);
848 *self.skybox_bindgroup.lock().expect("skybox_bindgroup lock") = None;
849 *self
850 .textures_bindgroup
851 .lock()
852 .expect("textures_bindgroup lock") = None;
853 self
854 }
855
856 pub fn remove_skybox(&self) -> Option<Skybox> {
863 let mut guard = self.skybox.write().expect("skybox write");
864 if guard.is_empty() {
865 None
867 } else {
868 let skybox = guard.clone();
869 *guard = Skybox::empty(self.runtime());
870 self.skybox_bindgroup
871 .lock()
872 .expect("skybox_bindgroup lock")
873 .take();
874 self.textures_bindgroup
875 .lock()
876 .expect("textures_bindgroup lock")
877 .take();
878 Some(skybox)
879 }
880 }
881
882 pub fn new_skybox_from_path(
887 &self,
888 path: impl AsRef<std::path::Path>,
889 ) -> Result<Skybox, AtlasImageError> {
890 let hdr = AtlasImage::from_hdr_path(path)?;
891 Ok(Skybox::new(self.runtime(), hdr))
892 }
893
894 pub fn new_skybox_from_bytes(&self, bytes: &[u8]) -> Result<Skybox, AtlasImageError> {
898 let hdr = AtlasImage::from_hdr_bytes(bytes)?;
899 Ok(Skybox::new(self.runtime(), hdr))
900 }
901}
902
903impl Stage {
905 pub fn new_ibl(&self, skybox: &Skybox) -> Ibl {
907 Ibl::new(self.runtime(), skybox)
908 }
909
910 pub fn use_ibl(&self, ibl: &Ibl) -> &Self {
914 let mut guard = self.ibl.write().expect("ibl write");
915 *guard = ibl.clone();
916 self.primitive_bind_group.invalidate();
917 self
918 }
919
920 pub fn remove_ibl(&self) -> Option<Ibl> {
923 let mut guard = self.ibl.write().expect("ibl write");
924 if guard.is_empty() {
925 None
927 } else {
928 let ibl = guard.clone();
929 *guard = Ibl::new(self.runtime(), &Skybox::empty(self.runtime()));
930 self.primitive_bind_group.invalidate();
931 Some(ibl)
932 }
933 }
934}
935
936impl Stage {
937 pub fn runtime(&self) -> &WgpuRuntime {
939 self.as_ref()
940 }
941
942 pub fn device(&self) -> &wgpu::Device {
943 &self.runtime().device
944 }
945
946 pub fn queue(&self) -> &wgpu::Queue {
947 &self.runtime().queue
948 }
949
950 pub fn brdf_lut(&self) -> &BrdfLut {
954 &self.brdf_lut
955 }
956
957 pub fn used_gpu_buffer_byte_size(&self) -> usize {
965 let num_u32s = self.geometry.slab_allocator().len()
966 + self.lighting.slab_allocator().len()
967 + self.materials.slab_allocator().len()
968 + self.bloom.slab_allocator().len()
969 + self.tonemapping.slab_allocator().len()
970 + self
971 .draw_calls
972 .read()
973 .unwrap()
974 .drawing_strategy()
975 .as_indirect()
976 .map(|draws| draws.slab_allocator().len())
977 .unwrap_or_default();
978 4 * num_u32s
979 }
980
981 pub fn hdr_texture(&self) -> impl Deref<Target = crate::texture::Texture> + '_ {
982 self.hdr_texture.read().expect("hdr_texture read")
983 }
984
985 #[must_use]
992 pub fn commit(&self) -> StageCommitResult {
993 let (materials_atlas_texture_was_recreated, materials_buffer) = self.materials.commit();
994 if materials_atlas_texture_was_recreated {
995 self.primitive_bind_group.invalidate();
996 }
997 let geometry_buffer = self.geometry.commit();
998 let lighting_buffer = self.lighting.commit();
999 StageCommitResult {
1000 geometry_buffer,
1001 lighting_buffer,
1002 materials_buffer,
1003 }
1004 }
1005
1006 fn primitive_pipeline_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
1007 let geometry_slab = wgpu::BindGroupLayoutEntry {
1008 binding: 0,
1009 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
1010 ty: wgpu::BindingType::Buffer {
1011 ty: wgpu::BufferBindingType::Storage { read_only: true },
1012 has_dynamic_offset: false,
1013 min_binding_size: None,
1014 },
1015 count: None,
1016 };
1017 let material_slab = wgpu::BindGroupLayoutEntry {
1018 binding: 1,
1019 visibility: wgpu::ShaderStages::FRAGMENT,
1020 ty: wgpu::BindingType::Buffer {
1021 ty: wgpu::BufferBindingType::Storage { read_only: true },
1022 has_dynamic_offset: false,
1023 min_binding_size: None,
1024 },
1025 count: None,
1026 };
1027
1028 fn image2d_entry(binding: u32) -> (wgpu::BindGroupLayoutEntry, wgpu::BindGroupLayoutEntry) {
1029 let img = wgpu::BindGroupLayoutEntry {
1030 binding,
1031 visibility: wgpu::ShaderStages::FRAGMENT,
1032 ty: wgpu::BindingType::Texture {
1033 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1034 view_dimension: wgpu::TextureViewDimension::D2,
1035 multisampled: false,
1036 },
1037 count: None,
1038 };
1039 let sampler = wgpu::BindGroupLayoutEntry {
1040 binding: binding + 1,
1041 visibility: wgpu::ShaderStages::FRAGMENT,
1042 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1043 count: None,
1044 };
1045 (img, sampler)
1046 }
1047
1048 fn cubemap_entry(binding: u32) -> (wgpu::BindGroupLayoutEntry, wgpu::BindGroupLayoutEntry) {
1049 let img = wgpu::BindGroupLayoutEntry {
1050 binding,
1051 visibility: wgpu::ShaderStages::FRAGMENT,
1052 ty: wgpu::BindingType::Texture {
1053 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1054 view_dimension: wgpu::TextureViewDimension::Cube,
1055 multisampled: false,
1056 },
1057 count: None,
1058 };
1059 let sampler = wgpu::BindGroupLayoutEntry {
1060 binding: binding + 1,
1061 visibility: wgpu::ShaderStages::FRAGMENT,
1062 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1063 count: None,
1064 };
1065 (img, sampler)
1066 }
1067
1068 let atlas = wgpu::BindGroupLayoutEntry {
1069 binding: 2,
1070 visibility: wgpu::ShaderStages::FRAGMENT,
1071 ty: wgpu::BindingType::Texture {
1072 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1073 view_dimension: wgpu::TextureViewDimension::D2Array,
1074 multisampled: false,
1075 },
1076 count: None,
1077 };
1078 let atlas_sampler = wgpu::BindGroupLayoutEntry {
1079 binding: 3,
1080 visibility: wgpu::ShaderStages::FRAGMENT,
1081 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1082 count: None,
1083 };
1084 let (irradiance, irradiance_sampler) = cubemap_entry(4);
1085 let (prefilter, prefilter_sampler) = cubemap_entry(6);
1086 let (brdf, brdf_sampler) = image2d_entry(8);
1087
1088 let LightingBindGroupLayoutEntries {
1089 light_slab,
1090 shadow_map_image,
1091 shadow_map_sampler,
1092 } = LightingBindGroupLayoutEntries::new(10);
1093
1094 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1095 label: Some("primitive"),
1096 entries: &[
1097 geometry_slab,
1098 material_slab,
1099 atlas,
1100 atlas_sampler,
1101 irradiance,
1102 irradiance_sampler,
1103 prefilter,
1104 prefilter_sampler,
1105 brdf,
1106 brdf_sampler,
1107 light_slab,
1108 shadow_map_image,
1109 shadow_map_sampler,
1110 ],
1111 })
1112 }
1113
1114 pub fn create_primitive_pipeline(
1115 device: &wgpu::Device,
1116 fragment_color_format: wgpu::TextureFormat,
1117 multisample_count: u32,
1118 ) -> wgpu::RenderPipeline {
1119 log::trace!("creating stage render pipeline");
1120 let label = Some("primitive");
1121 let vertex_linkage = crate::linkage::primitive_vertex::linkage(device);
1122 let fragment_linkage = crate::linkage::primitive_fragment::linkage(device);
1123
1124 let bind_group_layout = Self::primitive_pipeline_bindgroup_layout(device);
1125 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1126 label,
1127 bind_group_layouts: &[&bind_group_layout],
1128 push_constant_ranges: &[],
1129 });
1130
1131 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1132 label,
1133 layout: Some(&layout),
1134 vertex: wgpu::VertexState {
1135 module: &vertex_linkage.module,
1136 entry_point: Some(vertex_linkage.entry_point),
1137 buffers: &[],
1138 compilation_options: Default::default(),
1139 },
1140 primitive: wgpu::PrimitiveState {
1141 topology: wgpu::PrimitiveTopology::TriangleList,
1142 strip_index_format: None,
1143 front_face: wgpu::FrontFace::Ccw,
1144 cull_mode: None,
1145 unclipped_depth: false,
1146 polygon_mode: wgpu::PolygonMode::Fill,
1147 conservative: false,
1148 },
1149 depth_stencil: Some(wgpu::DepthStencilState {
1150 format: wgpu::TextureFormat::Depth32Float,
1151 depth_write_enabled: true,
1152 depth_compare: wgpu::CompareFunction::Less,
1153 stencil: wgpu::StencilState::default(),
1154 bias: wgpu::DepthBiasState::default(),
1155 }),
1156 multisample: wgpu::MultisampleState {
1157 mask: !0,
1158 alpha_to_coverage_enabled: false,
1159 count: multisample_count,
1160 },
1161 fragment: Some(wgpu::FragmentState {
1162 module: &fragment_linkage.module,
1163 entry_point: Some(fragment_linkage.entry_point),
1164 targets: &[Some(wgpu::ColorTargetState {
1165 format: fragment_color_format,
1166 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1167 write_mask: wgpu::ColorWrites::ALL,
1168 })],
1169 compilation_options: Default::default(),
1170 }),
1171 multiview: None,
1172 cache: None,
1173 })
1174 }
1175
1176 pub fn new(ctx: &crate::context::Context) -> Self {
1178 let runtime = ctx.runtime();
1179 let device = &runtime.device;
1180 let resolution @ UVec2 { x: w, y: h } = ctx.get_size();
1181 let stage_config = *ctx.stage_config.read().expect("stage_config read");
1182 let geometry = Geometry::new(
1183 ctx,
1184 resolution,
1185 UVec2::new(
1186 stage_config.atlas_size.width,
1187 stage_config.atlas_size.height,
1188 ),
1189 );
1190 let materials = Materials::new(runtime, stage_config.atlas_size);
1191 let multisample_count = 1;
1192 let hdr_texture = Arc::new(RwLock::new(Texture::create_hdr_texture(
1193 device,
1194 w,
1195 h,
1196 multisample_count,
1197 )));
1198 let depth_texture =
1199 Texture::create_depth_texture(device, w, h, multisample_count, Some("stage-depth"));
1200 let msaa_render_target = Default::default();
1201 let bloom = Bloom::new(ctx, &hdr_texture.read().expect("hdr_texture read"));
1204 let tonemapping = Tonemapping::new(
1205 runtime,
1206 ctx.get_render_target().format().add_srgb_suffix(),
1207 &bloom.get_mix_texture(),
1208 );
1209 let stage_pipeline = Self::create_primitive_pipeline(
1210 device,
1211 wgpu::TextureFormat::Rgba16Float,
1212 multisample_count,
1213 );
1214 let geometry_buffer = geometry.slab_allocator().commit();
1215 let lighting = Lighting::new(stage_config.shadow_map_atlas_size, &geometry);
1216
1217 let brdf_lut = BrdfLut::new(runtime);
1218 let skybox = Skybox::empty(runtime);
1219 let ibl = Ibl::new(runtime, &skybox);
1220
1221 Self {
1222 materials,
1223 draw_calls: Arc::new(RwLock::new(DrawCalls::new(
1224 ctx,
1225 ctx.get_use_direct_draw(),
1226 &geometry_buffer,
1227 &depth_texture,
1228 ))),
1229 lighting,
1230 depth_texture: Arc::new(RwLock::new(depth_texture)),
1231 stage_slab_buffer: Arc::new(RwLock::new(geometry_buffer)),
1232 geometry,
1233
1234 primitive_pipeline: Arc::new(RwLock::new(stage_pipeline)),
1235 primitive_bind_group: ManagedBindGroup::default(),
1236 primitive_bind_group_created: Arc::new(0.into()),
1237
1238 ibl: Arc::new(RwLock::new(ibl)),
1239 skybox: Arc::new(RwLock::new(skybox)),
1240 skybox_bindgroup: Default::default(),
1241 skybox_pipeline: Default::default(),
1242 has_skybox: Arc::new(AtomicBool::new(false)),
1243 brdf_lut,
1244 bloom,
1245 tonemapping,
1246 has_bloom: AtomicBool::from(true).into(),
1247 textures_bindgroup: Default::default(),
1248 debug_overlay: DebugOverlay::new(device, ctx.get_render_target().format()),
1249 has_debug_overlay: Arc::new(false.into()),
1250 hdr_texture,
1251 msaa_render_target,
1252 msaa_sample_count: Arc::new(multisample_count.into()),
1253 clear_color_attachments: Arc::new(true.into()),
1254 clear_depth_attachments: Arc::new(true.into()),
1255 background_color: Arc::new(RwLock::new(wgpu::Color::TRANSPARENT)),
1256 }
1257 }
1258
1259 pub fn set_background_color(&self, color: impl Into<Vec4>) {
1260 let color = color.into();
1261 *self
1262 .background_color
1263 .write()
1264 .expect("background_color write") = wgpu::Color {
1265 r: color.x as f64,
1266 g: color.y as f64,
1267 b: color.z as f64,
1268 a: color.w as f64,
1269 };
1270 }
1271
1272 pub fn with_background_color(self, color: impl Into<Vec4>) -> Self {
1273 self.set_background_color(color);
1274 self
1275 }
1276
1277 pub fn get_msaa_sample_count(&self) -> u32 {
1279 self.msaa_sample_count
1280 .load(std::sync::atomic::Ordering::Relaxed)
1281 }
1282
1283 pub fn set_msaa_sample_count(&self, multisample_count: u32) {
1288 let multisample_count = multisample_count.max(1);
1289 let prev_multisample_count = self
1290 .msaa_sample_count
1291 .swap(multisample_count, Ordering::Relaxed);
1292 if prev_multisample_count == multisample_count {
1293 log::warn!("set_multisample_count: multisample count is unchanged, noop");
1294 return;
1295 }
1296
1297 log::debug!("setting multisample count to {multisample_count}");
1298 *self
1300 .primitive_pipeline
1301 .write()
1302 .expect("primitive_pipeline write") = Self::create_primitive_pipeline(
1303 self.device(),
1304 wgpu::TextureFormat::Rgba16Float,
1305 multisample_count,
1306 );
1307 let size = self.get_size();
1308 *self.depth_texture.write().expect("depth_texture write") = Texture::create_depth_texture(
1310 self.device(),
1311 size.x,
1312 size.y,
1313 multisample_count,
1314 Some("stage-depth"),
1315 );
1316 let size = self.get_size();
1317 let format = self
1319 .hdr_texture
1320 .read()
1321 .expect("hdr_texture read")
1322 .texture
1323 .format();
1324 *self
1325 .msaa_render_target
1326 .write()
1327 .expect("msaa_render_target write") = if multisample_count == 1 {
1328 None
1329 } else {
1330 Some(create_msaa_textureview(
1331 self.device(),
1332 size.x,
1333 size.y,
1334 format,
1335 multisample_count,
1336 ))
1337 };
1338
1339 let _ = self
1341 .textures_bindgroup
1342 .lock()
1343 .expect("textures_bindgroup lock")
1344 .take();
1345 }
1346
1347 pub fn with_msaa_sample_count(self, multisample_count: u32) -> Self {
1352 self.set_msaa_sample_count(multisample_count);
1353 self
1354 }
1355
1356 pub fn set_clear_color_attachments(&self, should_clear: bool) {
1358 self.clear_color_attachments
1359 .store(should_clear, Ordering::Relaxed);
1360 }
1361
1362 pub fn with_clear_color_attachments(self, should_clear: bool) -> Self {
1364 self.set_clear_color_attachments(should_clear);
1365 self
1366 }
1367
1368 pub fn set_clear_depth_attachments(&self, should_clear: bool) {
1370 self.clear_depth_attachments
1371 .store(should_clear, Ordering::Relaxed);
1372 }
1373
1374 pub fn with_clear_depth_attachments(self, should_clear: bool) -> Self {
1376 self.set_clear_depth_attachments(should_clear);
1377 self
1378 }
1379
1380 pub fn set_debug_mode(&self, debug_mode: DebugChannel) {
1382 self.geometry
1383 .descriptor()
1384 .modify(|cfg| cfg.debug_channel = debug_mode);
1385 }
1386
1387 pub fn with_debug_mode(self, debug_mode: DebugChannel) -> Self {
1389 self.set_debug_mode(debug_mode);
1390 self
1391 }
1392
1393 pub fn set_use_debug_overlay(&self, use_debug_overlay: bool) {
1395 self.has_debug_overlay
1396 .store(use_debug_overlay, std::sync::atomic::Ordering::Relaxed);
1397 }
1398
1399 pub fn with_debug_overlay(self, use_debug_overlay: bool) -> Self {
1401 self.set_use_debug_overlay(use_debug_overlay);
1402 self
1403 }
1404
1405 pub fn set_use_frustum_culling(&self, use_frustum_culling: bool) {
1409 self.geometry
1410 .descriptor()
1411 .modify(|cfg| cfg.perform_frustum_culling = use_frustum_culling);
1412 }
1413
1414 pub fn with_frustum_culling(self, use_frustum_culling: bool) -> Self {
1416 self.set_use_frustum_culling(use_frustum_culling);
1417 self
1418 }
1419
1420 pub fn set_use_occlusion_culling(&self, use_occlusion_culling: bool) {
1428 self.geometry
1429 .descriptor()
1430 .modify(|cfg| cfg.perform_occlusion_culling = use_occlusion_culling);
1431 }
1432
1433 pub fn with_occlusion_culling(self, use_occlusion_culling: bool) -> Self {
1435 self.set_use_occlusion_culling(use_occlusion_culling);
1436 self
1437 }
1438
1439 pub fn set_has_lighting(&self, use_lighting: bool) {
1441 self.geometry
1442 .descriptor()
1443 .modify(|cfg| cfg.has_lighting = use_lighting);
1444 }
1445
1446 pub fn with_lighting(self, use_lighting: bool) -> Self {
1448 self.set_has_lighting(use_lighting);
1449 self
1450 }
1451
1452 pub fn set_has_vertex_skinning(&self, use_skinning: bool) {
1454 self.geometry
1455 .descriptor()
1456 .modify(|cfg| cfg.has_skinning = use_skinning);
1457 }
1458
1459 pub fn with_vertex_skinning(self, use_skinning: bool) -> Self {
1461 self.set_has_vertex_skinning(use_skinning);
1462 self
1463 }
1464
1465 pub fn get_size(&self) -> UVec2 {
1466 let hdr = self.hdr_texture.read().expect("hdr_texture read");
1468 let w = hdr.width();
1469 let h = hdr.height();
1470 UVec2::new(w, h)
1471 }
1472
1473 pub fn set_size(&self, size: UVec2) {
1474 if size == self.get_size() {
1475 return;
1476 }
1477
1478 self.geometry
1479 .descriptor()
1480 .modify(|cfg| cfg.resolution = size);
1481 let hdr_texture = Texture::create_hdr_texture(self.device(), size.x, size.y, 1);
1482 let sample_count = self.msaa_sample_count.load(Ordering::Relaxed);
1483 if let Some(msaa_view) = self
1484 .msaa_render_target
1485 .write()
1486 .expect("msaa_render_target write")
1487 .as_mut()
1488 {
1489 *msaa_view = create_msaa_textureview(
1490 self.device(),
1491 size.x,
1492 size.y,
1493 hdr_texture.texture.format(),
1494 sample_count,
1495 );
1496 }
1497
1498 *self.depth_texture.write().expect("depth_texture write") = Texture::create_depth_texture(
1500 self.device(),
1501 size.x,
1502 size.y,
1503 sample_count,
1504 Some("stage-depth"),
1505 );
1506 self.bloom.set_hdr_texture(self.runtime(), &hdr_texture);
1507 self.tonemapping
1508 .set_hdr_texture(self.device(), &hdr_texture);
1509 *self.hdr_texture.write().expect("hdr_texture write") = hdr_texture;
1510
1511 let _ = self
1512 .skybox_bindgroup
1513 .lock()
1514 .expect("skybox_bindgroup lock")
1515 .take();
1516 let _ = self
1517 .textures_bindgroup
1518 .lock()
1519 .expect("textures_bindgroup lock")
1520 .take();
1521 }
1522
1523 pub fn with_size(self, size: UVec2) -> Self {
1524 self.set_size(size);
1525 self
1526 }
1527
1528 pub fn tonemapping(&self) -> &Tonemapping {
1534 &self.tonemapping
1535 }
1536
1537 pub fn set_has_bloom(&self, has_bloom: bool) {
1539 self.has_bloom
1540 .store(has_bloom, std::sync::atomic::Ordering::Relaxed);
1541 }
1542
1543 pub fn with_bloom(self, has_bloom: bool) -> Self {
1545 self.set_has_bloom(has_bloom);
1546 self
1547 }
1548
1549 pub fn set_bloom_mix_strength(&self, strength: f32) {
1553 self.bloom.set_mix_strength(strength);
1554 }
1555
1556 pub fn with_bloom_mix_strength(self, strength: f32) -> Self {
1557 self.set_bloom_mix_strength(strength);
1558 self
1559 }
1560
1561 pub fn set_bloom_filter_radius(&self, filter_radius: f32) {
1565 self.bloom.set_filter_radius(filter_radius);
1566 }
1567
1568 pub fn with_bloom_filter_radius(self, filter_radius: f32) -> Self {
1572 self.set_bloom_filter_radius(filter_radius);
1573 self
1574 }
1575
1576 pub fn add_primitive(&self, primitive: &Primitive) -> usize {
1585 let mut draws = self.draw_calls.write().expect("draw_calls write");
1587 draws.add_primitive(primitive)
1588 }
1589
1590 pub fn remove_primitive(&self, primitive: &Primitive) -> usize {
1595 let mut draws = self.draw_calls.write().expect("draw_calls write");
1596 draws.remove_primitive(primitive)
1597 }
1598
1599 pub fn sort_primitive(&self, f: impl Fn(&Primitive, &Primitive) -> std::cmp::Ordering) {
1603 let mut guard = self.draw_calls.write().expect("draw_calls write");
1605 guard.sort_primitives(f);
1606 }
1607
1608 pub fn get_depth_texture(&self) -> DepthTexture {
1610 DepthTexture {
1611 runtime: self.runtime().clone(),
1612 texture: self
1613 .depth_texture
1614 .read()
1615 .expect("depth_texture read")
1616 .texture
1617 .clone(),
1618 }
1619 }
1620
1621 pub fn new_nested_transform(&self) -> NestedTransform {
1623 NestedTransform::new(self.geometry.slab_allocator())
1624 }
1625
1626 pub fn render(&self, view: &wgpu::TextureView) {
1628 let background_color = *self.background_color.read().expect("background_color read");
1630 let msaa_target = self
1632 .msaa_render_target
1633 .read()
1634 .expect("msaa_render_target read");
1635 let clear_colors = self.clear_color_attachments.load(Ordering::Relaxed);
1636 let hdr_texture = self.hdr_texture.read().expect("hdr_texture read");
1637
1638 let mk_ops = |store| wgpu::Operations {
1639 load: if clear_colors {
1640 wgpu::LoadOp::Clear(background_color)
1641 } else {
1642 wgpu::LoadOp::Load
1643 },
1644 store,
1645 };
1646 let render_pass_color_attachment = if let Some(msaa_view) = msaa_target.as_ref() {
1647 wgpu::RenderPassColorAttachment {
1648 ops: mk_ops(wgpu::StoreOp::Discard),
1649 view: msaa_view,
1650 resolve_target: Some(&hdr_texture.view),
1651 depth_slice: None,
1652 }
1653 } else {
1654 wgpu::RenderPassColorAttachment {
1655 ops: mk_ops(wgpu::StoreOp::Store),
1656 view: &hdr_texture.view,
1657 resolve_target: None,
1658 depth_slice: None,
1659 }
1660 };
1661
1662 let depth_texture = self.depth_texture.read().expect("depth_texture read");
1663 let clear_depth = self.clear_depth_attachments.load(Ordering::Relaxed);
1664 let render_pass_depth_attachment = wgpu::RenderPassDepthStencilAttachment {
1665 view: &depth_texture.view,
1666 depth_ops: Some(wgpu::Operations {
1667 load: if clear_depth {
1668 wgpu::LoadOp::Clear(1.0)
1669 } else {
1670 wgpu::LoadOp::Load
1671 },
1672 store: wgpu::StoreOp::Store,
1673 }),
1674 stencil_ops: None,
1675 };
1676 let pipeline_guard = self
1677 .primitive_pipeline
1678 .read()
1679 .expect("primitive_pipeline read");
1680 let (_submission_index, maybe_indirect_buffer) = StageRendering {
1681 pipeline: &pipeline_guard,
1682 stage: self,
1683 color_attachment: render_pass_color_attachment,
1684 depth_stencil_attachment: render_pass_depth_attachment,
1685 }
1686 .run();
1687
1688 if self.has_bloom.load(Ordering::Relaxed) {
1690 self.bloom.bloom(self.device(), self.queue());
1691 } else {
1692 let mut encoder =
1694 self.device()
1695 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1696 label: Some("no bloom copy"),
1697 });
1698 let bloom_mix_texture = self.bloom.get_mix_texture();
1699 encoder.copy_texture_to_texture(
1700 wgpu::TexelCopyTextureInfo {
1701 texture: &self.hdr_texture.read().expect("hdr_texture read").texture,
1702 mip_level: 0,
1703 origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
1704 aspect: wgpu::TextureAspect::All,
1705 },
1706 wgpu::TexelCopyTextureInfo {
1707 texture: &bloom_mix_texture.texture,
1708 mip_level: 0,
1709 origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
1710 aspect: wgpu::TextureAspect::All,
1711 },
1712 wgpu::Extent3d {
1713 width: bloom_mix_texture.width(),
1714 height: bloom_mix_texture.height(),
1715 depth_or_array_layers: 1,
1716 },
1717 );
1718 self.queue().submit(std::iter::once(encoder.finish()));
1719 }
1720
1721 self.tonemapping.render(self.device(), self.queue(), view);
1723
1724 if self.has_debug_overlay.load(Ordering::Relaxed) {
1726 if let Some(indirect_draw_buffer) = maybe_indirect_buffer {
1727 self.debug_overlay.render(
1728 self.device(),
1729 self.queue(),
1730 view,
1731 &self
1732 .stage_slab_buffer
1733 .read()
1734 .expect("stage_slab_buffer read"),
1735 &indirect_draw_buffer,
1736 );
1737 }
1738 }
1739 }
1740}
1741
1742#[cfg(test)]
1743mod test {
1744 use craballoc::{runtime::CpuRuntime, slab::SlabAllocator};
1745 use crabslab::{Array, Id, Slab};
1746 use glam::{Mat4, Vec2, Vec3, Vec4};
1747
1748 use crate::{
1749 context::Context,
1750 geometry::{shader::GeometryDescriptor, Geometry, Vertex},
1751 test::BlockOnFuture,
1752 transform::NestedTransform,
1753 };
1754
1755 #[test]
1756 fn vertex_slab_roundtrip() {
1757 let initial_vertices = {
1758 let tl = Vertex::default()
1759 .with_position(Vec3::ZERO)
1760 .with_uv0(Vec2::ZERO);
1761 let tr = Vertex::default()
1762 .with_position(Vec3::new(1.0, 0.0, 0.0))
1763 .with_uv0(Vec2::new(1.0, 0.0));
1764 let bl = Vertex::default()
1765 .with_position(Vec3::new(0.0, 1.0, 0.0))
1766 .with_uv0(Vec2::new(0.0, 1.0));
1767 let br = Vertex::default()
1768 .with_position(Vec3::new(1.0, 1.0, 0.0))
1769 .with_uv0(Vec2::splat(1.0));
1770 vec![tl, bl, br, tl, br, tr]
1771 };
1772 let mut slab = [0u32; 256];
1773 slab.write_indexed_slice(&initial_vertices, 0);
1774 let vertices = slab.read_vec(Array::<Vertex>::new(0, initial_vertices.len() as u32));
1775 pretty_assertions::assert_eq!(initial_vertices, vertices);
1776 }
1777
1778 #[test]
1779 fn matrix_subtraction_sanity() {
1780 let m = Mat4::IDENTITY - Mat4::IDENTITY;
1781 assert_eq!(Mat4::ZERO, m);
1782 }
1783
1784 #[test]
1785 fn can_global_transform_calculation() {
1786 #[expect(
1787 clippy::needless_borrows_for_generic_args,
1788 reason = "This is just riff-raff, as it doesn't compile without the borrow."
1789 )]
1790 let slab = SlabAllocator::<CpuRuntime>::new(&CpuRuntime, "transform", ());
1791 let root = NestedTransform::new(&slab);
1793 let child = NestedTransform::new(&slab).with_local_translation(Vec3::new(1.0, 0.0, 0.0));
1794 let grandchild =
1795 NestedTransform::new(&slab).with_local_translation(Vec3::new(1.0, 0.0, 0.0));
1796 log::info!("hierarchy");
1797 root.add_child(&child);
1799 child.add_child(&grandchild);
1800
1801 log::info!("get_global_transform");
1802 let grandchild_global_transform = grandchild.global_descriptor();
1804
1805 assert_eq!(
1807 grandchild_global_transform.translation.x, 2.0,
1808 "Grandchild's global translation should 2.0 along the x-axis"
1809 );
1810 }
1811
1812 #[test]
1813 fn can_msaa() {
1814 let ctx = Context::headless(100, 100).block();
1815 let stage = ctx
1816 .new_stage()
1817 .with_background_color([1.0, 1.0, 1.0, 1.0])
1818 .with_lighting(false);
1819 let (projection, view) = crate::camera::default_ortho2d(100.0, 100.0);
1820 let _camera = stage
1821 .new_camera()
1822 .with_projection_and_view(projection, view);
1823 let _triangle_rez = stage.new_primitive().with_vertices(
1824 stage.new_vertices([
1825 Vertex::default()
1826 .with_position([10.0, 10.0, 0.0])
1827 .with_color([0.0, 1.0, 1.0, 1.0]),
1828 Vertex::default()
1829 .with_position([10.0, 90.0, 0.0])
1830 .with_color([1.0, 1.0, 0.0, 1.0]),
1831 Vertex::default()
1832 .with_position([90.0, 10.0, 0.0])
1833 .with_color([1.0, 0.0, 1.0, 1.0]),
1834 ]),
1835 );
1836
1837 log::debug!("rendering without msaa");
1838 let frame = ctx.get_next_frame().unwrap();
1839 stage.render(&frame.view());
1840 let img = frame.read_image().block().unwrap();
1841 img_diff::assert_img_eq_cfg(
1842 "msaa/without.png",
1843 img,
1844 img_diff::DiffCfg {
1845 pixel_threshold: img_diff::LOW_PIXEL_THRESHOLD,
1846 ..Default::default()
1847 },
1848 );
1849 frame.present();
1850 log::debug!(" all good!");
1851
1852 stage.set_msaa_sample_count(4);
1853 log::debug!("rendering with msaa");
1854 let frame = ctx.get_next_frame().unwrap();
1855 stage.render(&frame.view());
1856 let img = frame.read_image().block().unwrap();
1857 img_diff::assert_img_eq_cfg(
1858 "msaa/with.png",
1859 img,
1860 img_diff::DiffCfg {
1861 pixel_threshold: img_diff::LOW_PIXEL_THRESHOLD,
1862 ..Default::default()
1863 },
1864 );
1865 frame.present();
1866 }
1867
1868 #[test]
1869 fn stage_geometry_desc_sanity() {
1872 let ctx = Context::headless(100, 100).block();
1873 let stage = ctx.new_stage();
1874 let _ = stage.commit();
1875
1876 let slab = futures_lite::future::block_on({
1877 let geometry: &Geometry = stage.as_ref();
1878 geometry.slab_allocator().read(..)
1879 })
1880 .unwrap();
1881 let pbr_desc = slab.read_unchecked(Id::<GeometryDescriptor>::new(0));
1882 pretty_assertions::assert_eq!(stage.geometry_descriptor().get(), pbr_desc);
1883 }
1884
1885 #[test]
1886 fn slabbed_vertices_native() {
1887 let ctx = Context::headless(100, 100).block();
1888 let runtime = ctx.as_ref();
1889
1890 let slab = SlabAllocator::new(
1892 runtime,
1893 "slabbed_isosceles_triangle",
1894 wgpu::BufferUsages::empty(),
1895 );
1896
1897 let geometry = vec![
1898 (Vec3::new(0.5, -0.5, 0.0), Vec4::new(1.0, 0.0, 0.0, 1.0)),
1899 (Vec3::new(0.0, 0.5, 0.0), Vec4::new(0.0, 1.0, 0.0, 1.0)),
1900 (Vec3::new(-0.5, -0.5, 0.0), Vec4::new(0.0, 0.0, 1.0, 1.0)),
1901 (Vec3::new(-1.0, 1.0, 0.0), Vec4::new(1.0, 0.0, 0.0, 1.0)),
1902 (Vec3::new(-1.0, 0.0, 0.0), Vec4::new(0.0, 1.0, 0.0, 1.0)),
1903 (Vec3::new(0.0, 1.0, 0.0), Vec4::new(0.0, 0.0, 1.0, 1.0)),
1904 ];
1905 let vertices = slab.new_array(geometry);
1906 let array = slab.new_value(vertices.array());
1907
1908 let bindgroup_layout =
1910 runtime
1911 .device
1912 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1913 label: None,
1914 entries: &[wgpu::BindGroupLayoutEntry {
1915 binding: 0,
1916 visibility: wgpu::ShaderStages::VERTEX,
1917 ty: wgpu::BindingType::Buffer {
1918 ty: wgpu::BufferBindingType::Storage { read_only: true },
1919 has_dynamic_offset: false,
1920 min_binding_size: None,
1921 },
1922 count: None,
1923 }],
1924 });
1925 let pipeline_layout =
1926 runtime
1927 .device
1928 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1929 label: None,
1930 bind_group_layouts: &[&bindgroup_layout],
1931 push_constant_ranges: &[],
1932 });
1933
1934 let vertex = crate::linkage::slabbed_vertices::linkage(&runtime.device);
1935 let fragment = crate::linkage::passthru_fragment::linkage(&runtime.device);
1936 let pipeline = runtime
1937 .device
1938 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1939 label: None,
1940 cache: None,
1941 layout: Some(&pipeline_layout),
1942 vertex: wgpu::VertexState {
1943 compilation_options: wgpu::PipelineCompilationOptions::default(),
1944 module: &vertex.module,
1945 entry_point: Some(vertex.entry_point),
1946 buffers: &[],
1947 },
1948 primitive: wgpu::PrimitiveState {
1949 topology: wgpu::PrimitiveTopology::TriangleList,
1950 strip_index_format: None,
1951 front_face: wgpu::FrontFace::Ccw,
1952 cull_mode: None,
1953 unclipped_depth: false,
1954 polygon_mode: wgpu::PolygonMode::Fill,
1955 conservative: false,
1956 },
1957 depth_stencil: None,
1958 multisample: wgpu::MultisampleState {
1959 mask: !0,
1960 alpha_to_coverage_enabled: false,
1961 count: 1,
1962 },
1963 fragment: Some(wgpu::FragmentState {
1964 compilation_options: Default::default(),
1965 module: &fragment.module,
1966 entry_point: Some(fragment.entry_point),
1967 targets: &[Some(wgpu::ColorTargetState {
1968 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1969 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1970 write_mask: wgpu::ColorWrites::ALL,
1971 })],
1972 }),
1973 multiview: None,
1974 });
1975 let slab_buffer = slab.commit();
1976
1977 let bindgroup = runtime
1978 .device
1979 .create_bind_group(&wgpu::BindGroupDescriptor {
1980 label: None,
1981 layout: &bindgroup_layout,
1982 entries: &[wgpu::BindGroupEntry {
1983 binding: 0,
1984 resource: slab_buffer.as_entire_binding(),
1985 }],
1986 });
1987
1988 let frame = ctx.get_next_frame().unwrap();
1989 let mut encoder = runtime.device.create_command_encoder(&Default::default());
1990 {
1991 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1992 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1993 view: &frame.view(),
1994 resolve_target: None,
1995 ops: wgpu::Operations {
1996 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
1997 store: wgpu::StoreOp::Store,
1998 },
1999 depth_slice: None,
2000 })],
2001 ..Default::default()
2002 });
2003 render_pass.set_pipeline(&pipeline);
2004 render_pass.set_bind_group(0, &bindgroup, &[]);
2005 let id = array.id().inner();
2006 render_pass.draw(0..vertices.len() as u32, id..id + 1);
2007 }
2008 runtime.queue.submit(std::iter::once(encoder.finish()));
2009
2010 let img = frame
2011 .read_linear_image()
2012 .block()
2013 .expect("could not read frame");
2014 img_diff::assert_img_eq("tutorial/slabbed_isosceles_triangle.png", img);
2015 }
2016}