1use crate::core::binding::GlobalBindGroupCache;
51use crate::core::gpu::Tracked;
52use crate::core::{ResourceManager, WgpuContext};
53use crate::graph::ExtractedScene;
54use crate::graph::RenderState;
55use crate::graph::core::GraphStorage;
56use crate::graph::core::graph::FrameConfig;
57use crate::graph::core::{
58 ExecuteContext, FrameArena, GraphBlackboard, HookStage, PrepareContext, RenderGraph,
59 TextureDesc, TransientPool, ViewResolver,
60};
61use crate::graph::frame::{PreparedSkyboxDraw, RenderLists};
62use crate::graph::passes::utils::add_msaa_resolve_pass;
63use crate::graph::passes::{
64 BloomFeature, BrdfLutFeature, CasFeature, FxaaFeature, IblComputeFeature, MsaaSyncFeature,
65 OpaqueFeature, PrepassFeature, ShadowFeature, SimpleForwardFeature, SkyboxFeature, SsaoFeature,
66 SsssFeature, TaaFeature, ToneMappingFeature, TransmissionCopyFeature, TransparentFeature,
67};
68use crate::pipeline::PipelineCache;
69use crate::pipeline::ShaderManager;
70use crate::renderer::FrameTime;
71use myth_assets::AssetServer;
72use myth_scene::Scene;
73use myth_scene::camera::RenderCamera;
74
75pub struct ComposerContext<'a> {
76 pub wgpu_ctx: &'a mut WgpuContext,
77 pub resource_manager: &'a mut ResourceManager,
78 pub pipeline_cache: &'a mut PipelineCache,
79 pub shader_manager: &'a mut ShaderManager,
80
81 pub extracted_scene: &'a ExtractedScene,
82 pub render_state: &'a RenderState,
83
84 pub global_bind_group_cache: &'a mut GlobalBindGroupCache,
85
86 pub render_lists: &'a mut RenderLists,
88
89 pub scene: &'a mut Scene,
91 pub camera: &'a RenderCamera,
92 pub assets: &'a AssetServer,
93 pub frame_time: FrameTime,
94
95 pub graph_storage: &'a mut GraphStorage,
96 pub transient_pool: &'a mut TransientPool,
97 pub frame_arena: &'a FrameArena,
99
100 pub fxaa_pass: &'a mut FxaaFeature,
103 pub taa_pass: &'a mut TaaFeature,
104 pub cas_pass: &'a mut CasFeature,
105 pub tone_map_pass: &'a mut ToneMappingFeature,
106 pub bloom_pass: &'a mut BloomFeature,
107 pub ssao_pass: &'a mut SsaoFeature,
108 pub prepass: &'a mut PrepassFeature,
110 pub opaque_pass: &'a mut OpaqueFeature,
111 pub skybox_pass: &'a mut SkyboxFeature,
112 pub transparent_pass: &'a mut TransparentFeature,
113 pub transmission_copy_pass: &'a mut TransmissionCopyFeature,
114 pub simple_forward_pass: &'a mut SimpleForwardFeature,
115 pub ssss_pass: &'a mut SsssFeature,
116 pub msaa_sync_pass: &'a mut MsaaSyncFeature,
117
118 pub shadow_pass: &'a mut ShadowFeature,
120 pub brdf_pass: &'a mut BrdfLutFeature,
121 pub ibl_pass: &'a mut IblComputeFeature,
122
123 #[cfg(feature = "debug_view")]
125 pub debug_view_pass: &'a mut crate::graph::passes::DebugViewFeature,
126}
127
128pub struct GraphBuilderContext<'a, 'g> {
129 pub graph: &'g mut RenderGraph<'a>,
130 pub pipeline_cache: &'a PipelineCache,
131 pub frame_config: &'g FrameConfig,
132}
133
134impl GraphBuilderContext<'_, '_> {
135 #[cfg(feature = "rdg_inspector")]
136 pub fn with_group<F, R>(&mut self, group_name: &'static str, f: F) -> R
137 where
138 F: FnOnce(&mut Self) -> R,
139 {
140 self.graph.push_group(group_name);
141 let result = f(self);
142 self.graph.pop_group();
143 result
144 }
145
146 #[cfg(not(feature = "rdg_inspector"))]
148 #[inline]
149 pub fn with_group<F, R>(&mut self, _group_name: &'static str, f: F) -> R
150 where
151 F: FnOnce(&mut Self) -> R,
152 {
153 f(self)
154 }
155}
156
157pub struct FrameComposer<'a> {
163 ctx: ComposerContext<'a>,
164 frame_config: FrameConfig,
165 #[allow(clippy::type_complexity)]
166 hooks: smallvec::SmallVec<
167 [(
168 HookStage,
169 Option<Box<dyn FnOnce(&mut RenderGraph<'a>, GraphBlackboard) -> GraphBlackboard + 'a>>,
170 ); 4],
171 >,
172}
173
174impl<'a> FrameComposer<'a> {
175 pub(crate) fn new(ctx: ComposerContext<'a>, size: (u32, u32)) -> Self {
177 let frame_config = FrameConfig {
178 width: size.0,
179 height: size.1,
180 depth_format: ctx.wgpu_ctx.depth_format,
181 msaa_samples: ctx.wgpu_ctx.msaa_samples,
182 surface_format: ctx.wgpu_ctx.surface_view_format,
183 hdr_format: crate::HDR_TEXTURE_FORMAT,
184 };
185
186 Self {
187 ctx,
188 frame_config,
189 hooks: smallvec::SmallVec::new(),
190 }
191 }
192
193 #[inline]
195 #[must_use]
196 pub fn device(&self) -> &wgpu::Device {
197 &self.ctx.wgpu_ctx.device
198 }
199
200 #[inline]
205 #[must_use]
206 pub fn resource_manager(&self) -> &ResourceManager {
207 self.ctx.resource_manager
208 }
209
210 #[inline]
235 #[must_use]
236 pub fn add_custom_pass<F>(mut self, stage: HookStage, hook: F) -> Self
237 where
238 F: FnOnce(&mut RenderGraph<'a>, GraphBlackboard) -> GraphBlackboard + 'a,
239 {
240 self.hooks.push((stage, Some(Box::new(hook))));
241 self
242 }
243
244 pub fn render(mut self) {
257 let output = match self.ctx.wgpu_ctx.surface.get_current_texture() {
259 wgpu::CurrentSurfaceTexture::Success(frame) => frame,
261 wgpu::CurrentSurfaceTexture::Suboptimal(frame) => {
262 self.ctx
263 .wgpu_ctx
264 .surface
265 .configure(&self.ctx.wgpu_ctx.device, &self.ctx.wgpu_ctx.config);
266 frame
267 }
268 _ => {
269 log::error!("Failed to acquire swap-chain surface");
270 return;
271 }
272 };
273
274 let view_format = self.ctx.wgpu_ctx.surface_view_format;
275 let surface_view = output.texture.create_view(&wgpu::TextureViewDescriptor {
276 format: Some(view_format),
277 ..Default::default()
278 });
279 let width = output.texture.width();
280 let height = output.texture.height();
281
282 let mut graph = RenderGraph::new(self.ctx.graph_storage, self.ctx.frame_arena);
285
286 let surface_desc = TextureDesc::new_2d(
287 width,
288 height,
289 view_format,
290 wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
291 );
292
293 let surface_view_tracked = Tracked::with_id(surface_view, 0);
294
295 let surface_out =
296 graph.import_external_resource("Surface_View", surface_desc, &surface_view_tracked);
297
298 let mut graph_ctx = GraphBuilderContext {
299 graph: &mut graph,
300 pipeline_cache: self.ctx.pipeline_cache,
301 frame_config: &self.frame_config,
302 };
303
304 let is_high_fidelity = self.ctx.wgpu_ctx.render_path.supports_post_processing();
309 let msaa_samples = self.ctx.wgpu_ctx.msaa_samples;
310 let is_msaa = msaa_samples > 1;
311
312 let ssao_enabled = self.ctx.scene.ssao.enabled && is_high_fidelity;
315 let needs_feature_id = is_high_fidelity
316 && (self.ctx.scene.screen_space.enable_sss || self.ctx.scene.screen_space.enable_ssr);
317 let needs_normal = ssao_enabled || needs_feature_id;
318 let needs_skybox = self.ctx.scene.background.needs_skybox_pass();
319 let ssss_enabled = self.ctx.scene.screen_space.enable_sss;
320 let has_transmission = self.ctx.render_lists.use_transmission;
321 let bloom_enabled = self.ctx.scene.bloom.enabled && is_high_fidelity;
322 graph_ctx.with_group("Compute", |c| {
327 if self.ctx.resource_manager.needs_brdf_compute {
328 self.ctx.brdf_pass.add_to_graph(c);
329 }
330
331 if let Some(source) = self.ctx.resource_manager.pending_ibl_source.take() {
332 self.ctx.ibl_pass.add_to_graph(c, source);
333 }
334 });
335
336 let shadow_tex = if self.ctx.extracted_scene.has_shadow_casters() {
337 graph_ctx.with_group("Shadow", |c| self.ctx.shadow_pass.add_to_graph(c))
338 } else {
339 None
340 };
341
342 let mut bb_scene_color = None;
351 let mut bb_scene_depth = None;
352
353 #[cfg(feature = "debug_view")]
355 let mut dbg_normals: Option<crate::graph::core::TextureNodeId> = None;
356 #[cfg(feature = "debug_view")]
357 let mut dbg_velocity: Option<crate::graph::core::TextureNodeId> = None;
358 #[cfg(feature = "debug_view")]
359 let mut dbg_ssao: Option<crate::graph::core::TextureNodeId> = None;
360 #[cfg(feature = "debug_view")]
361 let mut dbg_bloom_color: Option<crate::graph::core::TextureNodeId> = None;
362
363 let mut current_surface = surface_out;
364
365 if is_high_fidelity {
366 let taa_enabled = self.ctx.camera.aa_mode.is_taa();
373
374 let cas_enabled = if let Some(s) = self.ctx.camera.aa_mode.taa_settings() {
375 s.sharpen_intensity > 0.0
376 } else {
377 false
378 };
379
380 let fxaa_enabled = self.ctx.camera.aa_mode.is_fxaa();
381
382 let (mut active_color, mut scene_depth) = graph_ctx.with_group("Scene", |c| {
383 let prepass_out =
385 self.ctx
386 .prepass
387 .add_to_graph(c, needs_normal, needs_feature_id, taa_enabled);
388
389 let scene_depth = prepass_out.scene_depth;
390
391 let ssao_output = if ssao_enabled {
393 Some(
394 self.ctx.ssao_pass.add_to_graph(
395 c,
396 scene_depth,
397 prepass_out
398 .scene_normals
399 .expect("SSAO requires scene normals from Prepass"),
400 ),
401 )
402 } else {
403 None
404 };
405
406 let opaque_out = self.ctx.opaque_pass.add_to_graph(
408 c,
409 scene_depth,
410 self.ctx.extracted_scene.background.clear_color(),
411 ssss_enabled,
412 ssao_output,
413 shadow_tex,
414 );
415
416 let mut active_color = opaque_out.active_color;
417
418 if ssss_enabled {
420 if is_msaa {
421 let hdr_desc = TextureDesc::new_2d(
422 c.frame_config.width,
423 c.frame_config.height,
424 crate::HDR_TEXTURE_FORMAT,
425 wgpu::TextureUsages::RENDER_ATTACHMENT
426 | wgpu::TextureUsages::TEXTURE_BINDING
427 | wgpu::TextureUsages::COPY_SRC,
428 );
429 active_color = add_msaa_resolve_pass(c, active_color, hdr_desc);
431 }
432
433 active_color = self.ctx.ssss_pass.add_to_graph(
434 c,
435 active_color,
436 prepass_out.scene_depth,
437 prepass_out.scene_normals.unwrap(),
438 prepass_out.feature_id.unwrap(),
439 opaque_out.specular_mrt.unwrap(),
440 );
441
442 if is_msaa {
443 active_color = self.ctx.msaa_sync_pass.add_to_graph(c, active_color);
446 }
447 }
448
449 if needs_skybox {
451 active_color =
452 self.ctx
453 .skybox_pass
454 .add_to_graph(c, active_color, opaque_out.active_depth);
455 }
456
457 if taa_enabled && let Some(velocity) = prepass_out.velocity_buffer {
462 c.with_group("TAA_System", |c| {
463 active_color =
464 self.ctx
465 .taa_pass
466 .add_to_graph(c, active_color, velocity, scene_depth);
467
468 if cas_enabled {
471 active_color = self.ctx.cas_pass.add_to_graph(c, active_color);
472 }
473 });
474 }
475
476 let transmission_tex = if has_transmission {
478 Some(
479 self.ctx
480 .transmission_copy_pass
481 .add_to_graph(c, active_color),
482 )
483 } else {
484 None
485 };
486
487 let active_color = self.ctx.transparent_pass.add_to_graph(
489 c,
490 active_color,
491 opaque_out.active_depth,
492 transmission_tex,
493 ssao_output,
494 shadow_tex,
495 );
496
497 #[cfg(feature = "debug_view")]
499 {
500 dbg_normals = prepass_out.scene_normals;
501 dbg_velocity = prepass_out.velocity_buffer;
502 dbg_ssao = ssao_output;
503 }
504
505 (active_color, scene_depth)
506 });
507
508 {
510 let mut blackboard = GraphBlackboard {
511 scene_color: Some(active_color),
512 scene_depth: Some(scene_depth),
513 surface_out,
514 };
515 for (stage, hook_opt) in &mut self.hooks {
516 if *stage == HookStage::BeforePostProcess
517 && let Some(hook) = hook_opt.take()
518 {
519 blackboard = hook(graph_ctx.graph, blackboard);
520 }
521 }
522
523 active_color = blackboard.scene_color.unwrap_or(active_color);
524 scene_depth = blackboard.scene_depth.unwrap_or(scene_depth);
525 }
526
527 current_surface = graph_ctx.with_group("PostProcess", |ctx| {
529 if bloom_enabled {
531 active_color = self.ctx.bloom_pass.add_to_graph(
532 ctx,
533 active_color,
534 self.ctx.scene.bloom.karis_average,
535 self.ctx.scene.bloom.max_mip_levels(),
536 );
537 }
538
539 #[cfg(feature = "debug_view")]
540 {
541 dbg_bloom_color = if bloom_enabled {
542 Some(active_color)
543 } else {
544 None
545 };
546 }
547
548 let mut surface = if fxaa_enabled {
550 let ldr = ctx
552 .graph
553 .register_resource("LDR_Intermediate", surface_desc, false);
554 self.ctx.tone_map_pass.add_to_graph(ctx, active_color, ldr)
555 } else {
556 self.ctx
557 .tone_map_pass
558 .add_to_graph(ctx, active_color, current_surface)
559 };
560
561 if fxaa_enabled {
563 let ldr_intermediate = surface;
564 surface =
565 self.ctx
566 .fxaa_pass
567 .add_to_graph(ctx, ldr_intermediate, current_surface);
568 }
569
570 bb_scene_color = Some(active_color);
571 bb_scene_depth = Some(scene_depth);
572
573 surface
574 });
575
576 #[cfg(feature = "debug_view")]
582 {
583 use crate::graph::render_state::DebugViewTarget;
584 let target = self.ctx.render_state.debug_view_target;
585 let source: Option<crate::graph::core::TextureNodeId> = match target {
586 DebugViewTarget::None => None,
587 DebugViewTarget::SceneDepth => None,
591 DebugViewTarget::SceneNormal => dbg_normals,
592 DebugViewTarget::Velocity => dbg_velocity,
593 DebugViewTarget::SsaoRaw => dbg_ssao,
594 DebugViewTarget::BloomMip0 => dbg_bloom_color,
595 };
596
597 if let Some(src) = source {
598 current_surface =
599 self.ctx
600 .debug_view_pass
601 .add_to_graph(&mut graph_ctx, src, current_surface);
602 }
603 }
604 } else {
605 let prepared_skybox = if needs_skybox {
608 let skybox_pipeline = self.ctx.skybox_pass.current_pipeline;
609 let skybox_bind_group = &self.ctx.skybox_pass.current_bind_group;
610
611 if let (Some(pipeline_id), Some(bg)) = (skybox_pipeline, skybox_bind_group) {
612 Some(PreparedSkyboxDraw {
613 pipeline: self.ctx.pipeline_cache.get_render_pipeline(pipeline_id),
614 bind_group: bg,
615 })
616 } else {
617 None
618 }
619 } else {
620 None
621 };
622
623 graph_ctx.with_group("BasicForward", |c| {
624 self.ctx.simple_forward_pass.add_to_graph(
625 c,
626 surface_out,
627 self.ctx.extracted_scene.background.clear_color(),
628 prepared_skybox,
629 shadow_tex,
630 );
631 });
632 }
633
634 {
636 let mut blackboard = GraphBlackboard {
637 scene_color: bb_scene_color,
638 scene_depth: bb_scene_depth,
639 surface_out: current_surface,
640 };
641 for (stage, hook_opt) in &mut self.hooks {
642 if *stage == HookStage::AfterPostProcess
643 && let Some(hook) = hook_opt.take()
644 {
645 blackboard = hook(&mut graph, blackboard);
646 }
647 }
648 }
649
650 graph.compile(self.ctx.transient_pool, &self.ctx.wgpu_ctx.device);
653
654 let mut prepare_ctx = PrepareContext {
660 views: ViewResolver {
661 resources: &graph.storage.resources,
662 pool: self.ctx.transient_pool,
663 },
664 device: &self.ctx.wgpu_ctx.device,
665 queue: &self.ctx.wgpu_ctx.queue,
666 sampler_registry: &self.ctx.resource_manager.sampler_registry,
667 global_bind_group_cache: self.ctx.global_bind_group_cache,
668 system_textures: &self.ctx.resource_manager.system_textures,
669 };
670
671 for &pass_idx in &graph.storage.execution_queue {
672 let pass = graph.storage.passes[pass_idx].get_pass_mut();
673 pass.prepare(&mut prepare_ctx);
674 }
675
676 let prepass_config = if is_high_fidelity {
682 Some(crate::graph::bake::PrepassBakeConfig {
683 local_cache: self.ctx.prepass.local_cache(),
684 needs_normal: self.ctx.prepass.needs_normal(),
685 needs_feature_id: self.ctx.prepass.needs_feature_id(),
686 needs_velocity: self.ctx.prepass.needs_velocity(),
687 })
688 } else {
689 None
690 };
691
692 let baked_lists = crate::graph::bake::bake_render_lists(
693 self.ctx.render_lists,
694 self.ctx.resource_manager,
695 self.ctx.pipeline_cache,
696 &prepass_config,
697 );
698
699 let mut execute_ctx = ExecuteContext {
702 resources: &graph.storage.resources,
703 pool: self.ctx.transient_pool,
704 device: &self.ctx.wgpu_ctx.device,
705 queue: &self.ctx.wgpu_ctx.queue,
706 pipeline_cache: self.ctx.pipeline_cache,
707 global_bind_group_cache: self.ctx.global_bind_group_cache,
708 mipmap_generator: &self.ctx.resource_manager.mipmap_generator,
709 baked_lists: &baked_lists,
710 wgpu_ctx: &*self.ctx.wgpu_ctx,
711 current_timeline_index: 0,
712 };
713
714 let mut encoder =
715 self.ctx
716 .wgpu_ctx
717 .device
718 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
719 label: Some("Unified Encoder"),
720 });
721
722 for (timeline_index, &pass_idx) in graph.storage.execution_queue.iter().enumerate() {
723 execute_ctx.current_timeline_index = timeline_index;
724 #[cfg(debug_assertions)]
725 encoder.push_debug_group(graph.storage.passes[pass_idx].name);
726 graph.storage.passes[pass_idx]
727 .get_pass_mut()
728 .execute(&execute_ctx, &mut encoder);
729 #[cfg(debug_assertions)]
730 encoder.pop_debug_group();
731 }
732
733 self.ctx.wgpu_ctx.queue.submit(Some(encoder.finish()));
736 output.present();
737 }
738}