1use crate::rendergraph::executor::PassContext;
9use crate::rendergraph::graph::{
10 PassCondition, PassType, QueueAffinity, RenderGraph, RenderGraphBuilder, RenderPass,
11 ResolutionScale,
12};
13use crate::rendergraph::resources::{
14 ResourceDescriptor, ResourceHandle, SizePolicy, TextureFormat, UsageFlags,
15};
16
17use std::fmt;
18
19pub trait BuiltinPass {
26 fn name(&self) -> &str;
28
29 fn pass_type(&self) -> PassType {
31 PassType::Graphics
32 }
33
34 fn queue_affinity(&self) -> QueueAffinity {
36 QueueAffinity::Graphics
37 }
38
39 fn input_names(&self) -> Vec<&str>;
41
42 fn output_names(&self) -> Vec<&str>;
44
45 fn execute(&self, ctx: &PassContext);
47
48 fn condition(&self) -> PassCondition {
50 PassCondition::Always
51 }
52
53 fn resolution_scale(&self) -> ResolutionScale {
55 ResolutionScale::full()
56 }
57}
58
59#[derive(Debug, Clone)]
65pub struct DrawCall {
66 pub vertex_count: u32,
67 pub instance_count: u32,
68 pub pass_name: String,
69 pub draw_type: DrawType,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum DrawType {
74 Triangles,
75 FullscreenQuad,
76 Instanced,
77 Indirect,
78 Dispatch,
79}
80
81#[derive(Debug, Clone, Copy)]
83pub struct Viewport {
84 pub x: f32,
85 pub y: f32,
86 pub width: f32,
87 pub height: f32,
88 pub min_depth: f32,
89 pub max_depth: f32,
90}
91
92impl Viewport {
93 pub fn from_context(ctx: &PassContext) -> Self {
94 Self {
95 x: 0.0,
96 y: 0.0,
97 width: ctx.render_width as f32,
98 height: ctx.render_height as f32,
99 min_depth: 0.0,
100 max_depth: 1.0,
101 }
102 }
103}
104
105#[derive(Debug, Clone, Copy)]
107pub struct ClearValues {
108 pub color: [f32; 4],
109 pub depth: f32,
110 pub stencil: u8,
111}
112
113impl Default for ClearValues {
114 fn default() -> Self {
115 Self {
116 color: [0.0, 0.0, 0.0, 1.0],
117 depth: 1.0,
118 stencil: 0,
119 }
120 }
121}
122
123pub struct DepthPrePass {
130 pub clear_depth: f32,
131 pub depth_bias: f32,
132 pub depth_bias_slope: f32,
133}
134
135impl DepthPrePass {
136 pub fn new() -> Self {
137 Self {
138 clear_depth: 1.0,
139 depth_bias: 0.0,
140 depth_bias_slope: 0.0,
141 }
142 }
143
144 pub fn register(
146 &self,
147 builder: &mut RenderGraphBuilder,
148 depth_handle: ResourceHandle,
149 ) {
150 builder
151 .graphics_pass(self.name())
152 .writes(depth_handle, "depth")
153 .tag("geometry")
154 .finish();
155 }
156}
157
158impl Default for DepthPrePass {
159 fn default() -> Self {
160 Self::new()
161 }
162}
163
164impl BuiltinPass for DepthPrePass {
165 fn name(&self) -> &str {
166 "depth_prepass"
167 }
168
169 fn input_names(&self) -> Vec<&str> {
170 vec![]
171 }
172
173 fn output_names(&self) -> Vec<&str> {
174 vec!["depth"]
175 }
176
177 fn execute(&self, ctx: &PassContext) {
178 let _viewport = Viewport::from_context(ctx);
179 let _clear = ClearValues {
180 depth: self.clear_depth,
181 ..Default::default()
182 };
183 let _draw = DrawCall {
186 vertex_count: 0, instance_count: 1,
188 pass_name: self.name().to_string(),
189 draw_type: DrawType::Triangles,
190 };
191 }
192}
193
194pub struct GBufferPass {
200 pub write_velocity: bool,
201 pub write_emissive: bool,
202}
203
204impl GBufferPass {
205 pub fn new() -> Self {
206 Self {
207 write_velocity: true,
208 write_emissive: true,
209 }
210 }
211
212 pub fn register(
213 &self,
214 builder: &mut RenderGraphBuilder,
215 depth: ResourceHandle,
216 albedo: ResourceHandle,
217 normal: ResourceHandle,
218 roughness_metallic: ResourceHandle,
219 ) {
220 let mut pass_builder = builder
221 .graphics_pass(self.name())
222 .reads(depth, "depth")
223 .writes(albedo, "gbuffer_albedo")
224 .writes(normal, "gbuffer_normal")
225 .writes(roughness_metallic, "gbuffer_rm")
226 .tag("geometry");
227 pass_builder = pass_builder.depends_on("depth_prepass");
228 pass_builder.finish();
229 }
230}
231
232impl Default for GBufferPass {
233 fn default() -> Self {
234 Self::new()
235 }
236}
237
238impl BuiltinPass for GBufferPass {
239 fn name(&self) -> &str {
240 "gbuffer"
241 }
242
243 fn input_names(&self) -> Vec<&str> {
244 vec!["depth"]
245 }
246
247 fn output_names(&self) -> Vec<&str> {
248 let mut out = vec!["gbuffer_albedo", "gbuffer_normal", "gbuffer_rm"];
249 if self.write_velocity {
250 out.push("gbuffer_velocity");
251 }
252 if self.write_emissive {
253 out.push("gbuffer_emissive");
254 }
255 out
256 }
257
258 fn execute(&self, ctx: &PassContext) {
259 let _viewport = Viewport::from_context(ctx);
260 let _clear = ClearValues {
261 color: [0.0, 0.0, 0.0, 0.0],
262 ..Default::default()
263 };
264 let _draw = DrawCall {
266 vertex_count: 0,
267 instance_count: 1,
268 pass_name: self.name().to_string(),
269 draw_type: DrawType::Triangles,
270 };
271 }
272}
273
274pub struct ShadowPass {
281 pub light_index: usize,
282 pub cascade_count: u32,
283 pub shadow_map_resolution: u32,
284 pub depth_bias: f32,
285 pub normal_bias: f32,
286 pub pcf_radius: f32,
287}
288
289impl ShadowPass {
290 pub fn new(light_index: usize) -> Self {
291 Self {
292 light_index,
293 cascade_count: 4,
294 shadow_map_resolution: 2048,
295 depth_bias: 0.005,
296 normal_bias: 0.02,
297 pcf_radius: 1.5,
298 }
299 }
300
301 pub fn pass_name(&self) -> String {
302 format!("shadow_light_{}", self.light_index)
303 }
304
305 pub fn register(
306 &self,
307 builder: &mut RenderGraphBuilder,
308 shadow_map: ResourceHandle,
309 ) {
310 builder
311 .graphics_pass(&self.pass_name())
312 .writes(shadow_map, &format!("shadow_map_{}", self.light_index))
313 .tag("shadows")
314 .finish();
315 }
316}
317
318impl BuiltinPass for ShadowPass {
319 fn name(&self) -> &str {
320 "shadow_pass"
322 }
323
324 fn input_names(&self) -> Vec<&str> {
325 vec![]
326 }
327
328 fn output_names(&self) -> Vec<&str> {
329 vec!["shadow_map"]
330 }
331
332 fn execute(&self, ctx: &PassContext) {
333 for cascade in 0..self.cascade_count {
335 let _viewport = Viewport {
336 x: 0.0,
337 y: 0.0,
338 width: self.shadow_map_resolution as f32,
339 height: self.shadow_map_resolution as f32,
340 min_depth: 0.0,
341 max_depth: 1.0,
342 };
343 let _draw = DrawCall {
344 vertex_count: 0,
345 instance_count: 1,
346 pass_name: format!("{}_cascade_{}", self.pass_name(), cascade),
347 draw_type: DrawType::Triangles,
348 };
349 }
350 }
351}
352
353pub struct SSAOPass {
359 pub kernel_size: u32,
360 pub radius: f32,
361 pub bias: f32,
362 pub intensity: f32,
363 pub blur_passes: u32,
364 pub noise_texture_size: u32,
365}
366
367impl SSAOPass {
368 pub fn new() -> Self {
369 Self {
370 kernel_size: 64,
371 radius: 0.5,
372 bias: 0.025,
373 intensity: 1.5,
374 blur_passes: 2,
375 noise_texture_size: 4,
376 }
377 }
378
379 pub fn register(
380 &self,
381 builder: &mut RenderGraphBuilder,
382 depth: ResourceHandle,
383 normal: ResourceHandle,
384 ssao_out: ResourceHandle,
385 ) {
386 builder
387 .compute_pass(self.name())
388 .reads(depth, "depth")
389 .reads(normal, "gbuffer_normal")
390 .writes(ssao_out, "ssao")
391 .resolution(ResolutionScale::half())
392 .queue(QueueAffinity::Compute)
393 .condition(PassCondition::FeatureEnabled("ssao".to_string()))
394 .tag("lighting")
395 .finish();
396 }
397}
398
399impl Default for SSAOPass {
400 fn default() -> Self {
401 Self::new()
402 }
403}
404
405impl BuiltinPass for SSAOPass {
406 fn name(&self) -> &str {
407 "ssao"
408 }
409
410 fn pass_type(&self) -> PassType {
411 PassType::Compute
412 }
413
414 fn queue_affinity(&self) -> QueueAffinity {
415 QueueAffinity::Compute
416 }
417
418 fn input_names(&self) -> Vec<&str> {
419 vec!["depth", "gbuffer_normal"]
420 }
421
422 fn output_names(&self) -> Vec<&str> {
423 vec!["ssao"]
424 }
425
426 fn resolution_scale(&self) -> ResolutionScale {
427 ResolutionScale::half()
428 }
429
430 fn condition(&self) -> PassCondition {
431 PassCondition::FeatureEnabled("ssao".to_string())
432 }
433
434 fn execute(&self, ctx: &PassContext) {
435 let dispatch_x = (ctx.render_width + 7) / 8;
437 let dispatch_y = (ctx.render_height + 7) / 8;
438 let _dispatch = DrawCall {
439 vertex_count: dispatch_x * dispatch_y,
440 instance_count: 1,
441 pass_name: "ssao_generate".to_string(),
442 draw_type: DrawType::Dispatch,
443 };
444
445 for i in 0..self.blur_passes {
447 let _blur = DrawCall {
448 vertex_count: dispatch_x * dispatch_y,
449 instance_count: 1,
450 pass_name: format!("ssao_blur_{}", i),
451 draw_type: DrawType::Dispatch,
452 };
453 }
454 }
455}
456
457pub struct LightingPass {
463 pub max_point_lights: u32,
464 pub max_spot_lights: u32,
465 pub enable_ibl: bool,
466 pub enable_volumetric: bool,
467}
468
469impl LightingPass {
470 pub fn new() -> Self {
471 Self {
472 max_point_lights: 256,
473 max_spot_lights: 64,
474 enable_ibl: true,
475 enable_volumetric: false,
476 }
477 }
478
479 pub fn register(
480 &self,
481 builder: &mut RenderGraphBuilder,
482 albedo: ResourceHandle,
483 normal: ResourceHandle,
484 rm: ResourceHandle,
485 depth: ResourceHandle,
486 ssao: ResourceHandle,
487 hdr_color: ResourceHandle,
488 ) {
489 builder
490 .graphics_pass(self.name())
491 .reads(albedo, "gbuffer_albedo")
492 .reads(normal, "gbuffer_normal")
493 .reads(rm, "gbuffer_rm")
494 .reads(depth, "depth")
495 .reads(ssao, "ssao")
496 .writes(hdr_color, "hdr_color")
497 .tag("lighting")
498 .finish();
499 }
500}
501
502impl Default for LightingPass {
503 fn default() -> Self {
504 Self::new()
505 }
506}
507
508impl BuiltinPass for LightingPass {
509 fn name(&self) -> &str {
510 "lighting"
511 }
512
513 fn input_names(&self) -> Vec<&str> {
514 vec![
515 "gbuffer_albedo",
516 "gbuffer_normal",
517 "gbuffer_rm",
518 "depth",
519 "ssao",
520 ]
521 }
522
523 fn output_names(&self) -> Vec<&str> {
524 vec!["hdr_color"]
525 }
526
527 fn execute(&self, ctx: &PassContext) {
528 let _viewport = Viewport::from_context(ctx);
529 let _draw = DrawCall {
531 vertex_count: 3, instance_count: 1,
533 pass_name: self.name().to_string(),
534 draw_type: DrawType::FullscreenQuad,
535 };
536 if self.enable_ibl {
538 let _ibl = DrawCall {
539 vertex_count: 3,
540 instance_count: 1,
541 pass_name: "lighting_ibl".to_string(),
542 draw_type: DrawType::FullscreenQuad,
543 };
544 }
545 }
546}
547
548pub struct SkyboxPass {
554 pub exposure: f32,
555 pub rotation: f32,
556 pub blur_level: f32,
557}
558
559impl SkyboxPass {
560 pub fn new() -> Self {
561 Self {
562 exposure: 1.0,
563 rotation: 0.0,
564 blur_level: 0.0,
565 }
566 }
567
568 pub fn register(
569 &self,
570 builder: &mut RenderGraphBuilder,
571 depth: ResourceHandle,
572 hdr_color: ResourceHandle,
573 ) {
574 builder
575 .graphics_pass(self.name())
576 .reads(depth, "depth")
577 .writes(hdr_color, "hdr_color")
578 .depends_on("lighting")
579 .tag("lighting")
580 .finish();
581 }
582}
583
584impl Default for SkyboxPass {
585 fn default() -> Self {
586 Self::new()
587 }
588}
589
590impl BuiltinPass for SkyboxPass {
591 fn name(&self) -> &str {
592 "skybox"
593 }
594
595 fn input_names(&self) -> Vec<&str> {
596 vec!["depth"]
597 }
598
599 fn output_names(&self) -> Vec<&str> {
600 vec!["hdr_color"]
601 }
602
603 fn execute(&self, ctx: &PassContext) {
604 let _viewport = Viewport::from_context(ctx);
605 let _draw = DrawCall {
607 vertex_count: 36, instance_count: 1,
609 pass_name: self.name().to_string(),
610 draw_type: DrawType::Triangles,
611 };
612 }
613}
614
615pub struct TransparencyPass {
621 pub weight_function: WeightFunction,
622 pub max_fragments_per_pixel: u32,
623}
624
625#[derive(Debug, Clone, Copy, PartialEq, Eq)]
626pub enum WeightFunction {
627 DepthWeight,
629 Constant,
631 ColorWeight,
633}
634
635impl TransparencyPass {
636 pub fn new() -> Self {
637 Self {
638 weight_function: WeightFunction::DepthWeight,
639 max_fragments_per_pixel: 8,
640 }
641 }
642
643 pub fn register(
644 &self,
645 builder: &mut RenderGraphBuilder,
646 depth: ResourceHandle,
647 hdr_color: ResourceHandle,
648 accum: ResourceHandle,
649 reveal: ResourceHandle,
650 ) {
651 builder
653 .graphics_pass("transparency_accum")
654 .reads(depth, "depth")
655 .writes(accum, "oit_accum")
656 .writes(reveal, "oit_reveal")
657 .depends_on("skybox")
658 .tag("transparency")
659 .finish();
660
661 builder
663 .graphics_pass("transparency_composite")
664 .reads(accum, "oit_accum")
665 .reads(reveal, "oit_reveal")
666 .reads(hdr_color, "hdr_color")
667 .writes(hdr_color, "hdr_color")
668 .depends_on("transparency_accum")
669 .tag("transparency")
670 .finish();
671 }
672}
673
674impl Default for TransparencyPass {
675 fn default() -> Self {
676 Self::new()
677 }
678}
679
680impl BuiltinPass for TransparencyPass {
681 fn name(&self) -> &str {
682 "transparency"
683 }
684
685 fn input_names(&self) -> Vec<&str> {
686 vec!["depth", "hdr_color", "oit_accum", "oit_reveal"]
687 }
688
689 fn output_names(&self) -> Vec<&str> {
690 vec!["hdr_color"]
691 }
692
693 fn execute(&self, ctx: &PassContext) {
694 let _viewport = Viewport::from_context(ctx);
695
696 let _accum_draw = DrawCall {
698 vertex_count: 0,
699 instance_count: 1,
700 pass_name: "oit_accumulate".to_string(),
701 draw_type: DrawType::Triangles,
702 };
703
704 let _composite_draw = DrawCall {
706 vertex_count: 3,
707 instance_count: 1,
708 pass_name: "oit_composite".to_string(),
709 draw_type: DrawType::FullscreenQuad,
710 };
711 }
712}
713
714pub struct BloomPass {
720 pub threshold: f32,
721 pub intensity: f32,
722 pub mip_count: u32,
723 pub radius: f32,
724 pub soft_threshold: f32,
725}
726
727impl BloomPass {
728 pub fn new() -> Self {
729 Self {
730 threshold: 1.0,
731 intensity: 0.8,
732 mip_count: 5,
733 radius: 1.0,
734 soft_threshold: 0.5,
735 }
736 }
737
738 pub fn register(
740 &self,
741 builder: &mut RenderGraphBuilder,
742 hdr_color: ResourceHandle,
743 bloom_out: ResourceHandle,
744 ) {
745 builder
747 .graphics_pass("bloom_threshold")
748 .reads(hdr_color, "hdr_color")
749 .writes(bloom_out, "bloom_chain")
750 .resolution(ResolutionScale::half())
751 .depends_on("transparency_composite")
752 .tag("bloom")
753 .finish();
754
755 for i in 1..self.mip_count {
757 let scale = 1.0 / (2u32.pow(i + 1)) as f32;
758 builder
759 .graphics_pass(&format!("bloom_down_{}", i))
760 .reads(bloom_out, "bloom_chain")
761 .writes(bloom_out, "bloom_chain")
762 .resolution(ResolutionScale::custom(scale, scale))
763 .depends_on(&if i == 1 {
764 "bloom_threshold".to_string()
765 } else {
766 format!("bloom_down_{}", i - 1)
767 })
768 .tag("bloom")
769 .finish();
770 }
771
772 for i in (0..self.mip_count - 1).rev() {
774 let scale = 1.0 / (2u32.pow(i + 1)) as f32;
775 let dep = if i == self.mip_count - 2 {
776 format!("bloom_down_{}", self.mip_count - 1)
777 } else {
778 format!("bloom_up_{}", i + 1)
779 };
780 builder
781 .graphics_pass(&format!("bloom_up_{}", i))
782 .reads(bloom_out, "bloom_chain")
783 .writes(bloom_out, "bloom_chain")
784 .resolution(ResolutionScale::custom(scale, scale))
785 .depends_on(&dep)
786 .tag("bloom")
787 .finish();
788 }
789 }
790}
791
792impl Default for BloomPass {
793 fn default() -> Self {
794 Self::new()
795 }
796}
797
798impl BuiltinPass for BloomPass {
799 fn name(&self) -> &str {
800 "bloom"
801 }
802
803 fn pass_type(&self) -> PassType {
804 PassType::Graphics
805 }
806
807 fn input_names(&self) -> Vec<&str> {
808 vec!["hdr_color"]
809 }
810
811 fn output_names(&self) -> Vec<&str> {
812 vec!["bloom_chain"]
813 }
814
815 fn resolution_scale(&self) -> ResolutionScale {
816 ResolutionScale::half()
817 }
818
819 fn execute(&self, ctx: &PassContext) {
820 let _viewport = Viewport::from_context(ctx);
821
822 let _threshold_draw = DrawCall {
824 vertex_count: 3,
825 instance_count: 1,
826 pass_name: "bloom_threshold".to_string(),
827 draw_type: DrawType::FullscreenQuad,
828 };
829
830 for i in 0..self.mip_count {
832 let _down = DrawCall {
833 vertex_count: 3,
834 instance_count: 1,
835 pass_name: format!("bloom_down_{}", i),
836 draw_type: DrawType::FullscreenQuad,
837 };
838 }
839
840 for i in (0..self.mip_count).rev() {
842 let _up = DrawCall {
843 vertex_count: 3,
844 instance_count: 1,
845 pass_name: format!("bloom_up_{}", i),
846 draw_type: DrawType::FullscreenQuad,
847 };
848 }
849 }
850}
851
852pub struct ToneMappingPass {
858 pub operator: ToneMapOperator,
859 pub exposure: f32,
860 pub gamma: f32,
861 pub auto_exposure: bool,
862 pub adaptation_speed: f32,
863 pub min_luminance: f32,
864 pub max_luminance: f32,
865}
866
867#[derive(Debug, Clone, Copy, PartialEq, Eq)]
868pub enum ToneMapOperator {
869 Reinhard,
870 ReinhardExtended,
871 ACES,
872 Uncharted2,
873 AgX,
874 Neutral,
875}
876
877impl ToneMappingPass {
878 pub fn new() -> Self {
879 Self {
880 operator: ToneMapOperator::ACES,
881 exposure: 1.0,
882 gamma: 2.2,
883 auto_exposure: true,
884 adaptation_speed: 1.0,
885 min_luminance: 0.01,
886 max_luminance: 10.0,
887 }
888 }
889
890 pub fn register(
891 &self,
892 builder: &mut RenderGraphBuilder,
893 hdr_color: ResourceHandle,
894 bloom: ResourceHandle,
895 ldr_color: ResourceHandle,
896 ) {
897 builder
898 .graphics_pass(self.name())
899 .reads(hdr_color, "hdr_color")
900 .reads(bloom, "bloom_chain")
901 .writes(ldr_color, "ldr_color")
902 .tag("post")
903 .finish();
904 }
905
906 pub fn apply_operator(&self, hdr: [f32; 3]) -> [f32; 3] {
908 let exposed = [
909 hdr[0] * self.exposure,
910 hdr[1] * self.exposure,
911 hdr[2] * self.exposure,
912 ];
913 match self.operator {
914 ToneMapOperator::Reinhard => {
915 let r = exposed[0] / (1.0 + exposed[0]);
916 let g = exposed[1] / (1.0 + exposed[1]);
917 let b = exposed[2] / (1.0 + exposed[2]);
918 [gamma(r, self.gamma), gamma(g, self.gamma), gamma(b, self.gamma)]
919 }
920 ToneMapOperator::ReinhardExtended => {
921 let max_white = self.max_luminance;
922 let r = exposed[0] * (1.0 + exposed[0] / (max_white * max_white))
923 / (1.0 + exposed[0]);
924 let g = exposed[1] * (1.0 + exposed[1] / (max_white * max_white))
925 / (1.0 + exposed[1]);
926 let b = exposed[2] * (1.0 + exposed[2] / (max_white * max_white))
927 / (1.0 + exposed[2]);
928 [gamma(r, self.gamma), gamma(g, self.gamma), gamma(b, self.gamma)]
929 }
930 ToneMapOperator::ACES => {
931 let a = 2.51f32;
933 let b_val = 0.03f32;
934 let c = 2.43f32;
935 let d = 0.59f32;
936 let e = 0.14f32;
937 let aces = |x: f32| -> f32 {
938 let numerator = x * (a * x + b_val);
939 let denominator = x * (c * x + d) + e;
940 (numerator / denominator).clamp(0.0, 1.0)
941 };
942 [
943 gamma(aces(exposed[0]), self.gamma),
944 gamma(aces(exposed[1]), self.gamma),
945 gamma(aces(exposed[2]), self.gamma),
946 ]
947 }
948 ToneMapOperator::Uncharted2 => {
949 let uncharted = |x: f32| -> f32 {
950 let a = 0.15f32;
951 let b_val = 0.50f32;
952 let c = 0.10f32;
953 let d = 0.20f32;
954 let e = 0.02f32;
955 let f = 0.30f32;
956 ((x * (a * x + c * b_val) + d * e) / (x * (a * x + b_val) + d * f)) - e / f
957 };
958 let white = uncharted(self.max_luminance);
959 let r = uncharted(exposed[0]) / white;
960 let g = uncharted(exposed[1]) / white;
961 let b = uncharted(exposed[2]) / white;
962 [gamma(r, self.gamma), gamma(g, self.gamma), gamma(b, self.gamma)]
963 }
964 ToneMapOperator::AgX | ToneMapOperator::Neutral => {
965 let r = exposed[0] / (1.0 + exposed[0]);
967 let g = exposed[1] / (1.0 + exposed[1]);
968 let b = exposed[2] / (1.0 + exposed[2]);
969 [gamma(r, self.gamma), gamma(g, self.gamma), gamma(b, self.gamma)]
970 }
971 }
972 }
973}
974
975impl Default for ToneMappingPass {
976 fn default() -> Self {
977 Self::new()
978 }
979}
980
981fn gamma(linear: f32, gamma_val: f32) -> f32 {
983 if linear <= 0.0 {
984 0.0
985 } else {
986 linear.powf(1.0 / gamma_val)
987 }
988}
989
990impl BuiltinPass for ToneMappingPass {
991 fn name(&self) -> &str {
992 "tonemapping"
993 }
994
995 fn input_names(&self) -> Vec<&str> {
996 vec!["hdr_color", "bloom_chain"]
997 }
998
999 fn output_names(&self) -> Vec<&str> {
1000 vec!["ldr_color"]
1001 }
1002
1003 fn execute(&self, ctx: &PassContext) {
1004 let _viewport = Viewport::from_context(ctx);
1005 let _draw = DrawCall {
1007 vertex_count: 3,
1008 instance_count: 1,
1009 pass_name: self.name().to_string(),
1010 draw_type: DrawType::FullscreenQuad,
1011 };
1012 }
1013}
1014
1015pub struct FXAAPass {
1021 pub subpixel_quality: f32,
1022 pub edge_threshold: f32,
1023 pub edge_threshold_min: f32,
1024}
1025
1026impl FXAAPass {
1027 pub fn new() -> Self {
1028 Self {
1029 subpixel_quality: 0.75,
1030 edge_threshold: 0.166,
1031 edge_threshold_min: 0.0833,
1032 }
1033 }
1034
1035 pub fn register(
1036 &self,
1037 builder: &mut RenderGraphBuilder,
1038 ldr_color: ResourceHandle,
1039 aa_out: ResourceHandle,
1040 ) {
1041 builder
1042 .graphics_pass(self.name())
1043 .reads(ldr_color, "ldr_color")
1044 .writes(aa_out, "aa_color")
1045 .condition(PassCondition::FeatureEnabled("fxaa".to_string()))
1046 .tag("post")
1047 .finish();
1048 }
1049
1050 pub fn luma(color: [f32; 3]) -> f32 {
1052 color[0] * 0.299 + color[1] * 0.587 + color[2] * 0.114
1053 }
1054
1055 pub fn is_edge(
1057 center_luma: f32,
1058 north: f32,
1059 south: f32,
1060 east: f32,
1061 west: f32,
1062 threshold: f32,
1063 threshold_min: f32,
1064 ) -> bool {
1065 let range = north.max(south).max(east).max(west).max(center_luma)
1066 - north.min(south).min(east).min(west).min(center_luma);
1067 let threshold_val = threshold.max(threshold_min);
1068 range > threshold_val
1069 }
1070}
1071
1072impl Default for FXAAPass {
1073 fn default() -> Self {
1074 Self::new()
1075 }
1076}
1077
1078impl BuiltinPass for FXAAPass {
1079 fn name(&self) -> &str {
1080 "fxaa"
1081 }
1082
1083 fn input_names(&self) -> Vec<&str> {
1084 vec!["ldr_color"]
1085 }
1086
1087 fn output_names(&self) -> Vec<&str> {
1088 vec!["aa_color"]
1089 }
1090
1091 fn condition(&self) -> PassCondition {
1092 PassCondition::FeatureEnabled("fxaa".to_string())
1093 }
1094
1095 fn execute(&self, ctx: &PassContext) {
1096 let _viewport = Viewport::from_context(ctx);
1097 let _draw = DrawCall {
1098 vertex_count: 3,
1099 instance_count: 1,
1100 pass_name: self.name().to_string(),
1101 draw_type: DrawType::FullscreenQuad,
1102 };
1103 }
1104}
1105
1106pub struct DebugOverlayPass {
1112 pub mode: DebugVisualization,
1113 pub opacity: f32,
1114 pub show_grid: bool,
1115 pub show_wireframe: bool,
1116}
1117
1118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1119pub enum DebugVisualization {
1120 None,
1121 Depth,
1122 Normals,
1123 Albedo,
1124 Roughness,
1125 Metallic,
1126 AmbientOcclusion,
1127 Velocity,
1128 Wireframe,
1129 LightComplexity,
1130 Overdraw,
1131 MipLevel,
1132}
1133
1134impl DebugOverlayPass {
1135 pub fn new() -> Self {
1136 Self {
1137 mode: DebugVisualization::None,
1138 opacity: 1.0,
1139 show_grid: false,
1140 show_wireframe: false,
1141 }
1142 }
1143
1144 pub fn register(
1145 &self,
1146 builder: &mut RenderGraphBuilder,
1147 input_color: ResourceHandle,
1148 depth: ResourceHandle,
1149 debug_out: ResourceHandle,
1150 ) {
1151 builder
1152 .graphics_pass(self.name())
1153 .reads(input_color, "aa_color")
1154 .reads(depth, "depth")
1155 .writes(debug_out, "debug_color")
1156 .condition(PassCondition::FeatureEnabled("debug_overlay".to_string()))
1157 .tag("debug")
1158 .finish();
1159 }
1160
1161 pub fn depth_to_color(depth: f32, near: f32, far: f32) -> [f32; 3] {
1163 let linear = (2.0 * near * far) / (far + near - depth * (far - near));
1164 let normalized = (linear - near) / (far - near);
1165 let v = normalized.clamp(0.0, 1.0);
1166 [v, v, v]
1167 }
1168
1169 pub fn normal_to_color(normal: [f32; 3]) -> [f32; 3] {
1171 [
1172 normal[0] * 0.5 + 0.5,
1173 normal[1] * 0.5 + 0.5,
1174 normal[2] * 0.5 + 0.5,
1175 ]
1176 }
1177
1178 pub fn heat_map(value: f32) -> [f32; 3] {
1180 let v = value.clamp(0.0, 1.0);
1181 if v < 0.25 {
1182 [0.0, v * 4.0, 1.0]
1183 } else if v < 0.5 {
1184 [0.0, 1.0, 1.0 - (v - 0.25) * 4.0]
1185 } else if v < 0.75 {
1186 [(v - 0.5) * 4.0, 1.0, 0.0]
1187 } else {
1188 [1.0, 1.0 - (v - 0.75) * 4.0, 0.0]
1189 }
1190 }
1191}
1192
1193impl Default for DebugOverlayPass {
1194 fn default() -> Self {
1195 Self::new()
1196 }
1197}
1198
1199impl BuiltinPass for DebugOverlayPass {
1200 fn name(&self) -> &str {
1201 "debug_overlay"
1202 }
1203
1204 fn input_names(&self) -> Vec<&str> {
1205 vec!["aa_color", "depth"]
1206 }
1207
1208 fn output_names(&self) -> Vec<&str> {
1209 vec!["debug_color"]
1210 }
1211
1212 fn condition(&self) -> PassCondition {
1213 PassCondition::FeatureEnabled("debug_overlay".to_string())
1214 }
1215
1216 fn execute(&self, ctx: &PassContext) {
1217 let _viewport = Viewport::from_context(ctx);
1218 if self.mode == DebugVisualization::None && !self.show_grid && !self.show_wireframe {
1219 return;
1220 }
1221 let _draw = DrawCall {
1222 vertex_count: 3,
1223 instance_count: 1,
1224 pass_name: self.name().to_string(),
1225 draw_type: DrawType::FullscreenQuad,
1226 };
1227 }
1228}
1229
1230pub struct FinalCompositePass {
1236 pub letterbox: bool,
1237 pub letterbox_color: [f32; 3],
1238 pub dither: bool,
1239 pub output_format: TextureFormat,
1240}
1241
1242impl FinalCompositePass {
1243 pub fn new() -> Self {
1244 Self {
1245 letterbox: false,
1246 letterbox_color: [0.0, 0.0, 0.0],
1247 dither: true,
1248 output_format: TextureFormat::Bgra8Srgb,
1249 }
1250 }
1251
1252 pub fn register(
1253 &self,
1254 builder: &mut RenderGraphBuilder,
1255 input_color: ResourceHandle,
1256 swapchain: ResourceHandle,
1257 ) {
1258 builder
1259 .graphics_pass(self.name())
1260 .reads(input_color, "aa_color")
1261 .writes(swapchain, "swapchain")
1262 .side_effects()
1263 .tag("present")
1264 .finish();
1265 }
1266
1267 pub fn apply_dither(color: [f32; 3], uv: [f32; 2]) -> [f32; 3] {
1269 let noise = Self::ign(uv[0], uv[1]);
1271 let dither = (noise - 0.5) / 255.0;
1272 [
1273 (color[0] + dither).clamp(0.0, 1.0),
1274 (color[1] + dither).clamp(0.0, 1.0),
1275 (color[2] + dither).clamp(0.0, 1.0),
1276 ]
1277 }
1278
1279 fn ign(x: f32, y: f32) -> f32 {
1281 let f = 0.06711056 * x + 0.00583715 * y;
1282 (52.9829189 * (f - f.floor())).fract()
1283 }
1284}
1285
1286impl Default for FinalCompositePass {
1287 fn default() -> Self {
1288 Self::new()
1289 }
1290}
1291
1292impl BuiltinPass for FinalCompositePass {
1293 fn name(&self) -> &str {
1294 "final_composite"
1295 }
1296
1297 fn input_names(&self) -> Vec<&str> {
1298 vec!["aa_color"]
1299 }
1300
1301 fn output_names(&self) -> Vec<&str> {
1302 vec!["swapchain"]
1303 }
1304
1305 fn execute(&self, ctx: &PassContext) {
1306 let _viewport = Viewport::from_context(ctx);
1307 let _draw = DrawCall {
1308 vertex_count: 3,
1309 instance_count: 1,
1310 pass_name: self.name().to_string(),
1311 draw_type: DrawType::FullscreenQuad,
1312 };
1313 }
1314}
1315
1316pub fn build_deferred_pipeline(
1323 width: u32,
1324 height: u32,
1325 features: &[&str],
1326) -> RenderGraph {
1327 let mut b = RenderGraphBuilder::new("deferred_pipeline", width, height);
1328
1329 for f in features {
1331 b.enable_feature(f);
1332 }
1333
1334 let depth = b.texture("depth", TextureFormat::Depth32Float);
1336 let albedo = b.texture("gbuffer_albedo", TextureFormat::Rgba8Srgb);
1337 let normal = b.texture("gbuffer_normal", TextureFormat::Rgba16Float);
1338 let rm = b.texture("gbuffer_rm", TextureFormat::Rgba8Unorm);
1339 let ssao_tex = b.texture_scaled("ssao", TextureFormat::R16Float, 0.5, 0.5);
1340 let hdr_color = b.texture("hdr_color", TextureFormat::Rgba16Float);
1341 let shadow_map = b.texture_absolute("shadow_map_0", TextureFormat::Depth32Float, 2048, 2048);
1342 let oit_accum = b.texture("oit_accum", TextureFormat::Rgba16Float);
1343 let oit_reveal = b.texture("oit_reveal", TextureFormat::R8Unorm);
1344 let bloom_chain = b.texture_scaled("bloom_chain", TextureFormat::Rgba16Float, 0.5, 0.5);
1345 let ldr_color = b.texture("ldr_color", TextureFormat::Rgba8Unorm);
1346 let aa_color = b.texture("aa_color", TextureFormat::Rgba8Unorm);
1347 let debug_color = b.texture("debug_color", TextureFormat::Rgba8Unorm);
1348 let swapchain = b.import("swapchain", TextureFormat::Bgra8Srgb);
1349
1350 let depth_pre = DepthPrePass::new();
1352 depth_pre.register(&mut b, depth);
1353
1354 let gbuffer = GBufferPass::new();
1356 gbuffer.register(&mut b, depth, albedo, normal, rm);
1357
1358 let shadow = ShadowPass::new(0);
1360 shadow.register(&mut b, shadow_map);
1361
1362 let ssao = SSAOPass::new();
1364 ssao.register(&mut b, depth, normal, ssao_tex);
1365
1366 let lighting = LightingPass::new();
1368 lighting.register(&mut b, albedo, normal, rm, depth, ssao_tex, hdr_color);
1369
1370 let skybox = SkyboxPass::new();
1372 skybox.register(&mut b, depth, hdr_color);
1373
1374 let transparency = TransparencyPass::new();
1376 transparency.register(&mut b, depth, hdr_color, oit_accum, oit_reveal);
1377
1378 let bloom = BloomPass::new();
1380 bloom.register(&mut b, hdr_color, bloom_chain);
1381
1382 let tonemap = ToneMappingPass::new();
1384 tonemap.register(&mut b, hdr_color, bloom_chain, ldr_color);
1385
1386 let fxaa = FXAAPass::new();
1388 fxaa.register(&mut b, ldr_color, aa_color);
1389
1390 let debug = DebugOverlayPass::new();
1392 debug.register(&mut b, aa_color, depth, debug_color);
1393
1394 let composite = FinalCompositePass::new();
1396 composite.register(&mut b, aa_color, swapchain);
1397
1398 b.build()
1399}
1400
1401#[cfg(test)]
1406mod tests {
1407 use super::*;
1408
1409 #[test]
1410 fn test_build_deferred_pipeline() {
1411 let mut graph = build_deferred_pipeline(1920, 1080, &["ssao", "fxaa"]);
1412 assert!(graph.pass_count() >= 10);
1414 let result = graph.topological_sort();
1416 if let Ok(sorted) = &result {
1419 assert!(!sorted.is_empty());
1420 }
1421 }
1422
1423 #[test]
1424 fn test_depth_prepass() {
1425 let pass = DepthPrePass::new();
1426 assert_eq!(pass.name(), "depth_prepass");
1427 assert!(pass.input_names().is_empty());
1428 assert_eq!(pass.output_names(), vec!["depth"]);
1429 }
1430
1431 #[test]
1432 fn test_gbuffer_pass() {
1433 let pass = GBufferPass::new();
1434 assert_eq!(pass.name(), "gbuffer");
1435 assert!(!pass.output_names().is_empty());
1436 }
1437
1438 #[test]
1439 fn test_shadow_pass() {
1440 let pass = ShadowPass::new(0);
1441 assert_eq!(pass.light_index, 0);
1442 assert_eq!(pass.cascade_count, 4);
1443 assert_eq!(pass.pass_name(), "shadow_light_0");
1444 }
1445
1446 #[test]
1447 fn test_ssao_pass() {
1448 let pass = SSAOPass::new();
1449 assert_eq!(pass.name(), "ssao");
1450 assert_eq!(pass.pass_type(), PassType::Compute);
1451 assert_eq!(pass.queue_affinity(), QueueAffinity::Compute);
1452 }
1453
1454 #[test]
1455 fn test_tone_mapping_operators() {
1456 let pass = ToneMappingPass::new();
1457 let hdr = [2.0, 1.0, 0.5];
1458 let ldr = pass.apply_operator(hdr);
1459 for c in &ldr {
1461 assert!(*c >= 0.0 && *c <= 1.0);
1462 }
1463 }
1464
1465 #[test]
1466 fn test_fxaa_luma() {
1467 let luma = FXAAPass::luma([1.0, 1.0, 1.0]);
1468 assert!((luma - 1.0).abs() < 0.001);
1469
1470 let luma = FXAAPass::luma([0.0, 0.0, 0.0]);
1471 assert!((luma - 0.0).abs() < 0.001);
1472 }
1473
1474 #[test]
1475 fn test_debug_depth_to_color() {
1476 let color = DebugOverlayPass::depth_to_color(0.5, 0.1, 100.0);
1477 for c in &color {
1478 assert!(*c >= 0.0 && *c <= 1.0);
1479 }
1480 }
1481
1482 #[test]
1483 fn test_debug_heat_map() {
1484 let cold = DebugOverlayPass::heat_map(0.0);
1485 assert!(cold[2] > cold[0]); let hot = DebugOverlayPass::heat_map(1.0);
1488 assert!(hot[0] > hot[2]); }
1490
1491 #[test]
1492 fn test_final_composite_dither() {
1493 let color = FinalCompositePass::apply_dither([0.5, 0.5, 0.5], [100.0, 200.0]);
1494 for c in &color {
1495 assert!(*c >= 0.0 && *c <= 1.0);
1496 }
1497 }
1498
1499 #[test]
1500 fn test_transparency_pass() {
1501 let pass = TransparencyPass::new();
1502 assert_eq!(pass.weight_function, WeightFunction::DepthWeight);
1503 assert_eq!(pass.name(), "transparency");
1504 }
1505
1506 #[test]
1507 fn test_bloom_pass() {
1508 let pass = BloomPass::new();
1509 assert_eq!(pass.mip_count, 5);
1510 assert_eq!(pass.name(), "bloom");
1511 }
1512
1513 #[test]
1514 fn test_skybox_pass() {
1515 let pass = SkyboxPass::new();
1516 assert_eq!(pass.name(), "skybox");
1517 assert_eq!(pass.exposure, 1.0);
1518 }
1519}