1use std::cell::{Cell, RefCell};
53use std::sync::Arc;
54use std::sync::atomic::{AtomicU32, Ordering};
55
56use astrelis_core::profiling::{profile_function, profile_scope};
57use astrelis_winit::window::WinitWindow;
58
59use crate::Color;
60use crate::context::GraphicsContext;
61use crate::framebuffer::Framebuffer;
62use crate::gpu_profiling::GpuFrameProfiler;
63use crate::target::RenderTarget;
64
65#[derive(Debug, Clone, Copy, Default)]
69pub struct FrameStats {
70 pub passes: usize,
72 pub draw_calls: usize,
74}
75
76pub struct AtomicFrameStats {
81 passes: AtomicU32,
82 draw_calls: AtomicU32,
83}
84
85impl AtomicFrameStats {
86 pub fn new() -> Self {
88 Self {
89 passes: AtomicU32::new(0),
90 draw_calls: AtomicU32::new(0),
91 }
92 }
93
94 pub fn increment_passes(&self) {
96 self.passes.fetch_add(1, Ordering::Relaxed);
97 }
98
99 pub fn increment_draw_calls(&self) {
101 self.draw_calls.fetch_add(1, Ordering::Relaxed);
102 }
103
104 pub fn passes(&self) -> u32 {
106 self.passes.load(Ordering::Relaxed)
107 }
108
109 pub fn draw_calls(&self) -> u32 {
111 self.draw_calls.load(Ordering::Relaxed)
112 }
113
114 pub fn to_frame_stats(&self) -> FrameStats {
116 FrameStats {
117 passes: self.passes() as usize,
118 draw_calls: self.draw_calls() as usize,
119 }
120 }
121}
122
123impl Default for AtomicFrameStats {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129pub struct Surface {
134 pub(crate) texture: wgpu::SurfaceTexture,
135 pub(crate) view: wgpu::TextureView,
136}
137
138impl Surface {
139 pub fn texture(&self) -> &wgpu::Texture {
141 &self.texture.texture
142 }
143
144 pub fn view(&self) -> &wgpu::TextureView {
146 &self.view
147 }
148}
149
150pub struct Frame<'w> {
189 pub(crate) window: &'w crate::window::RenderWindow,
191 pub(crate) surface: Option<Surface>,
193 pub(crate) command_buffers: RefCell<Vec<wgpu::CommandBuffer>>,
195 pub(crate) stats: Arc<AtomicFrameStats>,
197 pub(crate) submitted: Cell<bool>,
199 pub(crate) surface_format: wgpu::TextureFormat,
201 pub(crate) gpu_profiler: Option<Arc<GpuFrameProfiler>>,
203 pub(crate) winit_window: Arc<WinitWindow>,
205}
206
207impl<'w> Frame<'w> {
208 pub fn surface_view(&self) -> &wgpu::TextureView {
213 self.surface
214 .as_ref()
215 .expect("Surface already consumed")
216 .view()
217 }
218
219 pub fn try_surface_view(&self) -> Option<&wgpu::TextureView> {
221 self.surface.as_ref().map(|s| s.view())
222 }
223
224 pub fn depth_view(&self) -> Option<&wgpu::TextureView> {
229 self.window.depth_view_ref()
230 }
231
232 pub fn surface_format(&self) -> wgpu::TextureFormat {
234 self.surface_format
235 }
236
237 pub fn size(&self) -> (u32, u32) {
239 self.window.size()
240 }
241
242 pub fn graphics(&self) -> &GraphicsContext {
244 self.window.graphics()
245 }
246
247 pub fn device(&self) -> &wgpu::Device {
249 self.window.graphics().device()
250 }
251
252 pub fn queue(&self) -> &wgpu::Queue {
254 self.window.graphics().queue()
255 }
256
257 pub fn stats(&self) -> FrameStats {
259 self.stats.to_frame_stats()
260 }
261
262 pub fn gpu_profiler(&self) -> Option<&GpuFrameProfiler> {
264 self.gpu_profiler.as_deref()
265 }
266
267 pub fn has_gpu_profiler(&self) -> bool {
269 self.gpu_profiler.is_some()
270 }
271
272 pub fn create_encoder(&self, label: Option<&str>) -> wgpu::CommandEncoder {
277 self.device()
278 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label })
279 }
280
281 pub fn add_command_buffer(&self, buffer: wgpu::CommandBuffer) {
285 self.command_buffers.borrow_mut().push(buffer);
286 }
287
288 pub fn render_pass(&self) -> RenderPassBuilder<'_, 'w> {
293 RenderPassBuilder::new(self)
294 }
295
296 pub fn compute_pass(&self) -> crate::compute::ComputePassBuilder<'_, 'w> {
298 crate::compute::ComputePassBuilder::new(self)
299 }
300
301 pub fn submit(self) {
306 drop(self);
308 }
309
310 fn submit_inner(&self) {
312 profile_function!();
313
314 if self.stats.passes() == 0 {
315 tracing::warn!("No render passes were executed for this frame");
316 }
317
318 if let Some(ref profiler) = self.gpu_profiler {
320 let mut resolve_encoder = self.create_encoder(Some("Profiler Resolve"));
322 profiler.resolve_queries(&mut resolve_encoder);
323 self.command_buffers
324 .borrow_mut()
325 .push(resolve_encoder.finish());
326 }
327
328 let buffers = std::mem::take(&mut *self.command_buffers.borrow_mut());
330
331 if !buffers.is_empty() {
332 profile_scope!("submit_commands");
333 self.queue().submit(buffers);
334 }
335
336 if let Some(_surface) = self.surface.as_ref() {
338 profile_scope!("present_surface");
339 }
342
343 if let Some(ref profiler) = self.gpu_profiler
345 && let Err(e) = profiler.end_frame()
346 {
347 tracing::warn!("GPU profiler end_frame error: {e:?}");
348 }
349 }
350}
351
352impl Drop for Frame<'_> {
353 fn drop(&mut self) {
354 if !self.submitted.get() {
355 self.submitted.set(true);
356 self.submit_inner();
357 }
358
359 if let Some(surface) = self.surface.take() {
361 profile_scope!("present_surface");
362 surface.texture.present();
363 }
364
365 self.winit_window.request_redraw();
367 }
368}
369
370#[derive(Debug, Clone, Copy, Default)]
376pub enum ColorTarget<'a> {
377 #[default]
379 Surface,
380 Custom(&'a wgpu::TextureView),
382 Framebuffer(&'a Framebuffer),
384}
385
386#[derive(Debug, Clone, Copy, Default)]
388pub enum ColorOp {
389 Clear(wgpu::Color),
391 #[default]
393 Load,
394}
395
396impl From<Color> for ColorOp {
397 fn from(color: Color) -> Self {
398 Self::Clear(color.to_wgpu())
399 }
400}
401
402impl From<wgpu::Color> for ColorOp {
403 fn from(color: wgpu::Color) -> Self {
404 Self::Clear(color)
405 }
406}
407
408#[derive(Debug, Clone, Copy)]
410pub enum DepthOp {
411 Clear(f32),
413 Load,
415 ReadOnly,
417}
418
419impl Default for DepthOp {
420 fn default() -> Self {
421 Self::Clear(1.0)
422 }
423}
424
425pub struct RenderPassBuilder<'f, 'w> {
441 frame: &'f Frame<'w>,
442 color_target: ColorTarget<'f>,
443 color_op: ColorOp,
444 depth_view: Option<&'f wgpu::TextureView>,
445 depth_op: DepthOp,
446 label: Option<String>,
447}
448
449impl<'f, 'w> RenderPassBuilder<'f, 'w> {
450 pub(crate) fn new(frame: &'f Frame<'w>) -> Self {
452 Self {
453 frame,
454 color_target: ColorTarget::Surface,
455 color_op: ColorOp::Load,
456 depth_view: None,
457 depth_op: DepthOp::default(),
458 label: None,
459 }
460 }
461
462 pub fn target(mut self, target: RenderTarget<'f>) -> Self {
464 match target {
465 RenderTarget::Surface => {
466 self.color_target = ColorTarget::Surface;
467 }
468 RenderTarget::SurfaceWithDepth {
469 depth_view,
470 clear_value,
471 } => {
472 self.color_target = ColorTarget::Surface;
473 self.depth_view = Some(depth_view);
474 if let Some(v) = clear_value {
475 self.depth_op = DepthOp::Clear(v);
476 } else {
477 self.depth_op = DepthOp::Load;
478 }
479 }
480 RenderTarget::Framebuffer(fb) => {
481 self.color_target = ColorTarget::Framebuffer(fb);
482 if let Some(dv) = fb.depth_view() {
483 self.depth_view = Some(dv);
484 }
485 }
486 }
487 self
488 }
489
490 pub fn to_surface(mut self) -> Self {
492 self.color_target = ColorTarget::Surface;
493 self
494 }
495
496 pub fn to_framebuffer(mut self, fb: &'f Framebuffer) -> Self {
498 self.color_target = ColorTarget::Framebuffer(fb);
499 if let Some(dv) = fb.depth_view() {
500 self.depth_view = Some(dv);
501 }
502 self
503 }
504
505 pub fn to_texture(mut self, view: &'f wgpu::TextureView) -> Self {
507 self.color_target = ColorTarget::Custom(view);
508 self
509 }
510
511 pub fn clear_color(mut self, color: impl Into<ColorOp>) -> Self {
513 self.color_op = color.into();
514 self
515 }
516
517 pub fn load_color(mut self) -> Self {
519 self.color_op = ColorOp::Load;
520 self
521 }
522
523 pub fn depth_attachment(mut self, view: &'f wgpu::TextureView) -> Self {
525 self.depth_view = Some(view);
526 self
527 }
528
529 pub fn with_window_depth(mut self) -> Self {
534 self.depth_view = Some(
535 self.frame
536 .depth_view()
537 .expect("Window must have depth buffer for with_window_depth()"),
538 );
539 self
540 }
541
542 pub fn with_window_depth_if_available(mut self) -> Self {
544 if let Some(dv) = self.frame.depth_view() {
545 self.depth_view = Some(dv);
546 }
547 self
548 }
549
550 pub fn clear_depth(mut self, value: f32) -> Self {
552 self.depth_op = DepthOp::Clear(value);
553 self
554 }
555
556 pub fn load_depth(mut self) -> Self {
558 self.depth_op = DepthOp::Load;
559 self
560 }
561
562 pub fn depth_readonly(mut self) -> Self {
564 self.depth_op = DepthOp::ReadOnly;
565 self
566 }
567
568 pub fn label(mut self, name: impl Into<String>) -> Self {
570 self.label = Some(name.into());
571 self
572 }
573
574 pub fn build(self) -> RenderPass<'f> {
579 profile_function!();
580
581 let label = self.label.clone();
582 let label_str = label.as_deref();
583
584 let encoder = self
586 .frame
587 .device()
588 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: label_str });
589
590 let color_view = match self.color_target {
592 ColorTarget::Surface => self.frame.surface_view(),
593 ColorTarget::Custom(v) => v,
594 ColorTarget::Framebuffer(fb) => fb.render_view(),
595 };
596
597 let color_ops = match self.color_op {
598 ColorOp::Clear(color) => wgpu::Operations {
599 load: wgpu::LoadOp::Clear(color),
600 store: wgpu::StoreOp::Store,
601 },
602 ColorOp::Load => wgpu::Operations {
603 load: wgpu::LoadOp::Load,
604 store: wgpu::StoreOp::Store,
605 },
606 };
607
608 let resolve_target = match self.color_target {
609 ColorTarget::Framebuffer(fb) => fb.resolve_target(),
610 _ => None,
611 };
612
613 let color_attachments = [Some(wgpu::RenderPassColorAttachment {
614 view: color_view,
615 resolve_target,
616 ops: color_ops,
617 depth_slice: None,
618 })];
619
620 let depth_attachment = self.depth_view.map(|view| {
622 let (depth_ops, read_only) = match self.depth_op {
623 DepthOp::Clear(value) => (
624 Some(wgpu::Operations {
625 load: wgpu::LoadOp::Clear(value),
626 store: wgpu::StoreOp::Store,
627 }),
628 false,
629 ),
630 DepthOp::Load => (
631 Some(wgpu::Operations {
632 load: wgpu::LoadOp::Load,
633 store: wgpu::StoreOp::Store,
634 }),
635 false,
636 ),
637 DepthOp::ReadOnly => (
638 Some(wgpu::Operations {
639 load: wgpu::LoadOp::Load,
640 store: wgpu::StoreOp::Discard,
641 }),
642 true,
643 ),
644 };
645
646 wgpu::RenderPassDepthStencilAttachment {
647 view,
648 depth_ops: if read_only { None } else { depth_ops },
649 stencil_ops: None,
650 }
651 });
652
653 self.frame.stats.increment_passes();
655
656 let mut encoder = encoder;
659 let pass = encoder
660 .begin_render_pass(&wgpu::RenderPassDescriptor {
661 label: label_str,
662 color_attachments: &color_attachments,
663 depth_stencil_attachment: depth_attachment,
664 timestamp_writes: None,
665 occlusion_query_set: None,
666 })
667 .forget_lifetime();
668
669 RenderPass {
670 frame: self.frame,
671 encoder: Some(encoder),
672 pass: Some(pass),
673 stats: self.frame.stats.clone(),
674 #[cfg(feature = "gpu-profiling")]
675 profiler_scope: None,
676 }
677 }
678}
679
680pub struct RenderPass<'f> {
693 frame: &'f Frame<'f>,
695 encoder: Option<wgpu::CommandEncoder>,
697 pass: Option<wgpu::RenderPass<'static>>,
699 stats: Arc<AtomicFrameStats>,
701 #[cfg(feature = "gpu-profiling")]
703 profiler_scope: Option<wgpu_profiler::scope::OwningScope>,
704}
705
706impl<'f> RenderPass<'f> {
707 pub fn wgpu_pass(&mut self) -> &mut wgpu::RenderPass<'static> {
712 self.pass.as_mut().expect("RenderPass already consumed")
713 }
714
715 pub fn wgpu_pass_ref(&self) -> &wgpu::RenderPass<'static> {
720 self.pass.as_ref().expect("RenderPass already consumed")
721 }
722
723 pub fn try_wgpu_pass(&mut self) -> Option<&mut wgpu::RenderPass<'static>> {
725 self.pass.as_mut()
726 }
727
728 pub fn is_valid(&self) -> bool {
730 self.pass.is_some()
731 }
732
733 pub fn raw_pass(&mut self) -> &mut wgpu::RenderPass<'static> {
735 self.wgpu_pass()
736 }
737
738 pub fn encoder(&self) -> Option<&wgpu::CommandEncoder> {
740 self.encoder.as_ref()
741 }
742
743 pub fn encoder_mut(&mut self) -> Option<&mut wgpu::CommandEncoder> {
745 self.encoder.as_mut()
746 }
747
748 pub fn graphics(&self) -> &GraphicsContext {
750 self.frame.graphics()
751 }
752
753 pub fn record_draw_call(&self) {
755 self.stats.increment_draw_calls();
756 }
757
758 pub fn into_encoder(mut self) -> wgpu::CommandEncoder {
763 drop(self.pass.take());
765
766 self.encoder.take().expect("Encoder already taken")
768 }
769
770 pub fn finish(self) {
772 drop(self);
773 }
774
775 pub fn set_viewport_physical(
781 &mut self,
782 rect: astrelis_core::geometry::PhysicalRect<f32>,
783 min_depth: f32,
784 max_depth: f32,
785 ) {
786 self.wgpu_pass().set_viewport(
787 rect.x,
788 rect.y,
789 rect.width,
790 rect.height,
791 min_depth,
792 max_depth,
793 );
794 }
795
796 pub fn set_viewport_logical(
798 &mut self,
799 rect: astrelis_core::geometry::LogicalRect<f32>,
800 min_depth: f32,
801 max_depth: f32,
802 scale: astrelis_core::geometry::ScaleFactor,
803 ) {
804 let physical = rect.to_physical_f32(scale);
805 self.set_viewport_physical(physical, min_depth, max_depth);
806 }
807
808 pub fn set_viewport(&mut self, viewport: &crate::Viewport) {
810 self.wgpu_pass().set_viewport(
811 viewport.position.x,
812 viewport.position.y,
813 viewport.size.width,
814 viewport.size.height,
815 0.0,
816 1.0,
817 );
818 }
819
820 pub fn set_scissor_physical(&mut self, rect: astrelis_core::geometry::PhysicalRect<u32>) {
822 self.wgpu_pass()
823 .set_scissor_rect(rect.x, rect.y, rect.width, rect.height);
824 }
825
826 pub fn set_scissor_logical(
828 &mut self,
829 rect: astrelis_core::geometry::LogicalRect<f32>,
830 scale: astrelis_core::geometry::ScaleFactor,
831 ) {
832 let physical = rect.to_physical(scale);
833 self.set_scissor_physical(physical);
834 }
835
836 pub fn set_pipeline(&mut self, pipeline: &wgpu::RenderPipeline) {
842 self.wgpu_pass().set_pipeline(pipeline);
843 }
844
845 pub fn set_bind_group(&mut self, index: u32, bind_group: &wgpu::BindGroup, offsets: &[u32]) {
847 self.wgpu_pass().set_bind_group(index, bind_group, offsets);
848 }
849
850 pub fn set_vertex_buffer(&mut self, slot: u32, buffer_slice: wgpu::BufferSlice<'_>) {
852 self.wgpu_pass().set_vertex_buffer(slot, buffer_slice);
853 }
854
855 pub fn set_index_buffer(
857 &mut self,
858 buffer_slice: wgpu::BufferSlice<'_>,
859 format: wgpu::IndexFormat,
860 ) {
861 self.wgpu_pass().set_index_buffer(buffer_slice, format);
862 }
863
864 pub fn draw(&mut self, vertices: std::ops::Range<u32>, instances: std::ops::Range<u32>) {
866 self.wgpu_pass().draw(vertices, instances);
867 self.stats.increment_draw_calls();
868 }
869
870 pub fn draw_indexed(
872 &mut self,
873 indices: std::ops::Range<u32>,
874 base_vertex: i32,
875 instances: std::ops::Range<u32>,
876 ) {
877 self.wgpu_pass()
878 .draw_indexed(indices, base_vertex, instances);
879 self.stats.increment_draw_calls();
880 }
881
882 pub fn insert_debug_marker(&mut self, label: &str) {
884 self.wgpu_pass().insert_debug_marker(label);
885 }
886
887 pub fn push_debug_group(&mut self, label: &str) {
889 self.wgpu_pass().push_debug_group(label);
890 }
891
892 pub fn pop_debug_group(&mut self) {
894 self.wgpu_pass().pop_debug_group();
895 }
896
897 pub fn set_push_constants<T: bytemuck::Pod>(
903 &mut self,
904 stages: wgpu::ShaderStages,
905 offset: u32,
906 data: &T,
907 ) {
908 self.wgpu_pass()
909 .set_push_constants(stages, offset, bytemuck::bytes_of(data));
910 }
911
912 pub fn set_push_constants_raw(&mut self, stages: wgpu::ShaderStages, offset: u32, data: &[u8]) {
914 self.wgpu_pass().set_push_constants(stages, offset, data);
915 }
916}
917
918impl Drop for RenderPass<'_> {
919 fn drop(&mut self) {
920 profile_function!();
921
922 #[cfg(feature = "gpu-profiling")]
924 drop(self.profiler_scope.take());
925
926 drop(self.pass.take());
928
929 if let Some(encoder) = self.encoder.take() {
931 let command_buffer = encoder.finish();
932 self.frame.command_buffers.borrow_mut().push(command_buffer);
933 }
934 }
935}
936
937#[derive(Debug, Clone, Copy, Default)]
943pub enum ClearOp {
944 #[default]
946 Load,
947 Clear(wgpu::Color),
949}
950
951impl From<wgpu::Color> for ClearOp {
952 fn from(color: wgpu::Color) -> Self {
953 ClearOp::Clear(color)
954 }
955}
956
957impl From<Color> for ClearOp {
958 fn from(color: Color) -> Self {
959 ClearOp::Clear(color.to_wgpu())
960 }
961}
962
963#[derive(Debug, Clone, Copy)]
965pub enum DepthClearOp {
966 Load,
968 Clear(f32),
970}
971
972impl Default for DepthClearOp {
973 fn default() -> Self {
974 DepthClearOp::Clear(1.0)
975 }
976}