1use core::sync::atomic::{AtomicU32, Ordering};
6use crabslab::{Array, Id, Slab, SlabItem};
7use rustc_hash::FxHashMap;
8use snafu::Snafu;
9use std::{
10 ops::{Deref, DerefMut},
11 sync::{atomic::AtomicBool, Arc, Mutex, RwLock},
12};
13
14use crate::{
15 atlas::{Atlas, AtlasError, AtlasImage, AtlasImageError, AtlasTexture},
16 bloom::Bloom,
17 camera::Camera,
18 pbr::{debug::DebugMode, light::Light, PbrConfig},
19 skybox::{Skybox, SkyboxRenderPipeline},
20 slab::*,
21 stage::Renderlet,
22 texture::{DepthTexture, Texture},
23 tonemapping::Tonemapping,
24 transform::Transform,
25};
26
27use super::*;
28
29#[derive(Debug, Snafu)]
30pub enum StageError {
31 #[snafu(display("{source}"))]
32 Atlas { source: AtlasError },
33}
34
35impl From<AtlasError> for StageError {
36 fn from(source: AtlasError) -> Self {
37 Self::Atlas { source }
38 }
39}
40
41pub const RENDERLET_STRONG_COUNT_LOWER_BOUND: usize = 2;
52
53struct IndirectDraws {
54 slab: SlabAllocator<wgpu::Buffer>,
55 draws: Vec<Gpu<DrawIndirectArgs>>,
56}
57
58impl From<&Hybrid<Renderlet>> for DrawIndirectArgs {
59 fn from(h: &Hybrid<Renderlet>) -> Self {
60 let r = h.get();
61 DrawIndirectArgs {
62 vertex_count: if r.indices_array.is_null() {
63 r.vertices_array.len() as u32
64 } else {
65 r.indices_array.len() as u32
66 },
67 instance_count: 1,
68 first_vertex: 0,
69 first_instance: h.id().into(),
70 }
71 }
72}
73
74#[derive(Default)]
77pub(crate) struct StageDraws {
78 hybrids: Vec<Hybrid<Renderlet>>,
79 indirect: Option<IndirectDraws>,
80}
81
82fn create_msaa_textureview(
83 device: &wgpu::Device,
84 width: u32,
85 height: u32,
86 format: wgpu::TextureFormat,
87 sample_count: u32,
88) -> wgpu::TextureView {
89 device
90 .create_texture(&wgpu::TextureDescriptor {
91 label: Some("stage msaa render target"),
92 size: wgpu::Extent3d {
93 width,
94 height,
95 depth_or_array_layers: 1,
96 },
97 mip_level_count: 1,
98 sample_count,
99 dimension: wgpu::TextureDimension::D2,
100 format,
101 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
102 view_formats: &[],
103 })
104 .create_view(&wgpu::TextureViewDescriptor::default())
105}
106
107fn create_stage_render_pipeline(
108 device: &wgpu::Device,
109 multisample_count: u32,
110) -> wgpu::RenderPipeline {
111 log::trace!("creating stage render pipeline");
112 let label = Some("stage render");
113 let vertex_linkage = crate::linkage::renderlet_vertex::linkage(device);
114 let fragment_linkage = crate::linkage::renderlet_fragment::linkage(device);
115 let stage_slab_buffers_layout = crate::linkage::slab_bindgroup_layout(device);
116 let atlas_and_skybox_layout = crate::linkage::atlas_and_skybox_bindgroup_layout(device);
117 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
118 label,
119 bind_group_layouts: &[&stage_slab_buffers_layout, &atlas_and_skybox_layout],
120 push_constant_ranges: &[],
121 });
122
123 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
124 label,
125 layout: Some(&layout),
126 vertex: wgpu::VertexState {
127 module: &vertex_linkage.module,
128 entry_point: vertex_linkage.entry_point,
129 buffers: &[],
130 compilation_options: Default::default(),
131 },
132 primitive: wgpu::PrimitiveState {
133 topology: wgpu::PrimitiveTopology::TriangleList,
134 strip_index_format: None,
135 front_face: wgpu::FrontFace::Ccw,
136 cull_mode: None,
137 unclipped_depth: false,
138 polygon_mode: wgpu::PolygonMode::Fill,
139 conservative: false,
140 },
141 depth_stencil: Some(wgpu::DepthStencilState {
142 format: wgpu::TextureFormat::Depth32Float,
143 depth_write_enabled: true,
144 depth_compare: wgpu::CompareFunction::Less,
145 stencil: wgpu::StencilState::default(),
146 bias: wgpu::DepthBiasState::default(),
147 }),
148 multisample: wgpu::MultisampleState {
149 mask: !0,
150 alpha_to_coverage_enabled: false,
151 count: multisample_count,
152 },
153 fragment: Some(wgpu::FragmentState {
154 module: &fragment_linkage.module,
155 entry_point: fragment_linkage.entry_point,
156 targets: &[Some(wgpu::ColorTargetState {
157 format: wgpu::TextureFormat::Rgba16Float,
158 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
159 write_mask: wgpu::ColorWrites::ALL,
160 })],
161 compilation_options: Default::default(),
162 }),
163 multiview: None,
164 cache: None,
165 })
166}
167
168#[derive(Clone)]
175pub struct Stage {
176 pub(crate) device: Arc<wgpu::Device>,
177 pub(crate) queue: Arc<wgpu::Queue>,
178
179 pub(crate) mngr: SlabAllocator<wgpu::Buffer>,
180
181 pub(crate) pbr_config: Hybrid<PbrConfig>,
182 pub(crate) lights: Arc<RwLock<HybridArray<Id<Light>>>>,
183
184 pub(crate) stage_pipeline: Arc<RwLock<wgpu::RenderPipeline>>,
185 pub(crate) skybox_pipeline: Arc<RwLock<Option<Arc<SkyboxRenderPipeline>>>>,
186
187 pub(crate) hdr_texture: Arc<RwLock<Texture>>,
188 pub(crate) depth_texture: Arc<RwLock<Texture>>,
189 pub(crate) msaa_render_target: Arc<RwLock<Option<wgpu::TextureView>>>,
190 pub(crate) msaa_sample_count: Arc<AtomicU32>,
191 pub(crate) clear_color_attachments: Arc<AtomicBool>,
192 pub(crate) clear_depth_attachments: Arc<AtomicBool>,
193
194 pub(crate) atlas: Atlas,
195 pub(crate) bloom: Bloom,
196 pub(crate) skybox: Arc<RwLock<Skybox>>,
197 pub(crate) tonemapping: Tonemapping,
198 pub(crate) background_color: Arc<RwLock<wgpu::Color>>,
199
200 pub(crate) has_skybox: Arc<AtomicBool>,
201 pub(crate) has_bloom: Arc<AtomicBool>,
202
203 pub(crate) skybox_bindgroup: Arc<Mutex<Option<Arc<wgpu::BindGroup>>>>,
204 pub(crate) buffers_bindgroup: Arc<Mutex<Option<Arc<wgpu::BindGroup>>>>,
205 pub(crate) textures_bindgroup: Arc<Mutex<Option<Arc<wgpu::BindGroup>>>>,
206
207 pub(crate) draws: Arc<RwLock<StageDraws>>,
208}
209
210impl Deref for Stage {
211 type Target = SlabAllocator<wgpu::Buffer>;
212
213 fn deref(&self) -> &Self::Target {
214 &self.mngr
215 }
216}
217
218impl Stage {
219 pub fn new(ctx: &crate::Context) -> Self {
221 let (device, queue) = ctx.get_device_and_queue_owned();
222 let resolution @ UVec2 { x: w, y: h } = ctx.get_size();
223 let atlas_size = *ctx.atlas_size.read().unwrap();
224 let atlas = Atlas::new(&device, &queue, atlas_size).unwrap();
225 let mngr = SlabAllocator::default();
226 let pbr_config = mngr.new_value(PbrConfig {
227 atlas_size: UVec2::new(atlas_size.width, atlas_size.height),
228 resolution,
229 ..Default::default()
230 });
231 let multisample_count = 1;
232 let lights = mngr.new_array(vec![Id::<Light>::NONE; 16]);
233 let hdr_texture = Arc::new(RwLock::new(Texture::create_hdr_texture(
234 &device,
235 w,
236 h,
237 multisample_count,
238 )));
239 let depth_texture = Arc::new(RwLock::new(Texture::create_depth_texture(
240 &device,
241 w,
242 h,
243 multisample_count,
244 )));
245 let msaa_render_target = Default::default();
246 let bloom = Bloom::new(&device, &queue, &hdr_texture.read().unwrap());
248 let tonemapping = Tonemapping::new(
249 &device,
250 &queue,
251 ctx.get_render_target().format().add_srgb_suffix(),
252 &bloom.get_mix_texture(),
253 );
254 let draws = StageDraws {
255 hybrids: vec![],
256 indirect: {
257 if ctx.get_adapter().features().contains(
258 wgpu::Features::INDIRECT_FIRST_INSTANCE | wgpu::Features::MULTI_DRAW_INDIRECT,
259 ) {
260 log::info!("creating stage to use Renderpass::multi_draw_indirect");
261 Some(IndirectDraws {
262 slab: SlabAllocator::default(),
263 draws: vec![],
264 })
265 } else {
266 None
267 }
268 },
269 };
270
271 Self {
272 mngr,
273 pbr_config,
274 lights: Arc::new(RwLock::new(lights)),
275
276 stage_pipeline: Arc::new(RwLock::new(create_stage_render_pipeline(
277 &device,
278 multisample_count,
279 ))),
280 atlas,
281 skybox: Arc::new(RwLock::new(Skybox::empty(&device, &queue))),
282 skybox_bindgroup: Default::default(),
283 skybox_pipeline: Default::default(),
284 has_skybox: Arc::new(AtomicBool::new(false)),
285 bloom,
286 tonemapping,
287 has_bloom: AtomicBool::from(true).into(),
288 buffers_bindgroup: Default::default(),
289 textures_bindgroup: Default::default(),
290 draws: Arc::new(RwLock::new(draws)),
291 hdr_texture,
292 depth_texture,
293 msaa_render_target,
294 msaa_sample_count: Arc::new(multisample_count.into()),
295 clear_color_attachments: Arc::new(true.into()),
296 clear_depth_attachments: Arc::new(true.into()),
297 background_color: Arc::new(RwLock::new(wgpu::Color::TRANSPARENT)),
298 device,
299 queue,
300 }
301 }
302
303 pub fn get_device_and_queue_owned(&self) -> (Arc<wgpu::Device>, Arc<wgpu::Queue>) {
304 (self.device.clone(), self.queue.clone())
305 }
306
307 pub fn set_background_color(&self, color: impl Into<Vec4>) {
308 let color = color.into();
309 *self.background_color.write().unwrap() = wgpu::Color {
310 r: color.x as f64,
311 g: color.y as f64,
312 b: color.z as f64,
313 a: color.w as f64,
314 };
315 }
316
317 pub fn with_background_color(self, color: impl Into<Vec4>) -> Self {
318 self.set_background_color(color);
319 self
320 }
321
322 pub fn set_msaa_sample_count(&self, multisample_count: u32) {
327 let multisample_count = multisample_count.max(1);
328 let prev_multisample_count = self
329 .msaa_sample_count
330 .swap(multisample_count, Ordering::Relaxed);
331 if prev_multisample_count == multisample_count {
332 log::warn!("set_multisample_count: multisample count is unchanged, noop");
333 return;
334 }
335
336 log::debug!("setting multisample count to {multisample_count}");
337 *self.stage_pipeline.write().unwrap() =
339 create_stage_render_pipeline(&self.device, multisample_count);
340 let size = self.get_size();
341 *self.depth_texture.write().unwrap() =
343 Texture::create_depth_texture(&self.device, size.x, size.y, multisample_count);
344 let format = self.hdr_texture.read().unwrap().texture.format();
346 *self.msaa_render_target.write().unwrap() = if multisample_count == 1 {
347 None
348 } else {
349 Some(create_msaa_textureview(
350 &self.device,
351 size.x,
352 size.y,
353 format,
354 multisample_count,
355 ))
356 };
357
358 let _ = self.textures_bindgroup.lock().unwrap().take();
360 }
361
362 pub fn with_msaa_sample_count(self, multisample_count: u32) -> Self {
367 self.set_msaa_sample_count(multisample_count);
368 self
369 }
370
371 pub fn set_clear_color_attachments(&self, should_clear: bool) {
373 self.clear_color_attachments
374 .store(should_clear, Ordering::Relaxed);
375 }
376
377 pub fn with_clear_color_attachments(self, should_clear: bool) -> Self {
379 self.set_clear_color_attachments(should_clear);
380 self
381 }
382
383 pub fn set_clear_depth_attachments(&self, should_clear: bool) {
385 self.clear_depth_attachments
386 .store(should_clear, Ordering::Relaxed);
387 }
388
389 pub fn with_clear_depth_attachments(self, should_clear: bool) -> Self {
391 self.set_clear_depth_attachments(should_clear);
392 self
393 }
394
395 pub fn set_debug_mode(&self, debug_mode: DebugMode) {
397 self.pbr_config.modify(|cfg| cfg.debug_mode = debug_mode);
398 }
399
400 pub fn with_debug_mode(self, debug_mode: DebugMode) -> Self {
402 self.set_debug_mode(debug_mode);
403 self
404 }
405
406 pub fn set_has_lighting(&self, use_lighting: bool) {
408 self.pbr_config
409 .modify(|cfg| cfg.has_lighting = use_lighting);
410 }
411
412 pub fn with_lighting(self, use_lighting: bool) -> Self {
414 self.set_has_lighting(use_lighting);
415 self
416 }
417
418 pub fn set_has_vertex_skinning(&self, use_skinning: bool) {
420 self.pbr_config
421 .modify(|cfg| cfg.has_skinning = use_skinning);
422 }
423
424 pub fn with_vertex_skinning(self, use_skinning: bool) -> Self {
426 self.set_has_vertex_skinning(use_skinning);
427 self
428 }
429
430 pub fn set_lights(&self, lights: impl IntoIterator<Item = Id<Light>>) {
432 log::trace!("setting lights");
433 let lights = self.mngr.new_array(lights);
434 self.pbr_config.modify(|cfg| {
435 cfg.light_array = lights.array();
436 });
437 *self.lights.write().unwrap() = lights;
439 }
440
441 pub fn get_size(&self) -> UVec2 {
442 let hdr = self.hdr_texture.read().unwrap();
444 let w = hdr.width();
445 let h = hdr.height();
446 UVec2::new(w, h)
447 }
448
449 pub fn set_size(&self, size: UVec2) {
450 if size == self.get_size() {
451 return;
452 }
453
454 self.pbr_config.modify(|cfg| cfg.resolution = size);
455 let hdr_texture = Texture::create_hdr_texture(&self.device, size.x, size.y, 1);
456 let sample_count = self.msaa_sample_count.load(Ordering::Relaxed);
457 if let Some(msaa_view) = self.msaa_render_target.write().unwrap().as_mut() {
458 *msaa_view = create_msaa_textureview(
459 &self.device,
460 size.x,
461 size.y,
462 hdr_texture.texture.format(),
463 sample_count,
464 );
465 }
466
467 *self.depth_texture.write().unwrap() =
469 Texture::create_depth_texture(&self.device, size.x, size.y, sample_count);
470 self.bloom
471 .set_hdr_texture(&self.device, &self.queue, &hdr_texture);
472 self.tonemapping.set_hdr_texture(&self.device, &hdr_texture);
473 *self.hdr_texture.write().unwrap() = hdr_texture;
474
475 let _ = self.skybox_bindgroup.lock().unwrap().take();
476 let _ = self.textures_bindgroup.lock().unwrap().take();
477 }
478
479 pub fn with_size(self, size: UVec2) -> Self {
480 self.set_size(size);
481 self
482 }
483
484 pub fn set_atlas_size(&self, size: wgpu::Extent3d) -> Result<(), StageError> {
488 self.atlas.resize(&self.device, &self.queue, size)?;
489 Ok(())
490 }
491
492 pub fn add_images(
508 &self,
509 images: impl IntoIterator<Item = impl Into<AtlasImage>>,
510 ) -> Result<Vec<Hybrid<AtlasTexture>>, StageError> {
511 let frames = self
512 .atlas
513 .add_images(&self.device, &self.queue, self, images)?;
514
515 let _ = self.textures_bindgroup.lock().unwrap().take();
517
518 Ok(frames)
519 }
520
521 pub fn clear_images(&self) -> Result<(), StageError> {
526 let none = Option::<AtlasImage>::None;
527 let _ = self.set_images(none)?;
528 Ok(())
529 }
530
531 pub fn set_images(
539 &self,
540 images: impl IntoIterator<Item = impl Into<AtlasImage>>,
541 ) -> Result<Vec<Hybrid<AtlasTexture>>, StageError> {
542 let frames = self
543 .atlas
544 .set_images(&self.device, &self.queue, self, images)?;
545
546 let _ = self.textures_bindgroup.lock().unwrap().take();
548
549 Ok(frames)
550 }
551
552 pub fn set_skybox(&self, skybox: Skybox) {
554 let mut guard = self.skybox.write().unwrap();
556 *guard = skybox;
557 self.has_skybox
558 .store(true, std::sync::atomic::Ordering::Relaxed);
559 *self.skybox_bindgroup.lock().unwrap() = None;
560 *self.textures_bindgroup.lock().unwrap() = None;
561 }
562
563 pub fn set_has_bloom(&self, has_bloom: bool) {
565 self.has_bloom
566 .store(has_bloom, std::sync::atomic::Ordering::Relaxed);
567 }
568
569 pub fn with_bloom(self, has_bloom: bool) -> Self {
571 self.set_has_bloom(has_bloom);
572 self
573 }
574
575 pub fn set_bloom_mix_strength(&self, strength: f32) {
579 self.bloom.set_mix_strength(strength);
580 }
581
582 pub fn with_bloom_mix_strength(self, strength: f32) -> Self {
583 self.set_bloom_mix_strength(strength);
584 self
585 }
586
587 pub fn set_bloom_filter_radius(&self, filter_radius: f32) {
591 self.bloom.set_filter_radius(filter_radius);
592 }
593
594 pub fn with_bloom_filter_radius(self, filter_radius: f32) -> Self {
598 self.set_bloom_filter_radius(filter_radius);
599 self
600 }
601
602 fn get_skybox_pipeline_and_bindgroup(
604 &self,
605 slab_buffer: &wgpu::Buffer,
606 ) -> (Arc<SkyboxRenderPipeline>, Arc<wgpu::BindGroup>) {
607 let msaa_sample_count = self.msaa_sample_count.load(Ordering::Relaxed);
608 let mut pipeline_guard = self.skybox_pipeline.write().unwrap();
610 let pipeline = if let Some(pipeline) = pipeline_guard.as_mut() {
611 if pipeline.msaa_sample_count() != msaa_sample_count {
612 *pipeline = Arc::new(crate::skybox::create_skybox_render_pipeline(
613 &self.device,
614 Texture::HDR_TEXTURE_FORMAT,
615 Some(msaa_sample_count),
616 ));
617 }
618 pipeline.clone()
619 } else {
620 let pipeline = Arc::new(crate::skybox::create_skybox_render_pipeline(
621 &self.device,
622 Texture::HDR_TEXTURE_FORMAT,
623 Some(msaa_sample_count),
624 ));
625 *pipeline_guard = Some(pipeline.clone());
626 pipeline
627 };
628 let mut bindgroup = self.skybox_bindgroup.lock().unwrap();
630 let bindgroup = if let Some(bindgroup) = bindgroup.as_ref() {
631 bindgroup.clone()
632 } else {
633 let bg = Arc::new(crate::skybox::create_skybox_bindgroup(
634 &self.device,
635 slab_buffer,
636 &self.skybox.read().unwrap().environment_cubemap,
637 ));
638 *bindgroup = Some(bg.clone());
639 bg
640 };
641 (pipeline, bindgroup)
642 }
643
644 fn get_slab_buffers_bindgroup(&self, slab_buffer: &wgpu::Buffer) -> Arc<wgpu::BindGroup> {
645 let mut bindgroup = self.buffers_bindgroup.lock().unwrap();
647 if let Some(bindgroup) = bindgroup.as_ref() {
648 bindgroup.clone()
649 } else {
650 let b = Arc::new({
651 let device: &wgpu::Device = &self.device;
652 crate::linkage::slab_bindgroup(
653 device,
654 slab_buffer,
655 &self.stage_pipeline.read().unwrap().get_bind_group_layout(0),
657 )
658 });
659 *bindgroup = Some(b.clone());
660 b
661 }
662 }
663
664 fn get_textures_bindgroup(&self) -> Arc<wgpu::BindGroup> {
665 let mut bindgroup = self.textures_bindgroup.lock().unwrap();
667 if let Some(bindgroup) = bindgroup.as_ref() {
668 bindgroup.clone()
669 } else {
670 let b = Arc::new(crate::linkage::atlas_and_skybox_bindgroup(
671 &self.device,
672 &{
673 let this = &self;
674 this.stage_pipeline.clone()
675 }
676 .read()
677 .unwrap()
679 .get_bind_group_layout(1),
680 &self.atlas,
682 &self.skybox.read().unwrap(),
683 ));
684 *bindgroup = Some(b.clone());
685 b
686 }
687 }
688
689 pub fn add_renderlet(&self, renderlet: &Hybrid<Renderlet>) {
698 let mut draws = self.draws.write().unwrap();
700 let StageDraws { hybrids, indirect } = draws.deref_mut();
701 hybrids.push(renderlet.clone());
702 if let Some(IndirectDraws { slab, draws }) = indirect.as_mut() {
703 draws.push(
704 slab.new_value(DrawIndirectArgs::from(renderlet))
705 .into_gpu_only(),
706 );
707 }
708 }
709
710 pub fn remove_renderlet(&self, renderlet: &Hybrid<Renderlet>) {
713 let id = renderlet.id();
714 let mut draws = self.draws.write().unwrap();
715 let StageDraws { hybrids, indirect } = draws.deref_mut();
716 hybrids.retain(|hybrid| hybrid.id() != id);
717 if let Some(IndirectDraws { slab: _, draws }) = indirect.as_mut() {
718 *draws = vec![];
719 }
720 }
721
722 pub fn get_renderlets(&self) -> Vec<Hybrid<Renderlet>> {
724 let draws = self.draws.read().unwrap();
726 let StageDraws {
727 hybrids,
728 indirect: _,
729 } = draws.deref();
730 hybrids.clone()
731 }
732
733 pub fn reorder_renderlets(&self, order: impl IntoIterator<Item = Id<Renderlet>>) {
741 log::trace!("reordering renderlets");
742 let mut guard = self.draws.write().unwrap();
744 let StageDraws {
745 ref mut hybrids,
746 indirect,
747 } = guard.deref_mut();
748 let mut ordered = vec![];
749 let mut m = FxHashMap::from_iter(std::mem::take(hybrids).into_iter().map(|r| (r.id(), r)));
750 for id in order.into_iter() {
751 if let Some(renderlet) = m.remove(&id) {
752 ordered.push(renderlet);
753 }
754 }
755 hybrids.extend(m.into_values());
756 hybrids.extend(ordered);
757 if let Some(indirect) = indirect.as_mut() {
758 indirect.draws.drain(..);
759 }
763 }
764
765 pub fn get_depth_texture(&self) -> DepthTexture {
767 DepthTexture {
768 device: self.device.clone(),
769 queue: self.queue.clone(),
770 texture: self.depth_texture.read().unwrap().clone(),
771 }
772 }
773
774 pub fn new_skybox_from_path(
775 &self,
776 path: impl AsRef<std::path::Path>,
777 camera_id: Id<Camera>,
778 ) -> Result<Skybox, AtlasImageError> {
779 let hdr = AtlasImage::from_hdr_path(path)?;
780 Ok(Skybox::new(&self.device, &self.queue, hdr, camera_id))
781 }
782
783 pub fn new_nested_transform(&self) -> NestedTransform {
784 NestedTransform::new(&self.mngr)
785 }
786
787 fn tick_internal(&self) -> (Arc<wgpu::Buffer>, Option<Arc<wgpu::Buffer>>) {
788 let maybe_indirect_draw_buffer = {
789 let mut redraw_args = false;
790 let mut draw_guard = self.draws.write().unwrap();
791 let StageDraws { hybrids, indirect } = draw_guard.deref_mut();
792 hybrids.retain(|d| {
793 if d.strong_count() > RENDERLET_STRONG_COUNT_LOWER_BOUND {
794 true
795 } else {
796 redraw_args = true;
797 false
798 }
799 });
800 if let Some(IndirectDraws { slab, draws }) = indirect.as_mut() {
801 if redraw_args || draws.len() != hybrids.len() {
802 let _ = slab.upkeep((
805 &self.device,
806 &self.queue,
807 Some("indirect draw pre upkeep"),
808 wgpu::BufferUsages::INDIRECT,
809 ));
810 *draws = hybrids
811 .iter()
812 .map(|h: &Hybrid<Renderlet>| {
813 slab.new_value(DrawIndirectArgs::from(h)).into_gpu_only()
814 })
815 .collect();
816 }
817 Some(slab.get_updated_buffer((
818 &self.device,
819 &self.queue,
820 Some("indirect draw upkeep"),
821 wgpu::BufferUsages::INDIRECT,
822 )))
823 } else {
824 None
825 }
826 };
827 if let Some(new_slab_buffer) = self.mngr.upkeep((
828 &self.device,
829 &self.queue,
830 Some("stage render upkeep"),
831 wgpu::BufferUsages::empty(),
832 )) {
833 let _ = self.skybox_bindgroup.lock().unwrap().take();
835 let _ = self.buffers_bindgroup.lock().unwrap().take();
836 (new_slab_buffer, maybe_indirect_draw_buffer)
837 } else {
838 (self.mngr.get_buffer().unwrap(), maybe_indirect_draw_buffer)
841 }
842 }
843
844 pub fn tick(&self) {
849 self.atlas.upkeep(&self.device, &self.queue);
850 let _ = self.tick_internal();
851 }
852
853 pub fn render(&self, view: &wgpu::TextureView) {
854 {
855 log::trace!("rendering the stage");
856 let label = Some("stage render");
857 let background_color = *self.background_color.read().unwrap();
859 let (slab_buffer, maybe_indirect_draw_buffer) = self.tick_internal();
860 let pipeline = self.stage_pipeline.read().unwrap();
862 let msaa_target = self.msaa_render_target.read().unwrap();
864 let slab_buffers_bindgroup = self.get_slab_buffers_bindgroup(&slab_buffer);
865 let textures_bindgroup = self.get_textures_bindgroup();
866 let has_skybox = self.has_skybox.load(Ordering::Relaxed);
867 let may_skybox_pipeline_and_bindgroup = if has_skybox {
868 Some(self.get_skybox_pipeline_and_bindgroup(&slab_buffer))
869 } else {
870 None
871 };
872 let clear_colors = self.clear_color_attachments.load(Ordering::Relaxed);
873 let clear_depth = self.clear_depth_attachments.load(Ordering::Relaxed);
874
875 let draws = self.draws.read().unwrap();
877
878 let mut encoder = self
879 .device
880 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label });
881 {
882 let hdr_texture = self.hdr_texture.read().unwrap();
883 let depth_texture = self.depth_texture.read().unwrap();
884 let ops = wgpu::Operations {
885 load: if clear_colors {
886 wgpu::LoadOp::Clear(background_color)
887 } else {
888 wgpu::LoadOp::Load
889 },
890 store: wgpu::StoreOp::Store,
891 };
892 let color_attachment = if let Some(msaa_view) = msaa_target.as_ref() {
893 wgpu::RenderPassColorAttachment {
894 ops,
895 view: msaa_view,
896 resolve_target: Some(&hdr_texture.view),
897 }
898 } else {
899 wgpu::RenderPassColorAttachment {
900 ops,
901 view: &hdr_texture.view,
902 resolve_target: None,
903 }
904 };
905 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
906 label,
907 color_attachments: &[Some(color_attachment)],
908 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
909 view: &depth_texture.view,
910 depth_ops: Some(wgpu::Operations {
911 load: if clear_depth {
912 wgpu::LoadOp::Clear(1.0)
913 } else {
914 wgpu::LoadOp::Load
915 },
916 store: wgpu::StoreOp::Store,
917 }),
918 stencil_ops: None,
919 }),
920 ..Default::default()
921 });
922
923 render_pass.set_pipeline(&pipeline);
924 render_pass.set_bind_group(0, &slab_buffers_bindgroup, &[]);
925 render_pass.set_bind_group(1, &textures_bindgroup, &[]);
926 let num_draw_calls = draws.hybrids.len();
927 if num_draw_calls > 0 {
928 if let Some(indirect_draw_buffer) = maybe_indirect_draw_buffer {
929 render_pass.multi_draw_indirect(
930 &indirect_draw_buffer,
931 0,
932 draws.hybrids.len() as u32,
933 );
934 } else {
935 for hybrid in draws.hybrids.iter() {
936 let rlet = hybrid.get();
937 if rlet.visible {
938 let vertex_range = if rlet.indices_array.is_null() {
939 0..rlet.vertices_array.len() as u32
940 } else {
941 0..rlet.indices_array.len() as u32
942 };
943 let id = hybrid.id();
944 let instance_range = id.inner()..id.inner() + 1;
945 log::trace!(
946 "drawing vertices {vertex_range:?} and instances \
947 {instance_range:?}"
948 );
949 render_pass.draw(vertex_range, instance_range);
950 }
951 }
952 }
953 }
954
955 if let Some((pipeline, bindgroup)) = may_skybox_pipeline_and_bindgroup.as_ref() {
956 log::trace!("rendering skybox");
957 let skybox = self.skybox.read().unwrap();
959 render_pass.set_pipeline(&pipeline.pipeline);
960 render_pass.set_bind_group(0, bindgroup, &[]);
961 render_pass.draw(0..36, skybox.camera.inner()..skybox.camera.inner() + 1);
962 }
963 }
964 self.queue.submit(std::iter::once(encoder.finish()));
965 }
966
967 if self.has_bloom.load(Ordering::Relaxed) {
969 log::trace!("stage bloom");
970 self.bloom.bloom(&self.device, &self.queue);
971 } else {
972 let mut encoder = self
974 .device
975 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
976 label: Some("no bloom copy"),
977 });
978 let bloom_mix_texture = self.bloom.get_mix_texture();
979 encoder.copy_texture_to_texture(
980 wgpu::ImageCopyTexture {
981 texture: &self.hdr_texture.read().unwrap().texture,
982 mip_level: 0,
983 origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
984 aspect: wgpu::TextureAspect::All,
985 },
986 wgpu::ImageCopyTexture {
987 texture: &bloom_mix_texture.texture,
988 mip_level: 0,
989 origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
990 aspect: wgpu::TextureAspect::All,
991 },
992 wgpu::Extent3d {
993 width: bloom_mix_texture.width(),
994 height: bloom_mix_texture.height(),
995 depth_or_array_layers: 1,
996 },
997 );
998 self.queue.submit(std::iter::once(encoder.finish()));
999 }
1000
1001 log::trace!("stage tonemapping");
1003 self.tonemapping.render(&self.device, &self.queue, view);
1004 }
1005}
1006
1007#[derive(Clone)]
1011pub struct NestedTransform {
1012 global_transform_id: Id<Transform>,
1013 local_transform: Arc<RwLock<Transform>>,
1014
1015 notifier_index: usize,
1016 notify: async_channel::Sender<usize>,
1017
1018 children: Arc<RwLock<Vec<NestedTransform>>>,
1019 parent: Arc<RwLock<Option<NestedTransform>>>,
1020}
1021
1022impl Drop for NestedTransform {
1023 fn drop(&mut self) {
1024 let _ = self.notify.try_send(self.notifier_index);
1025 }
1026}
1027
1028impl core::fmt::Debug for NestedTransform {
1029 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1030 let children = self
1031 .children
1032 .read()
1033 .unwrap()
1034 .iter()
1035 .map(|nt| nt.global_transform_id)
1036 .collect::<Vec<_>>();
1037 let parent = self
1038 .parent
1039 .read()
1040 .unwrap()
1041 .as_ref()
1042 .map(|nt| nt.global_transform_id);
1043 f.debug_struct("NestedTransform")
1044 .field("local_transform", &self.local_transform)
1045 .field("children", &children)
1046 .field("parent", &parent)
1047 .finish()
1048 }
1049}
1050
1051impl UpdatesSlab for NestedTransform {
1052 fn id(&self) -> usize {
1053 self.notifier_index
1054 }
1055
1056 fn u32_array(&self) -> Array<u32> {
1057 Array::new(
1058 self.global_transform_id.inner(),
1059 Transform::SLAB_SIZE as u32,
1060 )
1061 }
1062
1063 fn strong_count(&self) -> usize {
1064 Arc::strong_count(&self.local_transform)
1065 }
1066
1067 fn get_update(&self) -> Vec<SlabUpdate> {
1068 let transform = self.get_global_transform();
1069 let array = self.u32_array();
1070 let mut elements = vec![0u32; Transform::SLAB_SIZE];
1071 elements.write_indexed(&transform, 0);
1072 vec![SlabUpdate { array, elements }]
1073 }
1074}
1075
1076impl NestedTransform {
1077 pub fn new(mngr: &SlabAllocator<impl IsBuffer>) -> Self {
1078 let id = mngr.allocate::<Transform>();
1079 let notifier_index = mngr.next_update_k();
1080
1081 let nested = NestedTransform {
1082 global_transform_id: id,
1083 local_transform: Arc::new(RwLock::new(Transform::default())),
1084
1085 notifier_index,
1086 notify: mngr.notifier.0.clone(),
1087
1088 children: Default::default(),
1089 parent: Default::default(),
1090 };
1091
1092 mngr.insert_update_source(notifier_index, nested.clone());
1093
1094 nested.mark_dirty();
1095 nested
1096 }
1097
1098 #[cfg(test)]
1099 pub(crate) fn get_notifier_index(&self) -> usize {
1100 self.notifier_index
1101 }
1102
1103 fn mark_dirty(&self) {
1104 self.notify.try_send(self.notifier_index).unwrap();
1106 for child in self.children.read().unwrap().iter() {
1107 child.mark_dirty();
1108 }
1109 }
1110
1111 pub fn modify(&self, f: impl Fn(&mut Transform)) {
1115 let mut local_guard = self.local_transform.write().unwrap();
1117 f(&mut local_guard);
1118 self.mark_dirty();
1119 }
1120
1121 pub fn set(&self, transform: Transform) {
1125 self.modify(move |t| {
1126 *t = transform;
1127 });
1128 }
1129
1130 pub fn get(&self) -> Transform {
1131 *self.local_transform.read().unwrap()
1132 }
1133
1134 pub fn get_global_transform(&self) -> Transform {
1135 let maybe_parent_guard = self.parent.read().unwrap();
1136 let transform = self.get();
1137 let parent_transform = maybe_parent_guard
1138 .as_ref()
1139 .map(|parent| parent.get_global_transform())
1140 .unwrap_or_default();
1141 Transform::from(Mat4::from(parent_transform) * Mat4::from(transform))
1142 }
1143
1144 pub fn global_transform_id(&self) -> Id<Transform> {
1145 self.global_transform_id
1146 }
1147
1148 pub fn add_child(&self, node: &NestedTransform) {
1149 *node.parent.write().unwrap() = Some(self.clone());
1150 node.mark_dirty();
1151 self.children.write().unwrap().push(node.clone());
1152 }
1153
1154 pub fn remove_child(&self, node: &NestedTransform) {
1155 self.children.write().unwrap().retain_mut(|child| {
1156 if child.global_transform_id == node.global_transform_id {
1157 node.mark_dirty();
1158 let _ = node.parent.write().unwrap().take();
1159 false
1160 } else {
1161 true
1162 }
1163 });
1164 }
1165
1166 pub fn parent(&self) -> Option<NestedTransform> {
1167 self.parent.read().unwrap().clone()
1168 }
1169}
1170
1171#[cfg(test)]
1172mod test {
1173 use std::sync::Mutex;
1174
1175 use crabslab::{Array, Slab};
1176 use glam::{Mat4, Vec2, Vec3};
1177
1178 use crate::{
1179 camera::Camera,
1180 stage::{
1181 cpu::{SlabAllocator, UpdatesSlab},
1182 NestedTransform, Renderlet, Vertex,
1183 },
1184 transform::Transform,
1185 Context,
1186 };
1187
1188 #[test]
1189 fn vertex_slab_roundtrip() {
1190 let initial_vertices = {
1191 let tl = Vertex::default()
1192 .with_position(Vec3::ZERO)
1193 .with_uv0(Vec2::ZERO);
1194 let tr = Vertex::default()
1195 .with_position(Vec3::new(1.0, 0.0, 0.0))
1196 .with_uv0(Vec2::new(1.0, 0.0));
1197 let bl = Vertex::default()
1198 .with_position(Vec3::new(0.0, 1.0, 0.0))
1199 .with_uv0(Vec2::new(0.0, 1.0));
1200 let br = Vertex::default()
1201 .with_position(Vec3::new(1.0, 1.0, 0.0))
1202 .with_uv0(Vec2::splat(1.0));
1203 vec![tl, bl, br, tl, br, tr]
1204 };
1205 let mut slab = [0u32; 256];
1206 slab.write_indexed_slice(&initial_vertices, 0);
1207 let vertices = slab.read_vec(Array::<Vertex>::new(0, initial_vertices.len() as u32));
1208 pretty_assertions::assert_eq!(initial_vertices, vertices);
1209 }
1210
1211 #[test]
1212 fn matrix_subtraction_sanity() {
1213 let m = Mat4::IDENTITY - Mat4::IDENTITY;
1214 assert_eq!(Mat4::ZERO, m);
1215 }
1216
1217 #[test]
1218 fn can_global_transform_calculation() {
1219 let slab = SlabAllocator::<Mutex<Vec<u32>>>::default();
1220 let root = NestedTransform::new(&slab);
1222 let child = NestedTransform::new(&slab);
1223 child.set(Transform {
1224 translation: Vec3::new(1.0, 0.0, 0.0),
1225 ..Default::default()
1226 });
1227 let grandchild = NestedTransform::new(&slab);
1228 grandchild.set(Transform {
1229 translation: Vec3::new(1.0, 0.0, 0.0),
1230 ..Default::default()
1231 });
1232
1233 root.add_child(&child);
1235 child.add_child(&grandchild);
1236
1237 let grandchild_global_transform = grandchild.get_global_transform();
1239
1240 assert_eq!(
1242 grandchild_global_transform.translation.x, 2.0,
1243 "Grandchild's global translation should 2.0 along the x-axis"
1244 );
1245 }
1246
1247 #[test]
1248 fn can_msaa() {
1249 let ctx = Context::headless(100, 100);
1250 let stage = ctx
1251 .new_stage()
1252 .with_background_color([1.0, 1.0, 1.0, 1.0])
1253 .with_lighting(false);
1254 let camera = stage.new_value(Camera::default_ortho2d(100.0, 100.0));
1255 let vertices = stage.new_array([
1256 Vertex::default()
1257 .with_position([10.0, 10.0, 0.0])
1258 .with_color([0.0, 1.0, 1.0, 1.0]),
1259 Vertex::default()
1260 .with_position([10.0, 90.0, 0.0])
1261 .with_color([1.0, 1.0, 0.0, 1.0]),
1262 Vertex::default()
1263 .with_position([90.0, 10.0, 0.0])
1264 .with_color([1.0, 0.0, 1.0, 1.0]),
1265 ]);
1266 let triangle = stage.new_value(Renderlet {
1267 camera_id: camera.id(),
1268 vertices_array: vertices.array(),
1269 ..Default::default()
1270 });
1271 stage.add_renderlet(&triangle);
1272
1273 log::debug!("rendering without msaa");
1274 let frame = ctx.get_next_frame().unwrap();
1275 stage.render(&frame.view());
1276 let img = frame.read_image().unwrap();
1277 img_diff::assert_img_eq_cfg(
1278 "msaa/without.png",
1279 img,
1280 img_diff::DiffCfg {
1281 pixel_threshold: img_diff::LOW_PIXEL_THRESHOLD,
1282 ..Default::default()
1283 },
1284 );
1285 frame.present();
1286 log::debug!(" all good!");
1287
1288 stage.set_msaa_sample_count(4);
1289 log::debug!("rendering with msaa");
1290 let frame = ctx.get_next_frame().unwrap();
1291 stage.render(&frame.view());
1292 let img = frame.read_image().unwrap();
1293 img_diff::assert_img_eq_cfg(
1294 "msaa/with.png",
1295 img,
1296 img_diff::DiffCfg {
1297 pixel_threshold: img_diff::LOW_PIXEL_THRESHOLD,
1298 ..Default::default()
1299 },
1300 );
1301 frame.present();
1302 }
1303
1304 #[test]
1305 fn has_consistent_stage_renderlet_strong_count() {
1306 let ctx = Context::headless(100, 100);
1307 let stage = ctx.new_stage();
1308 let r = stage.new_value(Renderlet::default());
1309 stage.add_renderlet(&r);
1310 let expected_strong_count = 1 + super::RENDERLET_STRONG_COUNT_LOWER_BOUND;
1312 assert_eq!(expected_strong_count, r.strong_count());
1313 }
1314}