1use std::sync::Arc;
2
3use astrelis_core::profiling::{profile_function, profile_scope};
4use astrelis_winit::window::WinitWindow;
5
6use crate::context::GraphicsContext;
7use crate::target::RenderTarget;
8
9pub struct FrameStats {
11 pub passes: usize,
12 pub draw_calls: usize,
13}
14
15impl FrameStats {
16 pub(crate) fn new() -> Self {
17 Self {
18 passes: 0,
19 draw_calls: 0,
20 }
21 }
22}
23
24pub struct Surface {
26 pub(crate) texture: wgpu::SurfaceTexture,
27 pub(crate) view: wgpu::TextureView,
28}
29
30impl Surface {
31 pub fn texture(&self) -> &wgpu::Texture {
32 &self.texture.texture
33 }
34
35 pub fn view(&self) -> &wgpu::TextureView {
36 &self.view
37 }
38}
39
40pub struct FrameContext {
42 pub(crate) stats: FrameStats,
43 pub(crate) surface: Option<Surface>,
44 pub(crate) encoder: Option<wgpu::CommandEncoder>,
45 pub context: Arc<GraphicsContext>,
46 pub(crate) window: Arc<WinitWindow>,
47 pub(crate) surface_format: wgpu::TextureFormat,
48}
49
50impl FrameContext {
51 pub fn surface(&self) -> &Surface {
52 self.surface.as_ref().unwrap()
53 }
54
55 pub fn surface_format(&self) -> wgpu::TextureFormat {
56 self.surface_format
57 }
58
59 pub fn increment_passes(&mut self) {
60 self.stats.passes += 1;
61 }
62
63 pub fn increment_draw_calls(&mut self) {
64 self.stats.draw_calls += 1;
65 }
66
67 pub fn stats(&self) -> &FrameStats {
68 &self.stats
69 }
70
71 pub fn graphics_context(&self) -> &GraphicsContext {
72 &self.context
73 }
74
75 pub fn encoder(&mut self) -> &mut wgpu::CommandEncoder {
76 self.encoder.as_mut().expect("Encoder already taken")
77 }
78
79 pub fn encoder_and_surface(&mut self) -> (&mut wgpu::CommandEncoder, &Surface) {
80 (
81 self.encoder.as_mut().expect("Encoder already taken"),
82 self.surface.as_ref().unwrap(),
83 )
84 }
85
86 pub fn finish(self) {
87 drop(self);
88 }
89
90 pub fn with_pass<'a, F>(&'a mut self, builder: RenderPassBuilder<'a>, f: F)
111 where
112 F: FnOnce(&mut RenderPass<'a>),
113 {
114 let mut pass = builder.build(self);
115 f(&mut pass);
116 }
118
119 pub fn clear_and_render<'a, F>(
138 &'a mut self,
139 target: RenderTarget<'a>,
140 clear_color: impl Into<crate::Color>,
141 f: F,
142 ) where
143 F: FnOnce(&mut RenderPass<'a>),
144 {
145 self.with_pass(
146 RenderPassBuilder::new()
147 .target(target)
148 .clear_color(clear_color.into()),
149 f,
150 );
151 }
152}
153
154impl Drop for FrameContext {
155 fn drop(&mut self) {
156 profile_function!();
157
158 if self.stats.passes == 0 {
159 tracing::error!("No render passes were executed for this frame!");
160 return;
161 }
162
163 if let Some(encoder) = self.encoder.take() {
164 profile_scope!("submit_commands");
165 self.context.queue.submit(std::iter::once(encoder.finish()));
166 }
167
168 if let Some(surface) = self.surface.take() {
169 profile_scope!("present_surface");
170 surface.texture.present();
171 }
172
173 self.window.request_redraw();
175 }
176}
177
178#[derive(Debug, Clone, Copy)]
180#[derive(Default)]
181pub enum ClearOp {
182 #[default]
184 Load,
185 Clear(wgpu::Color),
187}
188
189
190impl From<wgpu::Color> for ClearOp {
191 fn from(color: wgpu::Color) -> Self {
192 ClearOp::Clear(color)
193 }
194}
195
196impl From<crate::Color> for ClearOp {
197 fn from(color: crate::Color) -> Self {
198 ClearOp::Clear(color.to_wgpu())
199 }
200}
201
202#[derive(Debug, Clone, Copy)]
204pub enum DepthClearOp {
205 Load,
207 Clear(f32),
209}
210
211impl Default for DepthClearOp {
212 fn default() -> Self {
213 DepthClearOp::Clear(1.0)
214 }
215}
216
217pub struct RenderPassBuilder<'a> {
219 label: Option<&'a str>,
220 target: Option<RenderTarget<'a>>,
222 clear_op: ClearOp,
223 depth_clear_op: DepthClearOp,
224 color_attachments: Vec<Option<wgpu::RenderPassColorAttachment<'a>>>,
226 surface_attachment_ops: Option<(wgpu::Operations<wgpu::Color>, Option<&'a wgpu::TextureView>)>,
227 depth_stencil_attachment: Option<wgpu::RenderPassDepthStencilAttachment<'a>>,
228}
229
230impl<'a> RenderPassBuilder<'a> {
231 pub fn new() -> Self {
232 Self {
233 label: None,
234 target: None,
235 clear_op: ClearOp::Load,
236 depth_clear_op: DepthClearOp::default(),
237 color_attachments: Vec::new(),
238 surface_attachment_ops: None,
239 depth_stencil_attachment: None,
240 }
241 }
242
243 pub fn label(mut self, label: &'a str) -> Self {
245 self.label = Some(label);
246 self
247 }
248
249 pub fn target(mut self, target: RenderTarget<'a>) -> Self {
253 self.target = Some(target);
254 self
255 }
256
257 pub fn clear_color(mut self, color: impl Into<ClearOp>) -> Self {
261 self.clear_op = color.into();
262 self
263 }
264
265 pub fn clear_depth(mut self, depth: f32) -> Self {
267 self.depth_clear_op = DepthClearOp::Clear(depth);
268 self
269 }
270
271 pub fn load_depth(mut self) -> Self {
273 self.depth_clear_op = DepthClearOp::Load;
274 self
275 }
276
277 pub fn color_attachment(
283 mut self,
284 view: Option<&'a wgpu::TextureView>,
285 resolve_target: Option<&'a wgpu::TextureView>,
286 ops: wgpu::Operations<wgpu::Color>,
287 ) -> Self {
288 if let Some(view) = view {
289 self.color_attachments
290 .push(Some(wgpu::RenderPassColorAttachment {
291 view,
292 resolve_target,
293 ops,
294 depth_slice: None,
295 }));
296 } else {
297 self.surface_attachment_ops = Some((ops, resolve_target));
299 }
300 self
301 }
302
303 pub fn depth_stencil_attachment(
308 mut self,
309 view: &'a wgpu::TextureView,
310 depth_ops: Option<wgpu::Operations<f32>>,
311 stencil_ops: Option<wgpu::Operations<u32>>,
312 ) -> Self {
313 self.depth_stencil_attachment = Some(wgpu::RenderPassDepthStencilAttachment {
314 view,
315 depth_ops,
316 stencil_ops,
317 });
318 self
319 }
320
321 pub fn build(self, frame_context: &'a mut FrameContext) -> RenderPass<'a> {
327 let mut encoder = frame_context.encoder.take().unwrap();
328
329 let mut all_attachments = Vec::new();
331
332 if let Some(target) = &self.target {
333 let color_ops = match self.clear_op {
335 ClearOp::Load => wgpu::Operations {
336 load: wgpu::LoadOp::Load,
337 store: wgpu::StoreOp::Store,
338 },
339 ClearOp::Clear(color) => wgpu::Operations {
340 load: wgpu::LoadOp::Clear(color),
341 store: wgpu::StoreOp::Store,
342 },
343 };
344
345 match target {
346 RenderTarget::Surface => {
347 let surface_view = frame_context.surface().view();
348 all_attachments.push(Some(wgpu::RenderPassColorAttachment {
349 view: surface_view,
350 resolve_target: None,
351 ops: color_ops,
352 depth_slice: None,
353 }));
354 }
355 RenderTarget::Framebuffer(fb) => {
356 all_attachments.push(Some(wgpu::RenderPassColorAttachment {
357 view: fb.render_view(),
358 resolve_target: fb.resolve_target(),
359 ops: color_ops,
360 depth_slice: None,
361 }));
362 }
363 }
364 } else {
365 if let Some((ops, resolve_target)) = self.surface_attachment_ops {
367 let surface_view = frame_context.surface().view();
368 all_attachments.push(Some(wgpu::RenderPassColorAttachment {
369 view: surface_view,
370 resolve_target,
371 ops,
372 depth_slice: None,
373 }));
374 }
375 all_attachments.extend(self.color_attachments);
376 }
377
378 let depth_attachment = if let Some(attachment) = self.depth_stencil_attachment {
380 Some(attachment)
381 } else if let Some(RenderTarget::Framebuffer(fb)) = &self.target {
382 fb.depth_view().map(|view| {
383 let depth_ops = match self.depth_clear_op {
384 DepthClearOp::Load => wgpu::Operations {
385 load: wgpu::LoadOp::Load,
386 store: wgpu::StoreOp::Store,
387 },
388 DepthClearOp::Clear(depth) => wgpu::Operations {
389 load: wgpu::LoadOp::Clear(depth),
390 store: wgpu::StoreOp::Store,
391 },
392 };
393 wgpu::RenderPassDepthStencilAttachment {
394 view,
395 depth_ops: Some(depth_ops),
396 stencil_ops: None,
397 }
398 })
399 } else {
400 None
401 };
402
403 let descriptor = wgpu::RenderPassDescriptor {
404 label: self.label,
405 color_attachments: &all_attachments,
406 depth_stencil_attachment: depth_attachment,
407 occlusion_query_set: None,
408 timestamp_writes: None,
409 };
410
411 let render_pass = encoder.begin_render_pass(&descriptor).forget_lifetime();
412
413 frame_context.increment_passes();
414
415 RenderPass {
416 context: frame_context,
417 encoder: Some(encoder),
418 descriptor: Some(render_pass),
419 }
420 }
421}
422
423impl Default for RenderPassBuilder<'_> {
424 fn default() -> Self {
425 Self::new()
426 }
427}
428
429pub struct RenderPass<'a> {
431 pub context: &'a mut FrameContext,
432 pub(crate) encoder: Option<wgpu::CommandEncoder>,
433 pub(crate) descriptor: Option<wgpu::RenderPass<'static>>,
434}
435
436impl<'a> RenderPass<'a> {
437 pub fn descriptor(&mut self) -> &mut wgpu::RenderPass<'static> {
438 self.descriptor.as_mut().unwrap()
439 }
440
441 pub fn finish(self) {
442 drop(self);
443 }
444
445 pub fn set_viewport_physical(
460 &mut self,
461 rect: astrelis_core::geometry::PhysicalRect<f32>,
462 min_depth: f32,
463 max_depth: f32,
464 ) {
465 self.descriptor().set_viewport(
466 rect.x,
467 rect.y,
468 rect.width,
469 rect.height,
470 min_depth,
471 max_depth,
472 );
473 }
474
475 pub fn set_viewport_logical(
484 &mut self,
485 rect: astrelis_core::geometry::LogicalRect<f32>,
486 min_depth: f32,
487 max_depth: f32,
488 scale: astrelis_core::geometry::ScaleFactor,
489 ) {
490 let physical = rect.to_physical_f32(scale);
491 self.set_viewport_physical(physical, min_depth, max_depth);
492 }
493
494 pub fn set_viewport(&mut self, viewport: &crate::Viewport) {
498 self.descriptor().set_viewport(
499 viewport.position.x,
500 viewport.position.y,
501 viewport.size.width,
502 viewport.size.height,
503 0.0,
504 1.0,
505 );
506 }
507
508 pub fn set_scissor_physical(&mut self, rect: astrelis_core::geometry::PhysicalRect<u32>) {
517 self.descriptor()
518 .set_scissor_rect(rect.x, rect.y, rect.width, rect.height);
519 }
520
521 pub fn set_scissor_logical(
528 &mut self,
529 rect: astrelis_core::geometry::LogicalRect<f32>,
530 scale: astrelis_core::geometry::ScaleFactor,
531 ) {
532 let physical = rect.to_physical(scale);
533 self.set_scissor_physical(physical);
534 }
535
536 pub fn set_pipeline(&mut self, pipeline: &'a wgpu::RenderPipeline) {
542 self.descriptor().set_pipeline(pipeline);
543 }
544
545 pub fn set_bind_group(
547 &mut self,
548 index: u32,
549 bind_group: &'a wgpu::BindGroup,
550 offsets: &[u32],
551 ) {
552 self.descriptor().set_bind_group(index, bind_group, offsets);
553 }
554
555 pub fn set_vertex_buffer(&mut self, slot: u32, buffer_slice: wgpu::BufferSlice<'a>) {
557 self.descriptor().set_vertex_buffer(slot, buffer_slice);
558 }
559
560 pub fn set_index_buffer(&mut self, buffer_slice: wgpu::BufferSlice<'a>, format: wgpu::IndexFormat) {
562 self.descriptor().set_index_buffer(buffer_slice, format);
563 }
564
565 pub fn draw(&mut self, vertices: std::ops::Range<u32>, instances: std::ops::Range<u32>) {
567 self.descriptor().draw(vertices, instances);
568 self.context.increment_draw_calls();
569 }
570
571 pub fn draw_indexed(
573 &mut self,
574 indices: std::ops::Range<u32>,
575 base_vertex: i32,
576 instances: std::ops::Range<u32>,
577 ) {
578 self.descriptor().draw_indexed(indices, base_vertex, instances);
579 self.context.increment_draw_calls();
580 }
581
582 pub fn insert_debug_marker(&mut self, label: &str) {
584 self.descriptor().insert_debug_marker(label);
585 }
586
587 pub fn push_debug_group(&mut self, label: &str) {
589 self.descriptor().push_debug_group(label);
590 }
591
592 pub fn pop_debug_group(&mut self) {
594 self.descriptor().pop_debug_group();
595 }
596
597 pub fn set_push_constants<T: bytemuck::Pod>(
637 &mut self,
638 stages: wgpu::ShaderStages,
639 offset: u32,
640 data: &T,
641 ) {
642 self.descriptor()
643 .set_push_constants(stages, offset, bytemuck::bytes_of(data));
644 }
645
646 pub fn set_push_constants_raw(
650 &mut self,
651 stages: wgpu::ShaderStages,
652 offset: u32,
653 data: &[u8],
654 ) {
655 self.descriptor().set_push_constants(stages, offset, data);
656 }
657}
658
659impl Drop for RenderPass<'_> {
660 fn drop(&mut self) {
661 profile_function!();
662
663 drop(self.descriptor.take());
664
665 self.context.encoder = self.encoder.take();
667 }
668}
669
670pub trait RenderPassExt {
672 fn clear_pass<'a>(
674 &'a mut self,
675 target: RenderTarget<'a>,
676 clear_color: wgpu::Color,
677 ) -> RenderPass<'a>;
678
679 fn load_pass<'a>(&'a mut self, target: RenderTarget<'a>) -> RenderPass<'a>;
681}
682
683impl RenderPassExt for FrameContext {
684 fn clear_pass<'a>(
685 &'a mut self,
686 target: RenderTarget<'a>,
687 clear_color: wgpu::Color,
688 ) -> RenderPass<'a> {
689 RenderPassBuilder::new()
690 .target(target)
691 .clear_color(clear_color)
692 .build(self)
693 }
694
695 fn load_pass<'a>(&'a mut self, target: RenderTarget<'a>) -> RenderPass<'a> {
696 RenderPassBuilder::new().target(target).build(self)
697 }
698}