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(crate) context: &'static 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) -> &'static 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
91impl Drop for FrameContext {
92 fn drop(&mut self) {
93 profile_function!();
94
95 if self.stats.passes == 0 {
96 tracing::error!("No render passes were executed for this frame!");
97 return;
98 }
99
100 if let Some(encoder) = self.encoder.take() {
101 profile_scope!("submit_commands");
102 self.context.queue.submit(std::iter::once(encoder.finish()));
103 }
104
105 if let Some(surface) = self.surface.take() {
106 profile_scope!("present_surface");
107 surface.texture.present();
108 }
109
110 self.window.request_redraw();
112 }
113}
114
115#[derive(Debug, Clone, Copy)]
117pub enum ClearOp {
118 Load,
120 Clear(wgpu::Color),
122}
123
124impl Default for ClearOp {
125 fn default() -> Self {
126 ClearOp::Load
127 }
128}
129
130impl From<wgpu::Color> for ClearOp {
131 fn from(color: wgpu::Color) -> Self {
132 ClearOp::Clear(color)
133 }
134}
135
136impl From<crate::Color> for ClearOp {
137 fn from(color: crate::Color) -> Self {
138 ClearOp::Clear(color.to_wgpu())
139 }
140}
141
142#[derive(Debug, Clone, Copy)]
144pub enum DepthClearOp {
145 Load,
147 Clear(f32),
149}
150
151impl Default for DepthClearOp {
152 fn default() -> Self {
153 DepthClearOp::Clear(1.0)
154 }
155}
156
157pub struct RenderPassBuilder<'a> {
159 label: Option<&'a str>,
160 target: Option<RenderTarget<'a>>,
162 clear_op: ClearOp,
163 depth_clear_op: DepthClearOp,
164 color_attachments: Vec<Option<wgpu::RenderPassColorAttachment<'a>>>,
166 surface_attachment_ops: Option<(wgpu::Operations<wgpu::Color>, Option<&'a wgpu::TextureView>)>,
167 depth_stencil_attachment: Option<wgpu::RenderPassDepthStencilAttachment<'a>>,
168}
169
170impl<'a> RenderPassBuilder<'a> {
171 pub fn new() -> Self {
172 Self {
173 label: None,
174 target: None,
175 clear_op: ClearOp::Load,
176 depth_clear_op: DepthClearOp::default(),
177 color_attachments: Vec::new(),
178 surface_attachment_ops: None,
179 depth_stencil_attachment: None,
180 }
181 }
182
183 pub fn label(mut self, label: &'a str) -> Self {
185 self.label = Some(label);
186 self
187 }
188
189 pub fn target(mut self, target: RenderTarget<'a>) -> Self {
193 self.target = Some(target);
194 self
195 }
196
197 pub fn clear_color(mut self, color: impl Into<ClearOp>) -> Self {
201 self.clear_op = color.into();
202 self
203 }
204
205 pub fn clear_depth(mut self, depth: f32) -> Self {
207 self.depth_clear_op = DepthClearOp::Clear(depth);
208 self
209 }
210
211 pub fn load_depth(mut self) -> Self {
213 self.depth_clear_op = DepthClearOp::Load;
214 self
215 }
216
217 pub fn color_attachment(
223 mut self,
224 view: Option<&'a wgpu::TextureView>,
225 resolve_target: Option<&'a wgpu::TextureView>,
226 ops: wgpu::Operations<wgpu::Color>,
227 ) -> Self {
228 if let Some(view) = view {
229 self.color_attachments
230 .push(Some(wgpu::RenderPassColorAttachment {
231 view,
232 resolve_target,
233 ops,
234 depth_slice: None,
235 }));
236 } else {
237 self.surface_attachment_ops = Some((ops, resolve_target));
239 }
240 self
241 }
242
243 pub fn depth_stencil_attachment(
248 mut self,
249 view: &'a wgpu::TextureView,
250 depth_ops: Option<wgpu::Operations<f32>>,
251 stencil_ops: Option<wgpu::Operations<u32>>,
252 ) -> Self {
253 self.depth_stencil_attachment = Some(wgpu::RenderPassDepthStencilAttachment {
254 view,
255 depth_ops,
256 stencil_ops,
257 });
258 self
259 }
260
261 pub fn build(self, frame_context: &'a mut FrameContext) -> RenderPass<'a> {
267 let mut encoder = frame_context.encoder.take().unwrap();
268
269 let mut all_attachments = Vec::new();
271
272 if let Some(target) = &self.target {
273 let color_ops = match self.clear_op {
275 ClearOp::Load => wgpu::Operations {
276 load: wgpu::LoadOp::Load,
277 store: wgpu::StoreOp::Store,
278 },
279 ClearOp::Clear(color) => wgpu::Operations {
280 load: wgpu::LoadOp::Clear(color),
281 store: wgpu::StoreOp::Store,
282 },
283 };
284
285 match target {
286 RenderTarget::Surface => {
287 let surface_view = frame_context.surface().view();
288 all_attachments.push(Some(wgpu::RenderPassColorAttachment {
289 view: surface_view,
290 resolve_target: None,
291 ops: color_ops,
292 depth_slice: None,
293 }));
294 }
295 RenderTarget::Framebuffer(fb) => {
296 all_attachments.push(Some(wgpu::RenderPassColorAttachment {
297 view: fb.render_view(),
298 resolve_target: fb.resolve_target(),
299 ops: color_ops,
300 depth_slice: None,
301 }));
302 }
303 }
304 } else {
305 if let Some((ops, resolve_target)) = self.surface_attachment_ops {
307 let surface_view = frame_context.surface().view();
308 all_attachments.push(Some(wgpu::RenderPassColorAttachment {
309 view: surface_view,
310 resolve_target,
311 ops,
312 depth_slice: None,
313 }));
314 }
315 all_attachments.extend(self.color_attachments);
316 }
317
318 let depth_attachment = if let Some(attachment) = self.depth_stencil_attachment {
320 Some(attachment)
321 } else if let Some(RenderTarget::Framebuffer(fb)) = &self.target {
322 fb.depth_view().map(|view| {
323 let depth_ops = match self.depth_clear_op {
324 DepthClearOp::Load => wgpu::Operations {
325 load: wgpu::LoadOp::Load,
326 store: wgpu::StoreOp::Store,
327 },
328 DepthClearOp::Clear(depth) => wgpu::Operations {
329 load: wgpu::LoadOp::Clear(depth),
330 store: wgpu::StoreOp::Store,
331 },
332 };
333 wgpu::RenderPassDepthStencilAttachment {
334 view,
335 depth_ops: Some(depth_ops),
336 stencil_ops: None,
337 }
338 })
339 } else {
340 None
341 };
342
343 let descriptor = wgpu::RenderPassDescriptor {
344 label: self.label,
345 color_attachments: &all_attachments,
346 depth_stencil_attachment: depth_attachment,
347 occlusion_query_set: None,
348 timestamp_writes: None,
349 };
350
351 let render_pass = encoder.begin_render_pass(&descriptor).forget_lifetime();
352
353 frame_context.increment_passes();
354
355 RenderPass {
356 context: frame_context,
357 encoder: Some(encoder),
358 descriptor: Some(render_pass),
359 }
360 }
361}
362
363impl Default for RenderPassBuilder<'_> {
364 fn default() -> Self {
365 Self::new()
366 }
367}
368
369pub struct RenderPass<'a> {
371 pub context: &'a mut FrameContext,
372 pub(crate) encoder: Option<wgpu::CommandEncoder>,
373 pub(crate) descriptor: Option<wgpu::RenderPass<'static>>,
374}
375
376impl<'a> RenderPass<'a> {
377 pub fn descriptor(&mut self) -> &mut wgpu::RenderPass<'static> {
378 self.descriptor.as_mut().unwrap()
379 }
380
381 pub fn finish(self) {
382 drop(self);
383 }
384}
385
386impl Drop for RenderPass<'_> {
387 fn drop(&mut self) {
388 profile_function!();
389
390 drop(self.descriptor.take());
391
392 self.context.encoder = self.encoder.take();
394 }
395}
396
397pub trait RenderPassExt {
399 fn clear_pass<'a>(
401 &'a mut self,
402 target: RenderTarget<'a>,
403 clear_color: wgpu::Color,
404 ) -> RenderPass<'a>;
405
406 fn load_pass<'a>(&'a mut self, target: RenderTarget<'a>) -> RenderPass<'a>;
408}
409
410impl RenderPassExt for FrameContext {
411 fn clear_pass<'a>(
412 &'a mut self,
413 target: RenderTarget<'a>,
414 clear_color: wgpu::Color,
415 ) -> RenderPass<'a> {
416 RenderPassBuilder::new()
417 .target(target)
418 .clear_color(clear_color)
419 .build(self)
420 }
421
422 fn load_pass<'a>(&'a mut self, target: RenderTarget<'a>) -> RenderPass<'a> {
423 RenderPassBuilder::new().target(target).build(self)
424 }
425}