1use crate::prelude::*;
2use wde_logger::prelude::*;
3use wde_wgpu::passes::{
4 CommandBuffer, RenderPassBuilder, RenderPassColorAttachment, RenderPassDepth
5};
6
7use bevy::ecs::system::{ReadOnlySystemParam, SystemParamItem, SystemState};
8use bevy::prelude::*;
9use std::collections::HashMap;
10use std::ops::Range;
11use wde_wgpu::pipelines::BindGroup;
12
13use crate::{
14 assets::{Mesh, Texture},
15 core::SwapchainFrame
16};
17
18pub use wde_wgpu::command_buffer::{LoadOp, Operations, StoreOp};
19pub use wde_wgpu::render_pass::RenderPassInstance;
20
21pub struct RenderPassDescColorAttachment {
24 pub texture: Option<AssetId<Texture>>,
26 pub load: LoadOp<crate::prelude::Color>,
28 pub store: StoreOp,
30 pub resolve_target: Option<AssetId<Texture>>
32}
33impl Default for RenderPassDescColorAttachment {
34 fn default() -> Self {
35 Self {
36 texture: None,
37 load: LoadOp::Load,
38 store: StoreOp::Store,
39 resolve_target: None
40 }
41 }
42}
43pub struct RenderPassDescDepthAttachment {
45 pub texture: Option<AssetId<Texture>>,
47 pub load: LoadOp<f32>,
49 pub store: StoreOp,
51 pub stencil_load: LoadOp<u32>,
53 pub stencil_store: StoreOp
55}
56impl Default for RenderPassDescDepthAttachment {
57 fn default() -> Self {
58 Self {
59 texture: None,
60 load: LoadOp::Load,
61 store: StoreOp::Store,
62 stencil_load: LoadOp::Load,
63 stencil_store: StoreOp::Store
64 }
65 }
66}
67#[derive(Default)]
69pub struct RenderPassDesc {
70 pub attachments_depth: Option<RenderPassDescDepthAttachment>,
72 pub attachments_colors: Option<Vec<RenderPassDescColorAttachment>>
74}
75
76pub struct DrawCommandsBatch {
78 pub bind_group: Option<(u32, BindGroup)>, pub index_range: Range<u32>,
80 pub instance_range: Range<u32>
81}
82impl Default for DrawCommandsBatch {
83 fn default() -> Self {
84 Self {
85 bind_group: None,
86 index_range: 0..0,
87 instance_range: 0..1
88 }
89 }
90}
91pub enum SubPassCommand {
93 Pipeline(Option<CachedPipelineIndex>),
95 BindGroup(u32, Option<BindGroup>),
97 Mesh(Option<AssetId<Mesh>>),
99 StencilReference(u32),
101 DrawBatches(Vec<DrawCommandsBatch>),
103 Custom(for<'pass> fn(&'pass World, &mut RenderPassInstance<'pass>))
105}
106#[derive(Default)]
109pub struct RenderSubPassDesc(pub Vec<SubPassCommand>);
110
111pub type RenderPassId = i32;
113pub trait RenderPass {
117 type Params: ReadOnlySystemParam;
118
119 fn describe(params: &SystemParamItem<Self::Params>) -> RenderPassDesc;
121 fn id() -> RenderPassId;
123 fn label() -> &'static str;
125
126 fn custom_render(_world: &mut World, _command_buffer: &mut CommandBuffer) -> Option<bool> {
134 None
135 }
136}
137trait RenderPassNode: Send + Sync + 'static {
139 fn describe(&mut self, world: &mut World) -> RenderPassDesc;
140 fn label(&mut self) -> &'static str;
141 fn custom_render(
142 &mut self,
143 world: &mut World,
144 command_buffer: &mut CommandBuffer
145 ) -> Option<bool>;
146}
147struct RenderPassHolder<P: RenderPass> {
148 _phantom: std::marker::PhantomData<P>
149}
150impl<P> RenderPassNode for RenderPassHolder<P>
151where
152 P: RenderPass + Send + Sync + 'static
153{
154 fn describe(&mut self, world: &mut World) -> RenderPassDesc {
155 let mut state: SystemState<P::Params> = SystemState::new(world);
156 let params = state.get(world);
157 P::describe(¶ms)
158 }
159 fn label(&mut self) -> &'static str {
160 P::label()
161 }
162 fn custom_render(
163 &mut self,
164 world: &mut World,
165 command_buffer: &mut CommandBuffer
166 ) -> Option<bool> {
167 P::custom_render(world, command_buffer)
168 }
169}
170
171pub trait RenderSubPass {
176 type Params: ReadOnlySystemParam;
177
178 fn describe(params: &SystemParamItem<Self::Params>) -> RenderSubPassDesc;
180 fn label() -> &'static str;
182}
183trait RenderSubPassNode: Send + Sync + 'static {
185 fn describe(&mut self, world: &mut World) -> RenderSubPassDesc;
186 fn label(&mut self) -> &'static str;
187}
188struct RenderSubPassHolder<SP: RenderSubPass> {
189 _phantom: std::marker::PhantomData<SP>
190}
191impl<SP> RenderSubPassNode for RenderSubPassHolder<SP>
192where
193 SP: RenderSubPass + Send + Sync + 'static
194{
195 fn describe(&mut self, world: &mut World) -> RenderSubPassDesc {
196 let mut state: SystemState<SP::Params> = SystemState::new(world);
197 let params = state.get(world);
198 SP::describe(¶ms)
199 }
200 fn label(&mut self) -> &'static str {
201 SP::label()
202 }
203}
204
205#[derive(Resource, Default)]
210pub struct RenderGraph {
211 passes: Vec<Box<dyn RenderPassNode>>,
213 sub_passes: Vec<Box<dyn RenderSubPassNode>>,
214
215 sorted_ids: Vec<RenderPassId>, render_passes_by_id: HashMap<RenderPassId, usize>, sub_passes_by_renderpass: HashMap<usize, Vec<usize>> }
222impl RenderGraph {
223 pub fn add_pass<P: RenderPass + Send + Sync + 'static>(&mut self) -> &mut Self {
226 let id = P::id();
227
228 debug_assert!(
230 !self.render_passes_by_id.contains_key(&id),
231 "A render pass with ID {} already exists in the render graph.",
232 id
233 );
234
235 self.passes.push(Box::new(RenderPassHolder {
237 _phantom: std::marker::PhantomData::<P>
238 }));
239 self.render_passes_by_id.insert(id, self.passes.len() - 1);
240 self.sorted_ids.push(id);
241
242 self.sorted_ids.sort();
244 self
245 }
246
247 pub fn add_sub_pass<SP: RenderSubPass + Send + Sync + 'static, P: RenderPass>(
250 &mut self
251 ) -> &mut Self {
252 self.sub_passes.push(Box::new(RenderSubPassHolder {
253 _phantom: std::marker::PhantomData::<SP>
254 }));
255 let sub_pass_index = self.render_passes_by_id.get(&P::id()).copied();
256 debug_assert!(
257 sub_pass_index.is_some(),
258 "Cannot add sub-pass '{}' to render pass '{}': no render pass with ID {} found in the render graph.",
259 SP::label(),
260 P::label(),
261 P::id()
262 );
263 self.sub_passes_by_renderpass
264 .entry(sub_pass_index.unwrap())
265 .or_default()
266 .push(self.sub_passes.len() - 1);
267 self
268 }
269
270 pub(crate) fn render(world: &mut World) {
271 world.resource_scope(|world, mut graph: Mut<RenderGraph>| {
272 render(&mut graph, world);
273 });
274 }
275}
276
277fn render(graph: &mut RenderGraph, world: &mut World) {
278 let _span = debug_span!("render_graph").entered();
279
280 let mut command_buffer = {
282 let render_instance = world.get_resource::<RenderInstance>().unwrap();
283 CommandBuffer::new(&render_instance.0.read().unwrap(), "render-graph")
284 };
285
286 for pass_id in &graph.sorted_ids {
288 let pass_index = *graph.render_passes_by_id.get(pass_id).unwrap();
290 let pass = &mut graph.passes[pass_index];
291 let pass_label = pass.label();
292 let pass_desc = pass.describe(world);
293
294 if let Some(custom_render_result) = pass.custom_render(world, &mut command_buffer) {
296 if !custom_render_result {
298 debug!(
299 "Custom rendering for pass '{}' could not be completed. Skipping this pass for this frame.",
300 pass_label
301 );
302 }
303 continue;
304 }
305
306 let mut sub_passes = Vec::new();
308 if let Some(sub_pass_indices) = graph.sub_passes_by_renderpass.get(&pass_index) {
309 for sub_pass_index in sub_pass_indices {
310 let sub_pass = &mut graph.sub_passes[*sub_pass_index];
311 let sub_pass_label = sub_pass.label();
312 let sub_pass_desc = sub_pass.describe(world);
313 sub_passes.push((sub_pass_label, sub_pass_desc));
314 }
315 }
316
317 let _pass_span = debug_span!("render_pass", pass_id = *pass_id, pass_label).entered();
319 let mut render_pass =
320 match create_render_pass(world, &mut command_buffer, pass_label, &pass_desc) {
321 Ok(pass) => pass,
322 Err(e) => {
323 debug!(
324 "Failed to create render pass: '{}'. Skipping this frame.",
325 e
326 );
327 continue;
328 }
329 };
330
331 for (sub_pass_label, sub_pass_desc) in &sub_passes {
333 let _sub_pass_span =
334 debug_span!("render_sub_pass", sub_pass_label = *sub_pass_label).entered();
335 render_sub_pass(world, &mut render_pass, sub_pass_desc);
336 }
337 }
338
339 let render_instance = world.get_resource::<RenderInstance>().unwrap();
341 command_buffer.submit(&render_instance.0.read().unwrap());
342}
343
344fn create_render_pass<'p>(
345 world: &'p World,
346 command_buffer: &'p mut CommandBuffer,
347 label: &str,
348 pass_desc: &RenderPassDesc
349) -> Result<RenderPassInstance<'p>, String> {
350 let render_instance = world.get_resource::<RenderInstance>().unwrap();
352 let textures = world.get_resource::<RenderAssets<GpuTexture>>().unwrap();
353 let swapchain_frame = world.get_resource::<SwapchainFrame>().unwrap();
354 let (surface_width, surface_height) = {
355 let render_instance = render_instance.0.read().unwrap();
356 let config = render_instance.surface_config.as_ref().unwrap();
357 (config.width, config.height)
358 };
359
360 command_buffer.create_render_pass(label, |builder: &mut RenderPassBuilder| -> Result<(), String> {
362 if let Some(attachments_color) = &pass_desc.attachments_colors {
364 for color_attachment_desc in attachments_color.iter() {
366 if let Some(texture_id) = color_attachment_desc.texture
367 && let Some(texture) = textures.get(texture_id) {
368 if !(surface_width == texture.texture.size.0 && surface_height == texture.texture.size.1) {
369 return Err(format!("Color attachment texture has invalid size: expected ({}, {}), got ({}, {})", surface_width, surface_height, texture.texture.size.0, texture.texture.size.1));
370 }
371 let resolve_target = if let Some(resolve_target) = color_attachment_desc.resolve_target {
372 if let Some(resolve_target) = textures.get(resolve_target)
373 && surface_width == resolve_target.texture.size.0 && surface_height == resolve_target.texture.size.1 {
374 Some(resolve_target)
375 } else {
376 return Err("Invalid resolve target for color attachment".to_string());
377 }
378 } else { None };
379 builder.add_color_attachment(RenderPassColorAttachment {
380 texture: Some(&texture.texture.view),
381 load: match color_attachment_desc.load {
382 LoadOp::Load => LoadOp::Load,
383 LoadOp::Clear(color) => LoadOp::Clear(color.into())
384 },
385 store: color_attachment_desc.store,
386 resolve_target: resolve_target.map(|tex| &tex.texture.view)
387 });
388 } else {
389 return Err("Invalid texture for color attachment".to_string());
390 }
391 }
392 } else {
393 let swapchain_frame = match swapchain_frame.data.as_ref() {
395 Some(frame) => frame,
396 None => return Err("No swapchain frame available".to_string())
397 };
398 builder.add_color_attachment(RenderPassColorAttachment {
399 texture: Some(&swapchain_frame.view),
400 ..default()
401 });
402 }
403
404 if let Some(depth_attachment) = pass_desc.attachments_depth.as_ref() {
406 if let Some(depth_texture) = depth_attachment.texture
407 && let Some(depth_texture) = textures.get(depth_texture) {
408 let depth_only = pass_desc.attachments_colors.as_ref().is_some_and(|c| c.is_empty());
411 if !(depth_only || surface_width == depth_texture.texture.size.0 && surface_height == depth_texture.texture.size.1) {
412 return Err(format!("Depth attachment texture has invalid size: expected ({}, {}), got ({}, {}).", surface_width, surface_height, depth_texture.texture.size.0, depth_texture.texture.size.1));
413 }
414 builder.set_depth_texture(RenderPassDepth {
415 texture: Some(&depth_texture.texture.view),
416 load: depth_attachment.load,
417 store: depth_attachment.store,
418 stencil_load: depth_attachment.stencil_load,
419 stencil_store: depth_attachment.stencil_store
420 });
421 } else {
422 return Err("Invalid texture for depth attachment".to_string());
423 }
424 }
425 Ok(())
426 })
427}
428
429fn render_sub_pass<'p>(
430 world: &'p World,
431 render_pass: &mut RenderPassInstance<'p>,
432 sub_pass_desc: &'p RenderSubPassDesc
433) {
434 let pipeline_manager = world.get_resource::<PipelineManager>().unwrap();
436 let meshes = world.get_resource::<RenderAssets<GpuMesh>>().unwrap();
437
438 for stage_command in &sub_pass_desc.0 {
440 match stage_command {
441 SubPassCommand::Pipeline(pipeline) => {
443 if let Some(pipeline) = pipeline
444 && let CachedPipelineStatus::OkRender(pipeline) =
445 pipeline_manager.get_pipeline(*pipeline)
446 {
447 if let Err(e) = render_pass.set_pipeline(pipeline) {
448 error!("Failed to set pipeline: {:?}.", e);
449 return;
450 }
451 } else {
452 return;
453 }
454 }
455
456 SubPassCommand::BindGroup(index, bind_group) => {
458 if let Some(bind_group) = bind_group {
459 render_pass.set_bind_group(*index, bind_group);
460 } else {
461 return;
462 }
463 }
464
465 SubPassCommand::Mesh(mesh) => {
467 if let Some(mesh) = mesh
468 && let Some(mesh) = meshes.get(*mesh)
469 {
470 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.as_ref().unwrap());
471 render_pass.set_index_buffer(mesh.index_buffer.as_ref().unwrap());
472 } else {
473 return;
474 }
475 }
476
477 SubPassCommand::StencilReference(reference) => {
479 render_pass.set_stencil_reference(*reference);
480 }
481
482 SubPassCommand::DrawBatches(batches) => {
484 for batch in batches {
485 if let Some((index, bind_group)) = &batch.bind_group {
487 render_pass.set_bind_group(*index, bind_group);
488 }
489
490 match render_pass
492 .draw_indexed(batch.index_range.clone(), batch.instance_range.clone())
493 {
494 Ok(_) => {}
495 Err(e) => {
496 error!("Failed to draw: {:?}.", e);
497 }
498 }
499 }
500 }
501
502 SubPassCommand::Custom(custom_fn) => {
504 custom_fn(world, render_pass);
505 }
506 }
507 }
508}