bevy_hikari/
lib.rs

1use crate::{
2    light::{LightNode, LightPlugin},
3    mesh_material::MeshMaterialPlugin,
4    overlay::{OverlayNode, OverlayPlugin},
5    post_process::{PostProcessNode, PostProcessPlugin},
6    prepass::{PrepassNode, PrepassPlugin},
7    transform::TransformPlugin,
8    view::ViewPlugin,
9};
10use bevy::{
11    asset::{load_internal_asset, load_internal_binary_asset},
12    core_pipeline::{
13        bloom::BloomNode, fxaa::FxaaNode, tonemapping::TonemappingNode, upscaling::UpscalingNode,
14    },
15    ecs::query::QueryItem,
16    prelude::*,
17    reflect::TypeUuid,
18    render::{
19        extract_component::{ExtractComponent, ExtractComponentPlugin},
20        extract_resource::{ExtractResource, ExtractResourcePlugin},
21        render_asset::RenderAssets,
22        render_graph::{RenderGraph, SlotInfo, SlotType},
23        render_resource::*,
24        renderer::RenderDevice,
25        texture::{CompressedImageFormats, FallbackImage, ImageType},
26        RenderApp,
27    },
28};
29use std::num::NonZeroU32;
30
31#[macro_use]
32extern crate num_derive;
33
34pub mod light;
35pub mod mesh_material;
36pub mod overlay;
37pub mod post_process;
38pub mod prelude;
39pub mod prepass;
40pub mod transform;
41pub mod view;
42
43pub mod graph {
44    pub const NAME: &str = "hikari";
45    pub mod node {
46        pub const PREPASS: &str = "hikari_prepass";
47        pub const LIGHT: &str = "hikari_light";
48        pub const POST_PROCESS: &str = "hikari_post_process";
49        pub const OVERLAY: &str = "hikari_overlay";
50    }
51}
52
53pub const WORKGROUP_SIZE: u32 = 8;
54pub const NOISE_TEXTURE_COUNT: usize = 16;
55
56pub const UTILS_SHADER_HANDLE: HandleUntyped =
57    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 4462033275253590181);
58pub const MESH_VIEW_TYPES_SHADER_HANDLE: HandleUntyped =
59    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 10086770709483722043);
60pub const MESH_VIEW_BINDINGS_SHADER_HANDLE: HandleUntyped =
61    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 8835349515886344623);
62pub const MESH_MATERIAL_TYPES_SHADER_HANDLE: HandleUntyped =
63    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 15819591594687298858);
64pub const MESH_MATERIAL_BINDINGS_SHADER_HANDLE: HandleUntyped =
65    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 5025976374517268);
66pub const DEFERRED_BINDINGS_SHADER_HANDLE: HandleUntyped =
67    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 14467895678105108252);
68pub const RESERVOIR_TYPES_SHADER_HANDLE: HandleUntyped =
69    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7770589395703787378);
70pub const RESERVOIR_BINDINGS_SHADER_HANDLE: HandleUntyped =
71    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 11658053183743104810);
72pub const RESERVOIR_FUNCTIONS_SHADER_HANDLE: HandleUntyped =
73    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7650021494161056224);
74pub const PREPASS_SHADER_HANDLE: HandleUntyped =
75    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 4693612430004931427);
76pub const LIGHT_SHADER_HANDLE: HandleUntyped =
77    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 9657319286592943583);
78pub const DENOISE_SHADER_HANDLE: HandleUntyped =
79    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 5179661212363325472);
80pub const TONE_MAPPING_SHADER_HANDLE: HandleUntyped =
81    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 3567017338952956671);
82pub const TAA_SHADER_HANDLE: HandleUntyped =
83    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1780446804546284);
84pub const SMAA_SHADER_HANDLE: HandleUntyped =
85    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 3793959332758430953);
86pub const FSR1_EASU_SHADER_HANDLE: HandleUntyped =
87    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 11823787237582686663);
88pub const FSR1_RCAS_SHADER_HANDLE: HandleUntyped =
89    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 17003547378277520107);
90pub const OVERLAY_SHADER_HANDLE: HandleUntyped =
91    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 10969344919103020615);
92pub const QUAD_MESH_HANDLE: HandleUntyped =
93    HandleUntyped::weak_from_u64(Mesh::TYPE_UUID, 4740146776519512271);
94
95pub struct HikariPlugin;
96impl Plugin for HikariPlugin {
97    fn build(&self, app: &mut App) {
98        load_internal_asset!(
99            app,
100            UTILS_SHADER_HANDLE,
101            "shaders/utils.wgsl",
102            Shader::from_wgsl
103        );
104        load_internal_asset!(
105            app,
106            MESH_VIEW_TYPES_SHADER_HANDLE,
107            "shaders/mesh_view_types.wgsl",
108            Shader::from_wgsl
109        );
110        load_internal_asset!(
111            app,
112            MESH_VIEW_BINDINGS_SHADER_HANDLE,
113            "shaders/mesh_view_bindings.wgsl",
114            Shader::from_wgsl
115        );
116        load_internal_asset!(
117            app,
118            MESH_MATERIAL_TYPES_SHADER_HANDLE,
119            "shaders/mesh_material_types.wgsl",
120            Shader::from_wgsl
121        );
122        load_internal_asset!(
123            app,
124            MESH_MATERIAL_BINDINGS_SHADER_HANDLE,
125            "shaders/mesh_material_bindings.wgsl",
126            Shader::from_wgsl
127        );
128        load_internal_asset!(
129            app,
130            DEFERRED_BINDINGS_SHADER_HANDLE,
131            "shaders/deferred_bindings.wgsl",
132            Shader::from_wgsl
133        );
134        load_internal_asset!(
135            app,
136            PREPASS_SHADER_HANDLE,
137            "shaders/prepass.wgsl",
138            Shader::from_wgsl
139        );
140        load_internal_asset!(
141            app,
142            LIGHT_SHADER_HANDLE,
143            "shaders/light.wgsl",
144            Shader::from_wgsl
145        );
146        load_internal_asset!(
147            app,
148            DENOISE_SHADER_HANDLE,
149            "shaders/denoise.wgsl",
150            Shader::from_wgsl
151        );
152        load_internal_asset!(
153            app,
154            TONE_MAPPING_SHADER_HANDLE,
155            "shaders/tone_mapping.wgsl",
156            Shader::from_wgsl
157        );
158        load_internal_asset!(
159            app,
160            TAA_SHADER_HANDLE,
161            "shaders/taa.wgsl",
162            Shader::from_wgsl
163        );
164        load_internal_asset!(
165            app,
166            SMAA_SHADER_HANDLE,
167            "shaders/smaa.wgsl",
168            Shader::from_wgsl
169        );
170        load_internal_asset!(
171            app,
172            OVERLAY_SHADER_HANDLE,
173            "shaders/overlay.wgsl",
174            Shader::from_wgsl
175        );
176        load_internal_binary_asset!(
177            app,
178            FSR1_EASU_SHADER_HANDLE,
179            "shaders/fsr/fsr_pass_easu.spv",
180            Shader::from_spirv
181        );
182        load_internal_binary_asset!(
183            app,
184            FSR1_RCAS_SHADER_HANDLE,
185            "shaders/fsr/fsr_pass_rcas.spv",
186            Shader::from_spirv
187        );
188
189        let noise_load_system = move |mut commands: Commands, mut images: ResMut<Assets<Image>>| {
190            let bytes = [
191                include_bytes!("noise/LDR_RGBA_0.png"),
192                include_bytes!("noise/LDR_RGBA_1.png"),
193                include_bytes!("noise/LDR_RGBA_2.png"),
194                include_bytes!("noise/LDR_RGBA_3.png"),
195                include_bytes!("noise/LDR_RGBA_4.png"),
196                include_bytes!("noise/LDR_RGBA_5.png"),
197                include_bytes!("noise/LDR_RGBA_6.png"),
198                include_bytes!("noise/LDR_RGBA_7.png"),
199                include_bytes!("noise/LDR_RGBA_8.png"),
200                include_bytes!("noise/LDR_RGBA_9.png"),
201                include_bytes!("noise/LDR_RGBA_10.png"),
202                include_bytes!("noise/LDR_RGBA_11.png"),
203                include_bytes!("noise/LDR_RGBA_12.png"),
204                include_bytes!("noise/LDR_RGBA_13.png"),
205                include_bytes!("noise/LDR_RGBA_14.png"),
206                include_bytes!("noise/LDR_RGBA_15.png"),
207            ];
208            let handles = Vec::from(bytes.map(|buffer| {
209                let image = Image::from_buffer(
210                    buffer,
211                    ImageType::Extension("png"),
212                    CompressedImageFormats::NONE,
213                    false,
214                )
215                .unwrap();
216                images.add(image)
217            }));
218            commands.insert_resource(NoiseTextures(handles));
219        };
220
221        app.register_type::<HikariUniversalSettings>()
222            .register_type::<HikariSettings>()
223            .register_type::<Taa>()
224            .register_type::<Upscale>()
225            .init_resource::<HikariUniversalSettings>()
226            .add_plugin(ExtractResourcePlugin::<NoiseTextures>::default())
227            .add_plugin(ExtractResourcePlugin::<HikariUniversalSettings>::default())
228            .add_plugin(ExtractComponentPlugin::<HikariSettings>::default())
229            .add_plugin(TransformPlugin)
230            .add_plugin(ViewPlugin)
231            .add_plugin(MeshMaterialPlugin)
232            .add_plugin(PrepassPlugin)
233            .add_plugin(LightPlugin)
234            .add_plugin(PostProcessPlugin)
235            .add_plugin(OverlayPlugin)
236            .add_startup_system(noise_load_system);
237
238        if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
239            use bevy::core_pipeline::core_3d;
240
241            let prepass_node = PrepassNode::new(&mut render_app.world);
242            let light_node = LightNode::new(&mut render_app.world);
243            let post_process_node = PostProcessNode::new(&mut render_app.world);
244            let overlay_node = OverlayNode::new(&mut render_app.world);
245            let bloom_node = BloomNode::new(&mut render_app.world);
246            let tonemapping_node = TonemappingNode::new(&mut render_app.world);
247            let fxaa_node = FxaaNode::new(&mut render_app.world);
248            let upscaling_node = UpscalingNode::new(&mut render_app.world);
249
250            let mut graph = render_app.world.resource_mut::<RenderGraph>();
251
252            let mut sub_graph = RenderGraph::default();
253            sub_graph.set_input(vec![SlotInfo::new(
254                core_3d::graph::input::VIEW_ENTITY,
255                SlotType::Entity,
256            )]);
257
258            sub_graph.add_node(graph::node::PREPASS, prepass_node);
259            sub_graph.add_node(graph::node::LIGHT, light_node);
260            sub_graph.add_node(graph::node::POST_PROCESS, post_process_node);
261            sub_graph.add_node(graph::node::OVERLAY, overlay_node);
262            sub_graph.add_node(core_3d::graph::node::BLOOM, bloom_node);
263            sub_graph.add_node(core_3d::graph::node::TONEMAPPING, tonemapping_node);
264            sub_graph.add_node(core_3d::graph::node::FXAA, fxaa_node);
265            sub_graph.add_node(core_3d::graph::node::UPSCALING, upscaling_node);
266
267            sub_graph
268                .add_slot_edge(
269                    sub_graph.input_node().unwrap().id,
270                    core_3d::graph::input::VIEW_ENTITY,
271                    graph::node::PREPASS,
272                    PrepassNode::IN_VIEW,
273                )
274                .unwrap();
275            sub_graph
276                .add_slot_edge(
277                    sub_graph.input_node().unwrap().id,
278                    core_3d::graph::input::VIEW_ENTITY,
279                    graph::node::LIGHT,
280                    LightNode::IN_VIEW,
281                )
282                .unwrap();
283            sub_graph
284                .add_slot_edge(
285                    sub_graph.input_node().unwrap().id,
286                    core_3d::graph::input::VIEW_ENTITY,
287                    graph::node::POST_PROCESS,
288                    PostProcessNode::IN_VIEW,
289                )
290                .unwrap();
291            sub_graph
292                .add_slot_edge(
293                    sub_graph.input_node().unwrap().id,
294                    core_3d::graph::input::VIEW_ENTITY,
295                    graph::node::OVERLAY,
296                    OverlayNode::IN_VIEW,
297                )
298                .unwrap();
299            sub_graph
300                .add_slot_edge(
301                    sub_graph.input_node().unwrap().id,
302                    core_3d::graph::input::VIEW_ENTITY,
303                    core_3d::graph::node::BLOOM,
304                    BloomNode::IN_VIEW,
305                )
306                .unwrap();
307            sub_graph
308                .add_slot_edge(
309                    sub_graph.input_node().unwrap().id,
310                    core_3d::graph::input::VIEW_ENTITY,
311                    core_3d::graph::node::TONEMAPPING,
312                    TonemappingNode::IN_VIEW,
313                )
314                .unwrap();
315            sub_graph
316                .add_slot_edge(
317                    sub_graph.input_node().unwrap().id,
318                    core_3d::graph::input::VIEW_ENTITY,
319                    core_3d::graph::node::FXAA,
320                    FxaaNode::IN_VIEW,
321                )
322                .unwrap();
323            sub_graph
324                .add_slot_edge(
325                    sub_graph.input_node().unwrap().id,
326                    core_3d::graph::input::VIEW_ENTITY,
327                    core_3d::graph::node::UPSCALING,
328                    UpscalingNode::IN_VIEW,
329                )
330                .unwrap();
331
332            // PREPASS -> LIGHT -> POST_PROCESS -> OVERLAY -> BLOOM -> TONEMAPPING -> UPSCALING
333            sub_graph
334                .add_node_edge(graph::node::PREPASS, graph::node::LIGHT)
335                .unwrap();
336            sub_graph
337                .add_node_edge(graph::node::LIGHT, graph::node::POST_PROCESS)
338                .unwrap();
339            sub_graph
340                .add_node_edge(graph::node::POST_PROCESS, graph::node::OVERLAY)
341                .unwrap();
342            sub_graph
343                .add_node_edge(
344                    graph::node::OVERLAY,
345                    bevy::core_pipeline::core_3d::graph::node::BLOOM,
346                )
347                .unwrap();
348            sub_graph
349                .add_node_edge(
350                    bevy::core_pipeline::core_3d::graph::node::BLOOM,
351                    bevy::core_pipeline::core_3d::graph::node::TONEMAPPING,
352                )
353                .unwrap();
354            sub_graph
355                .add_node_edge(
356                    bevy::core_pipeline::core_3d::graph::node::TONEMAPPING,
357                    bevy::core_pipeline::core_3d::graph::node::FXAA,
358                )
359                .unwrap();
360            sub_graph
361                .add_node_edge(
362                    bevy::core_pipeline::core_3d::graph::node::FXAA,
363                    bevy::core_pipeline::core_3d::graph::node::UPSCALING,
364                )
365                .unwrap();
366
367            graph.add_sub_graph(graph::NAME, sub_graph);
368        }
369    }
370}
371
372/// Settings apply globally.
373#[derive(Debug, Clone, Resource, Reflect)]
374#[reflect(Resource)]
375pub struct HikariUniversalSettings {
376    /// Whether to build acceleration structure for mesh assets.
377    pub build_mesh_acceleration_structure: bool,
378    /// Whether to build acceleration structure for scene instances.
379    pub build_instance_acceleration_structure: bool,
380}
381
382impl Default for HikariUniversalSettings {
383    fn default() -> Self {
384        Self {
385            build_mesh_acceleration_structure: true,
386            build_instance_acceleration_structure: true,
387        }
388    }
389}
390
391impl ExtractResource for HikariUniversalSettings {
392    type Source = Self;
393
394    fn extract_resource(source: &Self::Source) -> Self {
395        source.clone()
396    }
397}
398
399/// Settings that must be attached on cameras with `bevy_hikari` render graph.
400#[derive(Debug, Clone, Component, Reflect)]
401#[reflect(Component)]
402pub struct HikariSettings {
403    /// The interval of frames between sample validation passes.
404    pub direct_validate_interval: usize,
405    /// The interval of frames between sample validation passes.
406    pub emissive_validate_interval: usize,
407    /// Temporal reservoir sample count is capped by this value.
408    pub max_temporal_reuse_count: usize,
409    /// Spatial reservoir sample count is capped by this value.
410    pub max_spatial_reuse_count: usize,
411    /// Max lifetime of a reservoir sample before being replaced with new one.
412    pub max_reservoir_lifetime: f32,
413    /// Half angle of the solar cone apex in radians.
414    pub solar_angle: f32,
415    /// Count of indirect bounces.
416    pub indirect_bounces: usize,
417    /// Threshold for the indirect luminance to reduce fireflies.
418    pub max_indirect_luminance: f32,
419    /// Clear color override.
420    pub clear_color: Color,
421    /// Whether to do temporal sample reuse in ReSTIR.
422    pub temporal_reuse: bool,
423    /// Whether to do spatial sample reuse for emissive lighting in ReSTIR.
424    pub emissive_spatial_reuse: bool,
425    /// Whether to do spatial sample reuse for indirect lighting in ReSTIR.
426    pub indirect_spatial_reuse: bool,
427    /// Whether to do noise filtering.
428    pub denoise: bool,
429    /// Which temporal filtering implementation to use.
430    pub taa: Taa,
431    /// Which upscaling implementation to use.
432    pub upscale: Upscale,
433}
434
435impl Default for HikariSettings {
436    fn default() -> Self {
437        Self {
438            direct_validate_interval: 3,
439            emissive_validate_interval: 5,
440            max_temporal_reuse_count: 50,
441            max_spatial_reuse_count: 800,
442            max_reservoir_lifetime: 100.0,
443            solar_angle: 0.046,
444            clear_color: Color::rgb(0.4, 0.4, 0.4),
445            indirect_bounces: 1,
446            max_indirect_luminance: 10.0,
447            temporal_reuse: true,
448            emissive_spatial_reuse: false,
449            indirect_spatial_reuse: true,
450            denoise: true,
451            taa: Taa::default(),
452            upscale: Upscale::default(),
453        }
454    }
455}
456
457impl ExtractComponent for HikariSettings {
458    type Query = &'static Self;
459    type Filter = ();
460
461    fn extract_component(item: QueryItem<Self::Query>) -> Self {
462        item.clone()
463    }
464}
465
466/// Temporal Anti-Aliasing Method to use.
467#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, Reflect)]
468pub enum Taa {
469    #[default]
470    Jasmine,
471    None,
472}
473
474/// Upscale method to use.
475#[derive(Debug, Clone, Copy, Reflect)]
476pub enum Upscale {
477    /// [AMD FidelityFX™ Super Resolution](https://gpuopen.com/fidelityfx-superresolution/).
478    Fsr1 {
479        /// Renders the main pass and post process on a low resolution texture.
480        ratio: f32,
481        /// From 0.0 - 2.0 where 0.0 means max sharpness.
482        sharpness: f32,
483    },
484    /// [Filmic SMAA TU4x](https://www.activision.com/cdn/research/Dynamic_Temporal_Antialiasing_and_Upsampling_in_Call_of_Duty_v4.pdf).
485    SmaaTu4x {
486        /// Renders the main pass and post process on a low resolution texture.
487        ratio: f32,
488    },
489    None,
490}
491
492impl Default for Upscale {
493    fn default() -> Self {
494        Self::SmaaTu4x { ratio: 2.0 }
495    }
496}
497
498impl Upscale {
499    pub fn ratio(&self) -> f32 {
500        match self {
501            Upscale::Fsr1 { ratio, .. } | Upscale::SmaaTu4x { ratio } => ratio.clamp(1.0, 2.0),
502            Upscale::None => 1.0,
503        }
504    }
505
506    pub fn sharpness(&self) -> f32 {
507        match self {
508            Upscale::Fsr1 { sharpness, .. } => *sharpness,
509            _ => 0.0,
510        }
511    }
512}
513
514#[derive(Clone, Deref, Resource, ExtractResource)]
515pub struct NoiseTextures(pub Vec<Handle<Image>>);
516
517impl AsBindGroup for NoiseTextures {
518    type Data = ();
519
520    fn as_bind_group(
521        &self,
522        layout: &BindGroupLayout,
523        render_device: &RenderDevice,
524        images: &RenderAssets<Image>,
525        fallback_image: &FallbackImage,
526    ) -> Result<PreparedBindGroup<Self>, AsBindGroupError> {
527        let images: Vec<_> = self
528            .iter()
529            .map(|handle| match images.get(handle) {
530                Some(image) => image,
531                None => fallback_image,
532            })
533            .collect();
534        let texture_views: Vec<_> = images.iter().map(|image| &*image.texture_view).collect();
535        let bindings = images
536            .iter()
537            .map(|image| OwnedBindingResource::TextureView(image.texture_view.clone()))
538            .collect();
539
540        let sampler = render_device.create_sampler(&SamplerDescriptor {
541            label: None,
542            address_mode_u: AddressMode::Repeat,
543            address_mode_v: AddressMode::Repeat,
544            address_mode_w: AddressMode::Repeat,
545            mag_filter: FilterMode::Nearest,
546            min_filter: FilterMode::Nearest,
547            mipmap_filter: FilterMode::Nearest,
548            ..Default::default()
549        });
550
551        let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
552            label: None,
553            layout,
554            entries: &[
555                BindGroupEntry {
556                    binding: 0,
557                    resource: BindingResource::TextureViewArray(&texture_views),
558                },
559                BindGroupEntry {
560                    binding: 1,
561                    resource: BindingResource::Sampler(&sampler),
562                },
563            ],
564        });
565
566        Ok(PreparedBindGroup {
567            bindings,
568            bind_group,
569            data: (),
570        })
571    }
572
573    fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout {
574        render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
575            label: None,
576            entries: &[
577                // Blue Noise Texture
578                BindGroupLayoutEntry {
579                    binding: 0,
580                    visibility: ShaderStages::COMPUTE,
581                    ty: BindingType::Texture {
582                        sample_type: TextureSampleType::Float { filterable: false },
583                        view_dimension: TextureViewDimension::D2,
584                        multisampled: false,
585                    },
586                    count: NonZeroU32::new(NOISE_TEXTURE_COUNT as u32),
587                },
588                BindGroupLayoutEntry {
589                    binding: 1,
590                    visibility: ShaderStages::COMPUTE,
591                    ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
592                    count: None,
593                },
594            ],
595        })
596    }
597}