Skip to main content

bevy_pbr/ssao/
mod.rs

1use bevy_app::{App, Plugin};
2use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
3use bevy_camera::{Camera, Camera3d};
4use bevy_core_pipeline::{
5    prepass::{DepthPrepass, NormalPrepass, ViewPrepassTextures},
6    schedule::{Core3d, Core3dSystems},
7};
8use bevy_ecs::{
9    prelude::{Component, Entity},
10    query::{Has, With},
11    reflect::ReflectComponent,
12    resource::Resource,
13    schedule::IntoScheduleConfigs,
14    system::{Commands, Query, Res, ResMut},
15    world::{FromWorld, World},
16};
17use bevy_image::ToExtents;
18use bevy_reflect::{std_traits::ReflectDefault, Reflect};
19use bevy_render::{
20    camera::{ExtractedCamera, TemporalJitter},
21    diagnostic::RecordDiagnostics,
22    extract_component::ExtractComponent,
23    globals::{GlobalsBuffer, GlobalsUniform},
24    render_resource::{
25        binding_types::{
26            sampler, texture_2d, texture_depth_2d, texture_storage_2d, uniform_buffer,
27        },
28        *,
29    },
30    renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue, ViewQuery},
31    sync_component::SyncComponentPlugin,
32    sync_world::RenderEntity,
33    texture::{CachedTexture, TextureCache},
34    view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms},
35    Extract, ExtractSchedule, GpuResourceAppExt, Render, RenderApp, RenderSystems,
36};
37use bevy_shader::{load_shader_library, Shader, ShaderDefVal};
38use bevy_utils::prelude::default;
39use core::mem;
40use tracing::{error, warn};
41
42/// Plugin for screen space ambient occlusion.
43pub struct ScreenSpaceAmbientOcclusionPlugin;
44
45impl Plugin for ScreenSpaceAmbientOcclusionPlugin {
46    fn build(&self, app: &mut App) {
47        {
    {
        let mut embedded =
            app.world_mut().resource_mut::<::bevy_asset::io::embedded::EmbeddedAssetRegistry>();
        let path =
            {
                let crate_name = "bevy_pbr::ssao".split(':').next().unwrap();
                ::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
                    "src".as_ref(), "src/ssao/mod.rs".as_ref(),
                    "utils.wesl".as_ref())
            };
        let watched_path =
            ::bevy_asset::io::embedded::watched_path("src/ssao/mod.rs",
                "utils.wesl");
        embedded.insert_asset(watched_path, &path,
            b"import bevy_render::maths::{PI, HALF_PI};\n\n/// Approximates single-bounce ambient occlusion to multi-bounce ambient occlusion\n/// https://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pdf#page=78\nfn ssao_multibounce(visibility: f32, base_color: vec3<f32>) -> vec3<f32> {\n    let a = 2.0404 * base_color - 0.3324;\n    let b = -4.7951 * base_color + 0.6417;\n    let c = 2.7552 * base_color + 0.6903;\n    let x = vec3<f32>(visibility);\n    return max(x, ((x * a + b) * x + c) * x);\n}\n");
    }
};
let handle:
        ::bevy_shader::_macro::bevy_asset::prelude::Handle<::bevy_shader::prelude::Shader> =
    {
        let (path, asset_server) =
            {
                let path =
                    {
                        {
                            let crate_name =
                                "bevy_pbr::ssao".split(':').next().unwrap();
                            ::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
                                "src".as_ref(), "src/ssao/mod.rs".as_ref(),
                                "utils.wesl".as_ref())
                        }
                    };
                let path =
                    ::bevy_asset::AssetPath::from_path_buf(path).with_source("embedded");
                let asset_server =
                    ::bevy_asset::io::embedded::GetAssetServer::get_asset_server(app);
                (path, asset_server)
            };
        asset_server.load(path)
    };
::core::mem::forget(handle);load_shader_library!(app, "utils.wesl");
48
49        {
    {
        let mut embedded =
            app.world_mut().resource_mut::<::bevy_asset::io::embedded::EmbeddedAssetRegistry>();
        let path =
            {
                let crate_name = "bevy_pbr::ssao".split(':').next().unwrap();
                ::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
                    "src".as_ref(), "src/ssao/mod.rs".as_ref(),
                    "preprocess_depth.wesl".as_ref())
            };
        let watched_path =
            ::bevy_asset::io::embedded::watched_path("src/ssao/mod.rs",
                "preprocess_depth.wesl");
        embedded.insert_asset(watched_path, &path,
            b"//! Inputs a depth texture and outputs a MIP-chain of depths.\n//!\n//! Because SSAO\'s performance is bound by texture reads, this increases\n//! performance over using the full resolution depth for every sample.\n//!\n//! Reference: https://research.nvidia.com/sites/default/files/pubs/2012-06_Scalable-Ambient-Obscurance/McGuire12SAO.pdf, section 2.2\n\nimport bevy_render::view::View;\n\n@group(0) @binding(0) var input_depth: texture_depth_2d;\n@if(USE_R16FLOAT) {\n@group(0) @binding(1) var preprocessed_depth_mip0: texture_storage_2d<r16float, write>;\n@group(0) @binding(2) var preprocessed_depth_mip1: texture_storage_2d<r16float, write>;\n@group(0) @binding(3) var preprocessed_depth_mip2: texture_storage_2d<r16float, write>;\n@group(0) @binding(4) var preprocessed_depth_mip3: texture_storage_2d<r16float, write>;\n@group(0) @binding(5) var preprocessed_depth_mip4: texture_storage_2d<r16float, write>;\n}\n@if(!USE_R16FLOAT) {\n@group(0) @binding(1) var preprocessed_depth_mip0: texture_storage_2d<r32float, write>;\n@group(0) @binding(2) var preprocessed_depth_mip1: texture_storage_2d<r32float, write>;\n@group(0) @binding(3) var preprocessed_depth_mip2: texture_storage_2d<r32float, write>;\n@group(0) @binding(4) var preprocessed_depth_mip3: texture_storage_2d<r32float, write>;\n@group(0) @binding(5) var preprocessed_depth_mip4: texture_storage_2d<r32float, write>;\n}\n@group(1) @binding(0) var point_clamp_sampler: sampler;\n@group(1) @binding(1) var linear_clamp_sampler: sampler;\n@group(1) @binding(2) var<uniform> view: View;\n\n/// Using 4 depths from the previous MIP, compute a weighted average for the depth of the current MIP\nfn weighted_average(depth0: f32, depth1: f32, depth2: f32, depth3: f32) -> f32 {\n    let depth_range_scale_factor = 0.75;\n    let effect_radius = depth_range_scale_factor * 0.5 * 1.457;\n    let falloff_range = 0.615 * effect_radius;\n    let falloff_from = effect_radius * (1.0 - 0.615);\n    let falloff_mul = -1.0 / falloff_range;\n    let falloff_add = falloff_from / falloff_range + 1.0;\n\n    let min_depth = min(min(depth0, depth1), min(depth2, depth3));\n    let weight0 = saturate((depth0 - min_depth) * falloff_mul + falloff_add);\n    let weight1 = saturate((depth1 - min_depth) * falloff_mul + falloff_add);\n    let weight2 = saturate((depth2 - min_depth) * falloff_mul + falloff_add);\n    let weight3 = saturate((depth3 - min_depth) * falloff_mul + falloff_add);\n    let weight_total = weight0 + weight1 + weight2 + weight3;\n\n    return ((weight0 * depth0) + (weight1 * depth1) + (weight2 * depth2) + (weight3 * depth3)) / weight_total;\n}\n\n/// Used to share the depths from the previous MIP level between all invocations in a workgroup\nvar<workgroup> previous_mip_depth: array<array<f32, 8>, 8>;\n\n@compute\n@workgroup_size(8, 8, 1)\nfn preprocess_depth(@builtin(global_invocation_id) global_id: vec3<u32>, @builtin(local_invocation_id) local_id: vec3<u32>) {\n    let base_coordinates = vec2<i32>(global_id.xy);\n\n    // MIP 0 - Copy 4 texels from the input depth (per invocation, 8x8 invocations per workgroup)\n    let pixel_coordinates0 = base_coordinates * 2i;\n    let pixel_coordinates1 = pixel_coordinates0 + vec2<i32>(1i, 0i);\n    let pixel_coordinates2 = pixel_coordinates0 + vec2<i32>(0i, 1i);\n    let pixel_coordinates3 = pixel_coordinates0 + vec2<i32>(1i, 1i);\n    let input_depth_size = vec2<f32>(textureDimensions(input_depth));\n    let depths_uv = (vec2<f32>(pixel_coordinates0) + view.viewport.xy) / input_depth_size;\n    let depths = textureGather(input_depth, point_clamp_sampler, depths_uv, vec2<i32>(1i, 1i));\n    textureStore(preprocessed_depth_mip0, pixel_coordinates0, vec4<f32>(depths.w, 0.0, 0.0, 0.0));\n    textureStore(preprocessed_depth_mip0, pixel_coordinates1, vec4<f32>(depths.z, 0.0, 0.0, 0.0));\n    textureStore(preprocessed_depth_mip0, pixel_coordinates2, vec4<f32>(depths.x, 0.0, 0.0, 0.0));\n    textureStore(preprocessed_depth_mip0, pixel_coordinates3, vec4<f32>(depths.y, 0.0, 0.0, 0.0));\n\n    // MIP 1 - Weighted average of MIP 0\'s depth values (per invocation, 8x8 invocations per workgroup)\n    let depth_mip1 = weighted_average(depths.w, depths.z, depths.x, depths.y);\n    textureStore(preprocessed_depth_mip1, base_coordinates, vec4<f32>(depth_mip1, 0.0, 0.0, 0.0));\n    previous_mip_depth[local_id.x][local_id.y] = depth_mip1;\n\n    workgroupBarrier();\n\n    // MIP 2 - Weighted average of MIP 1\'s depth values (per invocation, 4x4 invocations per workgroup)\n    if all(local_id.xy % vec2<u32>(2u) == vec2<u32>(0u)) {\n        let depth0 = previous_mip_depth[local_id.x + 0u][local_id.y + 0u];\n        let depth1 = previous_mip_depth[local_id.x + 1u][local_id.y + 0u];\n        let depth2 = previous_mip_depth[local_id.x + 0u][local_id.y + 1u];\n        let depth3 = previous_mip_depth[local_id.x + 1u][local_id.y + 1u];\n        let depth_mip2 = weighted_average(depth0, depth1, depth2, depth3);\n        textureStore(preprocessed_depth_mip2, base_coordinates / 2i, vec4<f32>(depth_mip2, 0.0, 0.0, 0.0));\n        previous_mip_depth[local_id.x][local_id.y] = depth_mip2;\n    }\n\n    workgroupBarrier();\n\n    // MIP 3 - Weighted average of MIP 2\'s depth values (per invocation, 2x2 invocations per workgroup)\n    if all(local_id.xy % vec2<u32>(4u) == vec2<u32>(0u)) {\n        let depth0 = previous_mip_depth[local_id.x + 0u][local_id.y + 0u];\n        let depth1 = previous_mip_depth[local_id.x + 2u][local_id.y + 0u];\n        let depth2 = previous_mip_depth[local_id.x + 0u][local_id.y + 2u];\n        let depth3 = previous_mip_depth[local_id.x + 2u][local_id.y + 2u];\n        let depth_mip3 = weighted_average(depth0, depth1, depth2, depth3);\n        textureStore(preprocessed_depth_mip3, base_coordinates / 4i, vec4<f32>(depth_mip3, 0.0, 0.0, 0.0));\n        previous_mip_depth[local_id.x][local_id.y] = depth_mip3;\n    }\n\n    workgroupBarrier();\n\n    // MIP 4 - Weighted average of MIP 3\'s depth values (per invocation, 1 invocation per workgroup)\n    if all(local_id.xy % vec2<u32>(8u) == vec2<u32>(0u)) {\n        let depth0 = previous_mip_depth[local_id.x + 0u][local_id.y + 0u];\n        let depth1 = previous_mip_depth[local_id.x + 4u][local_id.y + 0u];\n        let depth2 = previous_mip_depth[local_id.x + 0u][local_id.y + 4u];\n        let depth3 = previous_mip_depth[local_id.x + 4u][local_id.y + 4u];\n        let depth_mip4 = weighted_average(depth0, depth1, depth2, depth3);\n        textureStore(preprocessed_depth_mip4, base_coordinates / 8i, vec4<f32>(depth_mip4, 0.0, 0.0, 0.0));\n    }\n}\n");
    }
};embedded_asset!(app, "preprocess_depth.wesl");
50        {
    {
        let mut embedded =
            app.world_mut().resource_mut::<::bevy_asset::io::embedded::EmbeddedAssetRegistry>();
        let path =
            {
                let crate_name = "bevy_pbr::ssao".split(':').next().unwrap();
                ::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
                    "src".as_ref(), "src/ssao/mod.rs".as_ref(),
                    "ssao.wesl".as_ref())
            };
        let watched_path =
            ::bevy_asset::io::embedded::watched_path("src/ssao/mod.rs",
                "ssao.wesl");
        embedded.insert_asset(watched_path, &path,
            b"//! Visibility Bitmask Ambient Occlusion (VBAO)\n//! Paper: ttps://ar5iv.labs.arxiv.org/html/2301.11376\n//!\n//! Source code heavily based on XeGTAO v1.30 from Intel\n//! https://github.com/GameTechDev/XeGTAO/blob/0d177ce06bfa642f64d8af4de1197ad1bcb862d4/Source/Rendering/Shaders/XeGTAO.hlsli\n//!\n//! Source code based on the existing XeGTAO implementation and\n//! https://cdrinmatane.github.io/posts/ssaovb-code/\n//!\n//! Source code base on SSRT3 implementation\n//! https://github.com/cdrinmatane/SSRT3\n\nimport bevy_render::maths::fast_acos;\n\nimport bevy_render::{\n    view::View,\n    globals::Globals,\n    maths::{PI, HALF_PI},\n};\n\n@group(0) @binding(0) var preprocessed_depth: texture_2d<f32>;\n@group(0) @binding(1) var normals: texture_2d<f32>;\n@group(0) @binding(2) var hilbert_index_lut: texture_2d<u32>;\n@if(USE_R16FLOAT)\n@group(0) @binding(3) var ambient_occlusion: texture_storage_2d<r16float, write>;\n@else\n@group(0) @binding(3) var ambient_occlusion: texture_storage_2d<r32float, write>;\n@group(0) @binding(4) var depth_differences: texture_storage_2d<r32uint, write>;\n@group(0) @binding(5) var<uniform> globals: Globals;\nstruct SsaoSettings {\n    radius: f32,\n    thickness: f32,\n}\n@group(0) @binding(6) var<uniform> settings: SsaoSettings;\n@group(1) @binding(0) var point_clamp_sampler: sampler;\n@group(1) @binding(1) var linear_clamp_sampler: sampler;\n@group(1) @binding(2) var<uniform> view: View;\n\nfn load_noise(pixel_coordinates: vec2<i32>) -> vec2<f32> {\n    var index = textureLoad(hilbert_index_lut, pixel_coordinates % 64, 0).r;\n\n@if(TEMPORAL_JITTER)\n    index += 288u * (globals.frame_count % 64u);\n\n    // R2 sequence - http://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences\n    return fract(0.5 + f32(index) * vec2<f32>(0.75487766624669276005, 0.5698402909980532659114));\n}\n\n/// Calculate differences in depth between neighbor pixels (later used by the spatial denoiser pass to preserve object edges)\nfn calculate_neighboring_depth_differences(pixel_coordinates: vec2<i32>) -> f32 {\n    // Sample the pixel\'s depth and 4 depths around it\n    let uv = vec2<f32>(pixel_coordinates) / view.viewport.zw;\n    let depths_upper_left = textureGather(0, preprocessed_depth, point_clamp_sampler, uv);\n    let depths_bottom_right = textureGather(0, preprocessed_depth, point_clamp_sampler, uv, vec2<i32>(1i, 1i));\n    let depth_center = depths_upper_left.y;\n    let depth_left = depths_upper_left.x;\n    let depth_top = depths_upper_left.z;\n    let depth_bottom = depths_bottom_right.x;\n    let depth_right = depths_bottom_right.z;\n\n    // Calculate the depth differences (large differences represent object edges)\n    var edge_info = vec4<f32>(depth_left, depth_right, depth_top, depth_bottom) - depth_center;\n    let slope_left_right = (edge_info.y - edge_info.x) * 0.5;\n    let slope_top_bottom = (edge_info.w - edge_info.z) * 0.5;\n    let edge_info_slope_adjusted = edge_info + vec4<f32>(slope_left_right, -slope_left_right, slope_top_bottom, -slope_top_bottom);\n    edge_info = min(abs(edge_info), abs(edge_info_slope_adjusted));\n    let bias = 0.25; // Using the bias and then saturating nudges the values a bit\n    let scale = depth_center * 0.011; // Weight the edges by their distance from the camera\n    edge_info = saturate((1.0 + bias) - edge_info / scale); // Apply the bias and scale, and invert edge_info so that small values become large, and vice versa\n\n    // Pack the edge info into the texture\n    let edge_info_packed = vec4<u32>(pack4x8unorm(edge_info), 0u, 0u, 0u);\n    textureStore(depth_differences, pixel_coordinates, edge_info_packed);\n\n    return depth_center;\n}\n\nfn load_normal_view_space(uv_normals: vec2<f32>) -> vec3<f32> {\n    var world_normal = textureSampleLevel(normals, point_clamp_sampler, uv_normals, 0.0).xyz;\n    world_normal = (world_normal * 2.0) - 1.0;\n    let view_from_world = mat3x3<f32>(\n        view.view_from_world[0].xyz,\n        view.view_from_world[1].xyz,\n        view.view_from_world[2].xyz,\n    );\n    return view_from_world * world_normal;\n}\n\nfn reconstruct_view_space_position(depth: f32, uv: vec2<f32>) -> vec3<f32> {\n    let clip_xy = vec2<f32>(uv.x * 2.0 - 1.0, 1.0 - 2.0 * uv.y);\n    let t = view.view_from_clip * vec4<f32>(clip_xy, depth, 1.0);\n    let view_xyz = t.xyz / t.w;\n    return view_xyz;\n}\n\nfn load_and_reconstruct_view_space_position(uv: vec2<f32>, sample_mip_level: f32) -> vec3<f32> {\n    let depth = textureSampleLevel(preprocessed_depth, linear_clamp_sampler, uv, sample_mip_level).r;\n    return reconstruct_view_space_position(depth, uv);\n}\n\nfn updateSectors(\n    min_horizon: f32,\n    max_horizon: f32,\n    samples_per_slice: f32,\n    bitmask: u32,\n) -> u32 {\n    let start_horizon = u32(min_horizon * samples_per_slice);\n    let angle_horizon = u32(ceil((max_horizon - min_horizon) * samples_per_slice));\n\n    return insertBits(bitmask, 0xFFFFFFFFu, start_horizon, angle_horizon);\n}\n\nfn processSample(\n    delta_position: vec3<f32>,\n    view_vec: vec3<f32>,\n    sampling_direction: f32,\n    n: vec2<f32>,\n    samples_per_slice: f32,\n    bitmask: ptr<function, u32>,\n) {\n    let delta_position_back_face = delta_position - view_vec * settings.thickness;\n\n    var front_back_horizon = vec2(\n        fast_acos(dot(normalize(delta_position), view_vec)),\n        fast_acos(dot(normalize(delta_position_back_face), view_vec)),\n    );\n\n    front_back_horizon = saturate(fma(vec2(sampling_direction), -front_back_horizon * (1.0 / PI), n));\n    front_back_horizon = select(front_back_horizon.xy, front_back_horizon.yx, sampling_direction >= 0.0);\n\n    *bitmask = updateSectors(front_back_horizon.x, front_back_horizon.y, samples_per_slice, *bitmask);\n}\n\n@compute\n@workgroup_size(8, 8, 1)\nfn ssao(@builtin(global_invocation_id) global_id: vec3<u32>) {\n    let slice_count = f32(constants::SLICE_COUNT);\n    let samples_per_slice_side = f32(constants::SAMPLES_PER_SLICE_SIDE);\n\n    let pixel_coordinates = vec2<i32>(global_id.xy);\n    let uv = (vec2<f32>(pixel_coordinates) + 0.5) / view.viewport.zw;\n\n    var pixel_depth = calculate_neighboring_depth_differences(pixel_coordinates);\n    pixel_depth += 0.00001; // Avoid depth precision issues\n\n    let pixel_position = reconstruct_view_space_position(pixel_depth, uv);\n    let normals_size = vec2<f32>(textureDimensions(normals));\n    let uv_normals = (vec2<f32>(pixel_coordinates) + 0.5 + view.viewport.xy) / normals_size;\n    let pixel_normal = load_normal_view_space(uv_normals);\n    let view_vec = normalize(-pixel_position);\n\n    let noise = load_noise(pixel_coordinates);\n    let safe_radius = max(settings.radius, 0.0001);\n    let sample_scale = (-0.5 * safe_radius * view.clip_from_view[0][0]) / pixel_position.z;\n\n    var visibility = 0.0;\n    var occluded_sample_count = 0u;\n    for (var slice_t = 0.0; slice_t < slice_count; slice_t += 1.0) {\n        let slice = slice_t + noise.x;\n        let phi = (PI / slice_count) * slice;\n        let omega = vec2<f32>(cos(phi), sin(phi));\n\n        let direction = vec3<f32>(omega.xy, 0.0);\n        let orthographic_direction = direction - (dot(direction, view_vec) * view_vec);\n        let axis = cross(direction, view_vec);\n        let projected_normal = pixel_normal - axis * dot(pixel_normal, axis);\n        let projected_normal_length = length(projected_normal);\n\n        let sign_norm = sign(dot(orthographic_direction, projected_normal));\n        let cos_norm = saturate(dot(projected_normal, view_vec) / projected_normal_length);\n        let n = vec2((HALF_PI - sign_norm * fast_acos(cos_norm)) * (1.0 / PI));\n\n        var bitmask = 0u;\n\n        let sample_mul = vec2<f32>(omega.x, -omega.y) * sample_scale;\n        for (var sample_t = 0.0; sample_t < samples_per_slice_side; sample_t += 1.0) {\n            var sample_noise = (slice_t + sample_t * samples_per_slice_side) * 0.6180339887498948482;\n            sample_noise = fract(noise.y + sample_noise);\n\n            var s = (sample_t + sample_noise) / samples_per_slice_side;\n            s *= s; // https://github.com/GameTechDev/XeGTAO#sample-distribution\n            let sample = s * sample_mul;\n\n            // * view.viewport.zw gets us from [0, 1] to [0, viewport_size], which is needed for this to get the correct mip levels\n            let sample_mip_level = clamp(log2(length(sample * view.viewport.zw)) - 3.3, 0.0, 5.0); // https://github.com/GameTechDev/XeGTAO#memory-bandwidth-bottleneck\n            let sample_position_1 = load_and_reconstruct_view_space_position(uv + sample, sample_mip_level);\n            let sample_position_2 = load_and_reconstruct_view_space_position(uv - sample, sample_mip_level);\n\n            let sample_difference_1 = sample_position_1 - pixel_position;\n            let sample_difference_2 = sample_position_2 - pixel_position;\n\n            processSample(sample_difference_1, view_vec, -1.0, n, samples_per_slice_side * 2.0, &bitmask);\n            processSample(sample_difference_2, view_vec, 1.0, n, samples_per_slice_side * 2.0, &bitmask);\n        }\n\n        occluded_sample_count += countOneBits(bitmask);\n    }\n\n    visibility = 1.0 - f32(occluded_sample_count) / (slice_count * 2.0 * samples_per_slice_side);\n\n    visibility = clamp(visibility, 0.03, 1.0);\n\n    textureStore(ambient_occlusion, pixel_coordinates, vec4<f32>(visibility, 0.0, 0.0, 0.0));\n}\n");
    }
};embedded_asset!(app, "ssao.wesl");
51        {
    {
        let mut embedded =
            app.world_mut().resource_mut::<::bevy_asset::io::embedded::EmbeddedAssetRegistry>();
        let path =
            {
                let crate_name = "bevy_pbr::ssao".split(':').next().unwrap();
                ::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
                    "src".as_ref(), "src/ssao/mod.rs".as_ref(),
                    "spatial_denoise.wesl".as_ref())
            };
        let watched_path =
            ::bevy_asset::io::embedded::watched_path("src/ssao/mod.rs",
                "spatial_denoise.wesl");
        embedded.insert_asset(watched_path, &path,
            b"//! 3x3 bilaterial filter (edge-preserving blur)\n//! https://people.csail.mit.edu/sparis/bf_course/course_notes.pdf\n//!\n//! Note: Does not use the Gaussian kernel part of a typical bilateral blur\n//! From the paper: \"use the information gathered on a neighborhood of 4 \xc3\x97 4 using a bilateral filter for\n//! reconstruction, using _uniform_ convolution weights\"\n//!\n//! Note: The paper does a 4x4 (not quite centered) filter, offset by +/- 1 pixel every other frame\n//! XeGTAO does a 3x3 filter, on two pixels at a time per compute thread, applied twice\n//! We do a 3x3 filter, on 1 pixel per compute thread, applied once\n\nimport bevy_render::view::View;\n\n@group(0) @binding(0) var ambient_occlusion_noisy: texture_2d<f32>;\n@group(0) @binding(1) var depth_differences: texture_2d<u32>;\n@if(USE_R16FLOAT)\n@group(0) @binding(2) var ambient_occlusion: texture_storage_2d<r16float, write>;\n@else\n@group(0) @binding(2) var ambient_occlusion: texture_storage_2d<r32float, write>;\n@group(1) @binding(0) var point_clamp_sampler: sampler;\n@group(1) @binding(1) var linear_clamp_sampler: sampler;\n@group(1) @binding(2) var<uniform> view: View;\n\n@compute\n@workgroup_size(8, 8, 1)\nfn spatial_denoise(@builtin(global_invocation_id) global_id: vec3<u32>) {\n    let pixel_coordinates = vec2<i32>(global_id.xy);\n    let uv = vec2<f32>(pixel_coordinates) / view.viewport.zw;\n\n    let edges0 = textureGather(0, depth_differences, point_clamp_sampler, uv);\n    let edges1 = textureGather(0, depth_differences, point_clamp_sampler, uv, vec2<i32>(2i, 0i));\n    let edges2 = textureGather(0, depth_differences, point_clamp_sampler, uv, vec2<i32>(1i, 2i));\n    let visibility0 = textureGather(0, ambient_occlusion_noisy, point_clamp_sampler, uv);\n    let visibility1 = textureGather(0, ambient_occlusion_noisy, point_clamp_sampler, uv, vec2<i32>(2i, 0i));\n    let visibility2 = textureGather(0, ambient_occlusion_noisy, point_clamp_sampler, uv, vec2<i32>(0i, 2i));\n    let visibility3 = textureGather(0, ambient_occlusion_noisy, point_clamp_sampler, uv, vec2<i32>(2i, 2i));\n\n    let left_edges = unpack4x8unorm(edges0.x);\n    let right_edges = unpack4x8unorm(edges1.x);\n    let top_edges = unpack4x8unorm(edges0.z);\n    let bottom_edges = unpack4x8unorm(edges2.w);\n    var center_edges = unpack4x8unorm(edges0.y);\n    center_edges *= vec4<f32>(left_edges.y, right_edges.x, top_edges.w, bottom_edges.z);\n\n    let center_weight = 1.2;\n    let left_weight = center_edges.x;\n    let right_weight = center_edges.y;\n    let top_weight = center_edges.z;\n    let bottom_weight = center_edges.w;\n    let top_left_weight = 0.425 * (top_weight * top_edges.x + left_weight * left_edges.z);\n    let top_right_weight = 0.425 * (top_weight * top_edges.y + right_weight * right_edges.z);\n    let bottom_left_weight = 0.425 * (bottom_weight * bottom_edges.x + left_weight * left_edges.w);\n    let bottom_right_weight = 0.425 * (bottom_weight * bottom_edges.y + right_weight * right_edges.w);\n\n    let center_visibility = visibility0.y;\n    let left_visibility = visibility0.x;\n    let right_visibility = visibility1.x;\n    let top_visibility = visibility0.z;\n    let bottom_visibility = visibility2.z;\n    let top_left_visibility = visibility0.w;\n    let top_right_visibility = visibility1.w;\n    let bottom_left_visibility = visibility2.w;\n    let bottom_right_visibility = visibility3.w;\n\n    var sum = center_visibility * center_weight;\n    sum += left_visibility * left_weight;\n    sum += right_visibility * right_weight;\n    sum += top_visibility * top_weight;\n    sum += bottom_visibility * bottom_weight;\n    sum += top_left_visibility * top_left_weight;\n    sum += top_right_visibility * top_right_weight;\n    sum += bottom_left_visibility * bottom_left_weight;\n    sum += bottom_right_visibility * bottom_right_weight;\n\n    var sum_weight = center_weight;\n    sum_weight += left_weight;\n    sum_weight += right_weight;\n    sum_weight += top_weight;\n    sum_weight += bottom_weight;\n    sum_weight += top_left_weight;\n    sum_weight += top_right_weight;\n    sum_weight += bottom_left_weight;\n    sum_weight += bottom_right_weight;\n\n    let denoised_visibility = sum / sum_weight;\n\n    textureStore(ambient_occlusion, pixel_coordinates, vec4<f32>(denoised_visibility, 0.0, 0.0, 0.0));\n}\n");
    }
};embedded_asset!(app, "spatial_denoise.wesl");
52
53        app.add_plugins(SyncComponentPlugin::<ScreenSpaceAmbientOcclusion>::default());
54    }
55
56    fn finish(&self, app: &mut App) {
57        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
58            return;
59        };
60
61        if render_app
62            .world()
63            .resource::<RenderDevice>()
64            .limits()
65            .max_storage_textures_per_shader_stage
66            < 5
67        {
68            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event src/ssao/mod.rs:68",
                        "bevy_pbr::ssao", ::tracing::Level::WARN,
                        ::tracing_core::__macro_support::Option::Some("src/ssao/mod.rs"),
                        ::tracing_core::__macro_support::Option::Some(68u32),
                        ::tracing_core::__macro_support::Option::Some("bevy_pbr::ssao"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::WARN <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::WARN <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("ScreenSpaceAmbientOcclusionPlugin not loaded. GPU lacks support: Limits::max_storage_textures_per_shader_stage is less than 5.")
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};warn!("ScreenSpaceAmbientOcclusionPlugin not loaded. GPU lacks support: Limits::max_storage_textures_per_shader_stage is less than 5.");
69            return;
70        }
71
72        render_app
73            .init_gpu_resource::<SsaoPipelines>()
74            .init_gpu_resource::<SpecializedComputePipelines<SsaoPipelines>>()
75            .add_systems(ExtractSchedule, extract_ssao_settings)
76            .add_systems(
77                Render,
78                (
79                    prepare_ssao_pipelines.in_set(RenderSystems::Prepare),
80                    prepare_ssao_textures.in_set(RenderSystems::PrepareResources),
81                    prepare_ssao_bind_groups.in_set(RenderSystems::PrepareBindGroups),
82                ),
83            );
84
85        render_app.add_systems(
86            Core3d,
87            ssao.after(Core3dSystems::Prepass)
88                .before(Core3dSystems::MainPass),
89        );
90    }
91}
92
93/// Component to apply screen space ambient occlusion to a 3d camera.
94///
95/// Screen space ambient occlusion (SSAO) approximates small-scale,
96/// local occlusion of _indirect_ diffuse light between objects, based on what's visible on-screen.
97/// SSAO does not apply to direct lighting, such as point or directional lights.
98///
99/// This darkens creases, e.g. on staircases, and gives nice contact shadows
100/// where objects meet, giving entities a more "grounded" feel.
101///
102/// # Usage Notes
103///
104/// Requires that you add [`ScreenSpaceAmbientOcclusionPlugin`] to your app.
105///
106/// It strongly recommended that you use SSAO in conjunction with
107/// TAA (`TemporalAntiAliasing`).
108/// Doing so greatly reduces SSAO noise.
109///
110/// SSAO is not supported on `WebGL2`, and is not currently supported on `WebGPU`.
111#[derive(#[doc =
"**Required Components**: [`DepthPrepass`], [`NormalPrepass`]. \n\n A component's Required Components are inserted whenever it is inserted. Note that this will also insert the required components _of_ the required components, recursively, in depth-first order."]
impl bevy_ecs::component::Component for ScreenSpaceAmbientOcclusion where
    Self: ::core::marker::Send + ::core::marker::Sync + 'static {
    const STORAGE_TYPE: bevy_ecs::component::StorageType =
        bevy_ecs::component::StorageType::Table;
    type Mutability = bevy_ecs::component::Mutable;
    fn register_required_components(_requiree:
            bevy_ecs::component::ComponentId,
        required_components:
            &mut bevy_ecs::component::RequiredComponentsRegistrator) {
        required_components.register_required::<DepthPrepass>(<DepthPrepass as
                ::core::default::Default>::default);
        required_components.register_required::<NormalPrepass>(<NormalPrepass
                as ::core::default::Default>::default);
    }
    fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
        use bevy_ecs::component::{
            DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
        };
        (&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
    }
    fn relationship_accessor()
        ->
            ::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
        ::core::option::Option::None
    }
}Component, impl bevy_extract::sync_component::SyncComponent<RenderApp> for
    ScreenSpaceAmbientOcclusion where Self: ::core::clone::Clone {
    type Target =
        (Self, ScreenSpaceAmbientOcclusionResources, SsaoPipelineId,
        SsaoBindGroups);
}
impl bevy_extract::extract_component::ExtractComponent<RenderApp> for
    ScreenSpaceAmbientOcclusion where Self: ::core::clone::Clone {
    type QueryData = &'static Self;
    type QueryFilter = ();
    type Out = Self;
    fn extract_component(item:
            bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>)
        -> ::core::option::Option<Self::Out> {
        ::core::option::Option::Some(item.clone())
    }
}ExtractComponent, const _: () =
    {
        impl bevy_reflect::GetTypeRegistration for ScreenSpaceAmbientOcclusion
            where  {
            fn get_type_registration() -> bevy_reflect::TypeRegistration {
                let mut registration =
                    bevy_reflect::TypeRegistration::of::<Self>();
                registration.insert(<bevy_reflect::ReflectFromPtr as
                            bevy_reflect::CreateTypeData<Self>>::create_type_data(()));
                registration.insert(<bevy_reflect::ReflectFromReflect as
                            bevy_reflect::CreateTypeData<Self>>::create_type_data(()));
                registration.register_type_data_with::<ReflectComponent, Self,
                    _>(());
                registration.register_type_data_with::<ReflectDefault, Self,
                    _>(());
                registration
            }
            #[inline(never)]
            fn register_type_dependencies(registry:
                    &mut bevy_reflect::TypeRegistry) {
                <ScreenSpaceAmbientOcclusionQualityLevel as
                        bevy_reflect::__macro_exports::RegisterForReflection>::__register(registry);
                <f32 as
                        bevy_reflect::__macro_exports::RegisterForReflection>::__register(registry);
            }
        }
        impl bevy_reflect::Typed for ScreenSpaceAmbientOcclusion where  {
            #[inline(never)]
            fn type_info() -> &'static bevy_reflect::TypeInfo {
                static CELL: bevy_reflect::utility::NonGenericTypeInfoCell =
                    bevy_reflect::utility::NonGenericTypeInfoCell::new();
                CELL.get_or_set(#[inline(never)] ||
                        {
                            bevy_reflect::TypeInfo::Struct(bevy_reflect::structs::StructInfo::new::<Self>(&[bevy_reflect::NamedField::new::<ScreenSpaceAmbientOcclusionQualityLevel>("quality_level"),
                                                bevy_reflect::NamedField::new::<f32>("radius"),
                                                bevy_reflect::NamedField::new::<f32>("constant_object_thickness")]))
                        })
            }
        }
        #[allow(deprecated, reason =
        "derives on a deprecated type shouldn't be considered a usage")]
        impl bevy_reflect::TypePath for ScreenSpaceAmbientOcclusion where  {
            fn type_path() -> &'static str {
                "bevy_pbr::ssao::ScreenSpaceAmbientOcclusion"
            }
            fn short_type_path() -> &'static str {
                "ScreenSpaceAmbientOcclusion"
            }
            fn type_ident() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("ScreenSpaceAmbientOcclusion")
            }
            fn crate_name() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("bevy_pbr::ssao".split(':').next().unwrap())
            }
            fn module_path() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("bevy_pbr::ssao")
            }
        }
        impl bevy_reflect::Reflect for ScreenSpaceAmbientOcclusion where  {
            #[inline]
            fn into_any(self:
                    bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
                ->
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn ::core::any::Any> {
                self
            }
            #[inline]
            fn as_any(&self) -> &dyn ::core::any::Any { self }
            #[inline]
            fn as_any_mut(&mut self) -> &mut dyn ::core::any::Any { self }
            #[inline]
            fn into_reflect(self:
                    bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
                ->
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect> {
                self
            }
            #[inline]
            fn as_reflect(&self) -> &dyn bevy_reflect::Reflect { self }
            #[inline]
            fn as_reflect_mut(&mut self) -> &mut dyn bevy_reflect::Reflect {
                self
            }
            #[inline]
            fn set(&mut self,
                value:
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>)
                ->
                    ::core::result::Result<(),
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>> {
                *self = <dyn bevy_reflect::Reflect>::take(value)?;
                ::core::result::Result::Ok(())
            }
        }
        #[allow(non_upper_case_globals)]
        const _: () =
            {
                static __INVENTORY: ::inventory::Node =
                    ::inventory::Node {
                        value: &{
                                bevy_reflect::__macro_exports::auto_register::AutomaticReflectRegistrations(<ScreenSpaceAmbientOcclusion
                                        as
                                        bevy_reflect::__macro_exports::auto_register::RegisterForReflection>::__register)
                            },
                        next: ::inventory::__private::UnsafeCell::new(::inventory::__private::Option::None),
                    };
                #[link_section = ".text.startup"]
                unsafe extern "C" fn __ctor() {
                    unsafe {
                        ::inventory::ErasedNode::submit(__INVENTORY.value,
                            &__INVENTORY)
                    }
                }
                #[used]
                #[link_section = ".init_array"]
                static __CTOR: unsafe extern "C" fn() = __ctor;
            };
        impl bevy_reflect::structs::Struct for ScreenSpaceAmbientOcclusion
            where  {
            fn field(&self, name: &str)
                -> ::core::option::Option<&dyn bevy_reflect::PartialReflect> {
                match name {
                    "quality_level" =>
                        ::core::option::Option::Some(&self.quality_level),
                    "radius" => ::core::option::Option::Some(&self.radius),
                    "constant_object_thickness" =>
                        ::core::option::Option::Some(&self.constant_object_thickness),
                    _ => ::core::option::Option::None,
                }
            }
            fn field_mut(&mut self, name: &str)
                ->
                    ::core::option::Option<&mut dyn bevy_reflect::PartialReflect> {
                match name {
                    "quality_level" =>
                        ::core::option::Option::Some(&mut self.quality_level),
                    "radius" => ::core::option::Option::Some(&mut self.radius),
                    "constant_object_thickness" =>
                        ::core::option::Option::Some(&mut self.constant_object_thickness),
                    _ => ::core::option::Option::None,
                }
            }
            fn field_at(&self, index: usize)
                -> ::core::option::Option<&dyn bevy_reflect::PartialReflect> {
                match index {
                    0usize => ::core::option::Option::Some(&self.quality_level),
                    1usize => ::core::option::Option::Some(&self.radius),
                    2usize =>
                        ::core::option::Option::Some(&self.constant_object_thickness),
                    _ => ::core::option::Option::None,
                }
            }
            fn field_at_mut(&mut self, index: usize)
                ->
                    ::core::option::Option<&mut dyn bevy_reflect::PartialReflect> {
                match index {
                    0usize =>
                        ::core::option::Option::Some(&mut self.quality_level),
                    1usize => ::core::option::Option::Some(&mut self.radius),
                    2usize =>
                        ::core::option::Option::Some(&mut self.constant_object_thickness),
                    _ => ::core::option::Option::None,
                }
            }
            fn name_at(&self, index: usize) -> ::core::option::Option<&str> {
                match index {
                    0usize => ::core::option::Option::Some("quality_level"),
                    1usize => ::core::option::Option::Some("radius"),
                    2usize =>
                        ::core::option::Option::Some("constant_object_thickness"),
                    _ => ::core::option::Option::None,
                }
            }
            fn index_of_name(&self, name: &str)
                -> ::core::option::Option<usize> {
                match name {
                    "quality_level" => ::core::option::Option::Some(0usize),
                    "radius" => ::core::option::Option::Some(1usize),
                    "constant_object_thickness" =>
                        ::core::option::Option::Some(2usize),
                    _ => ::core::option::Option::None,
                }
            }
            fn field_len(&self) -> usize { 3usize }
            fn iter_fields(&self) -> bevy_reflect::structs::FieldIter {
                bevy_reflect::structs::FieldIter::new(self)
            }
            fn to_dynamic_struct(&self)
                ->
                    ::core::result::Result<bevy_reflect::structs::DynamicStruct,
                    bevy_reflect::ReflectCloneError> {
                let mut dynamic: bevy_reflect::structs::DynamicStruct =
                    ::core::default::Default::default();
                dynamic.set_represented_type(bevy_reflect::PartialReflect::get_represented_type_info(self));
                dynamic.insert_boxed("quality_level",
                    bevy_reflect::PartialReflect::to_dynamic(&self.quality_level)?);
                dynamic.insert_boxed("radius",
                    bevy_reflect::PartialReflect::to_dynamic(&self.radius)?);
                dynamic.insert_boxed("constant_object_thickness",
                    bevy_reflect::PartialReflect::to_dynamic(&self.constant_object_thickness)?);
                ::core::result::Result::Ok(dynamic)
            }
        }
        impl bevy_reflect::PartialReflect for ScreenSpaceAmbientOcclusion
            where  {
            #[inline]
            fn get_represented_type_info(&self)
                -> ::core::option::Option<&'static bevy_reflect::TypeInfo> {
                ::core::option::Option::Some(<Self as
                            bevy_reflect::Typed>::type_info())
            }
            #[inline]
            fn try_apply(&mut self, value: &dyn bevy_reflect::PartialReflect)
                -> ::core::result::Result<(), bevy_reflect::ApplyError> {
                if let bevy_reflect::ReflectRef::Struct(struct_value) =
                        bevy_reflect::PartialReflect::reflect_ref(value) {
                    for (name, value) in
                        bevy_reflect::structs::Struct::iter_fields(struct_value) {
                        if let ::core::option::Option::Some(v) =
                                bevy_reflect::structs::Struct::field_mut(self, name) {
                            bevy_reflect::PartialReflect::try_apply(v, value)?;
                        }
                    }
                } else {
                    return ::core::result::Result::Err(bevy_reflect::ApplyError::MismatchedKinds {
                                from_kind: bevy_reflect::PartialReflect::reflect_kind(value),
                                to_kind: bevy_reflect::ReflectKind::Struct,
                            });
                }
                ::core::result::Result::Ok(())
            }
            #[inline]
            fn reflect_kind(&self) -> bevy_reflect::ReflectKind {
                bevy_reflect::ReflectKind::Struct
            }
            #[inline]
            fn reflect_ref(&self) -> bevy_reflect::ReflectRef {
                bevy_reflect::ReflectRef::Struct(self)
            }
            #[inline]
            fn reflect_mut(&mut self) -> bevy_reflect::ReflectMut {
                bevy_reflect::ReflectMut::Struct(self)
            }
            #[inline]
            fn reflect_owned(self:
                    bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
                -> bevy_reflect::ReflectOwned {
                bevy_reflect::ReflectOwned::Struct(self)
            }
            #[inline]
            fn try_into_reflect(self:
                    bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
                ->
                    ::core::result::Result<bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>,
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::PartialReflect>> {
                ::core::result::Result::Ok(self)
            }
            #[inline]
            fn try_as_reflect(&self)
                -> ::core::option::Option<&dyn bevy_reflect::Reflect> {
                ::core::option::Option::Some(self)
            }
            #[inline]
            fn try_as_reflect_mut(&mut self)
                -> ::core::option::Option<&mut dyn bevy_reflect::Reflect> {
                ::core::option::Option::Some(self)
            }
            #[inline]
            fn into_partial_reflect(self:
                    bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
                ->
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::PartialReflect> {
                self
            }
            #[inline]
            fn as_partial_reflect(&self)
                -> &dyn bevy_reflect::PartialReflect {
                self
            }
            #[inline]
            fn as_partial_reflect_mut(&mut self)
                -> &mut dyn bevy_reflect::PartialReflect {
                self
            }
            fn reflect_partial_eq(&self,
                value: &dyn bevy_reflect::PartialReflect)
                -> ::core::option::Option<bool> {
                let value =
                    <dyn bevy_reflect::PartialReflect>::try_downcast_ref::<Self>(value);
                if let ::core::option::Option::Some(value) = value {
                    ::core::option::Option::Some(::core::cmp::PartialEq::eq(self,
                            value))
                } else { ::core::option::Option::Some(false) }
            }
            fn reflect_partial_cmp(&self,
                value: &dyn bevy_reflect::PartialReflect)
                -> ::core::option::Option<::core::cmp::Ordering> {
                (bevy_reflect::structs::struct_partial_cmp)(self, value)
            }
            fn debug(&self, f: &mut ::core::fmt::Formatter<'_>)
                -> ::core::fmt::Result {
                ::core::fmt::Debug::fmt(self, f)
            }
            #[inline]
            fn reflect_clone(&self)
                ->
                    ::core::result::Result<bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>,
                    bevy_reflect::ReflectCloneError> {
                ::core::result::Result::Ok(bevy_reflect::__macro_exports::alloc_utils::Box::new(::core::clone::Clone::clone(self)))
            }
        }
        impl bevy_reflect::FromReflect for ScreenSpaceAmbientOcclusion where
            {
            fn from_reflect(reflect: &dyn bevy_reflect::PartialReflect)
                -> ::core::option::Option<Self> {
                if let bevy_reflect::ReflectRef::Struct(__ref_struct) =
                        bevy_reflect::PartialReflect::reflect_ref(reflect) {
                    let mut __this =
                        <Self as ::core::default::Default>::default();
                    if let ::core::option::Option::Some(__field) =
                            (||
                                        <ScreenSpaceAmbientOcclusionQualityLevel as
                                                bevy_reflect::FromReflect>::from_reflect(bevy_reflect::structs::Struct::field(__ref_struct,
                                                    "quality_level")?))() {
                        __this.quality_level = __field;
                    }
                    if let ::core::option::Option::Some(__field) =
                            (||
                                        <f32 as
                                                bevy_reflect::FromReflect>::from_reflect(bevy_reflect::structs::Struct::field(__ref_struct,
                                                    "radius")?))() {
                        __this.radius = __field;
                    }
                    if let ::core::option::Option::Some(__field) =
                            (||
                                        <f32 as
                                                bevy_reflect::FromReflect>::from_reflect(bevy_reflect::structs::Struct::field(__ref_struct,
                                                    "constant_object_thickness")?))() {
                        __this.constant_object_thickness = __field;
                    }
                    ::core::option::Option::Some(__this)
                } else { ::core::option::Option::None }
            }
        }
    };Reflect, #[automatically_derived]impl ::core::marker::StructuralPartialEq for
    ScreenSpaceAmbientOcclusion {
}
#[automatically_derived]impl ::core::cmp::PartialEq for
    ScreenSpaceAmbientOcclusion {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.radius == other.radius &&
                self.constant_object_thickness ==
                    other.constant_object_thickness &&
            self.quality_level == other.quality_level
    }
}PartialEq, #[automatically_derived]impl ::core::clone::Clone for
    ScreenSpaceAmbientOcclusion {
    #[inline]
    fn clone(&self) -> Self {
        ScreenSpaceAmbientOcclusion {
            quality_level: ::core::clone::Clone::clone(&self.quality_level),
            radius: ::core::clone::Clone::clone(&self.radius),
            constant_object_thickness: ::core::clone::Clone::clone(&self.constant_object_thickness),
        }
    }
}Clone, #[automatically_derived]impl ::core::fmt::Debug for
    ScreenSpaceAmbientOcclusion {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f,
            "ScreenSpaceAmbientOcclusion", "quality_level",
            &self.quality_level, "radius", &self.radius,
            "constant_object_thickness", &&self.constant_object_thickness)
    }
}Debug)]
112#[reflect(Component, Debug, Default, PartialEq, Clone)]
113#[require(DepthPrepass, NormalPrepass)]
114#[extract_component_sync_target((Self, ScreenSpaceAmbientOcclusionResources, SsaoPipelineId, SsaoBindGroups))]
115#[doc(alias = "Ssao")]
116#[extract_app(RenderApp)]
117pub struct ScreenSpaceAmbientOcclusion {
118    /// Quality of the SSAO effect.
119    pub quality_level: ScreenSpaceAmbientOcclusionQualityLevel,
120    /// Maximum sample distance, in view-space units.
121    ///
122    /// Use larger values for broader occlusion. This can increase haloing and noise.
123    /// This value must be greater than `0.0`.
124    /// Defaults to `0.7285`.
125    ///
126    /// To disable SSAO, remove [`ScreenSpaceAmbientOcclusion`] from the camera instead.
127    pub radius: f32,
128    /// A constant estimated thickness of objects.
129    ///
130    /// This value is used to decide how far behind an object a ray of light needs to be in order
131    /// to pass behind it. Any ray closer than that will be occluded.
132    pub constant_object_thickness: f32,
133}
134
135const DEFAULT_SSAO_RADIUS: f32 = 0.5 * 1.457;
136
137impl Default for ScreenSpaceAmbientOcclusion {
138    fn default() -> Self {
139        Self {
140            quality_level: ScreenSpaceAmbientOcclusionQualityLevel::default(),
141            radius: DEFAULT_SSAO_RADIUS,
142            constant_object_thickness: 0.25,
143        }
144    }
145}
146
147#[derive(#[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
    for SsaoSettingsUniform {
}
#[automatically_derived]impl ::core::clone::Clone for SsaoSettingsUniform {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<f32>;
        *self
    }
}Clone, #[automatically_derived]impl ::core::marker::Copy for SsaoSettingsUniform { }Copy, impl bevy_render::render_resource::encase::private::ShaderType for
    SsaoSettingsUniform where
    f32: bevy_render::render_resource::encase::private::ShaderType +
    bevy_render::render_resource::encase::private::ShaderSize,
    f32: bevy_render::render_resource::encase::private::ShaderType {
    type ExtraMetadata =
        bevy_render::render_resource::encase::private::StructMetadata<2usize>;
    const METADATA:
        bevy_render::render_resource::encase::private::Metadata<Self::ExtraMetadata>
        =
        {
            let struct_alignment =
                bevy_render::render_resource::encase::private::AlignmentValue::max([<f32
                                    as
                                    bevy_render::render_resource::encase::private::ShaderType>::METADATA.alignment(),
                            <f32 as
                                    bevy_render::render_resource::encase::private::ShaderType>::METADATA.alignment()]);
            let extra =
                {
                    let mut paddings = [0; 2usize];
                    let mut offsets = [0; 2usize];
                    let mut offset = 0;
                    offset +=
                        <f32 as
                                bevy_render::render_resource::encase::private::ShaderSize>::SHADER_SIZE.get();
                    offsets[1usize] =
                        <f32 as
                                    bevy_render::render_resource::encase::private::ShaderType>::METADATA.alignment().round_up(offset);
                    let padding =
                        <f32 as
                                    bevy_render::render_resource::encase::private::ShaderType>::METADATA.alignment().padding_needed_for(offset);
                    offset += padding;
                    paddings[0usize] = padding;
                    offset +=
                        <f32 as
                                bevy_render::render_resource::encase::private::ShaderSize>::SHADER_SIZE.get();
                    paddings[1usize] =
                        struct_alignment.padding_needed_for(offset);
                    bevy_render::render_resource::encase::private::StructMetadata {
                        offsets,
                        paddings,
                    }
                };
            let min_size =
                {
                    let mut offset = extra.offsets[2usize - 1];
                    offset +=
                        <f32 as
                                    bevy_render::render_resource::encase::private::ShaderType>::METADATA.min_size().get();
                    bevy_render::render_resource::encase::private::SizeValue::new(struct_alignment.round_up(offset))
                };
            bevy_render::render_resource::encase::private::Metadata {
                alignment: struct_alignment,
                has_uniform_min_alignment: true,
                min_size,
                is_pod: false,
                extra,
            }
        };
    const UNIFORM_COMPAT_ASSERT: fn() =
        ||
            bevy_render::render_resource::encase::private::consume_zsts([<f32
                                as
                                bevy_render::render_resource::encase::private::ShaderType>::UNIFORM_COMPAT_ASSERT(),
                        if let ::core::option::Option::Some(min_alignment) =
                                <f32 as
                                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.uniform_min_alignment()
                            {
                            let offset =
                                <Self as
                                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.offset(0usize);
                            {

                                #[allow(clippy :: equatable_if_let)]
                                if let false = min_alignment.is_aligned(offset) {
                                    {
                                        let mut fmt: ::const_panic::FmtArg =
                                            ::const_panic::FmtArg::DEBUG;
                                        match &[::const_panic::StdWrapper(&match &"offset of field '"
                                                                            {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &"radius" {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &"' must be a multiple of "
                                                                            {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &min_alignment.get() {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt)).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &" (current offset: " {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &offset {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt)).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &")" {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals()]
                                            {
                                            args => ::const_panic::concat_panic(args),
                                        }
                                    }
                                }
                            }
                        }, (),
                        <f32 as
                                bevy_render::render_resource::encase::private::ShaderType>::UNIFORM_COMPAT_ASSERT(),
                        if let ::core::option::Option::Some(min_alignment) =
                                <f32 as
                                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.uniform_min_alignment()
                            {
                            let offset =
                                <Self as
                                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.offset(1usize);
                            {

                                #[allow(clippy :: equatable_if_let)]
                                if let false = min_alignment.is_aligned(offset) {
                                    {
                                        let mut fmt: ::const_panic::FmtArg =
                                            ::const_panic::FmtArg::DEBUG;
                                        match &[::const_panic::StdWrapper(&match &"offset of field '"
                                                                            {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &"constant_object_thickness"
                                                                            {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &"' must be a multiple of "
                                                                            {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &min_alignment.get() {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt)).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &" (current offset: " {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &offset {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt)).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &")" {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals()]
                                            {
                                            args => ::const_panic::concat_panic(args),
                                        }
                                    }
                                }
                            }
                        },
                        if let ::core::option::Option::Some(min_alignment) =
                                <f32 as
                                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.uniform_min_alignment()
                            {
                            let prev_offset =
                                <Self as
                                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.offset(1usize
                                        - 1);
                            let offset =
                                <Self as
                                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.offset(1usize);
                            let diff = offset - prev_offset;
                            let prev_size =
                                <f32 as
                                        bevy_render::render_resource::encase::private::ShaderSize>::SHADER_SIZE.get();
                            let prev_size = min_alignment.round_up(prev_size);
                            {

                                #[allow(clippy :: equatable_if_let)]
                                if let false = diff >= prev_size {
                                    {
                                        let mut fmt: ::const_panic::FmtArg =
                                            ::const_panic::FmtArg::DEBUG;
                                        match &[::const_panic::StdWrapper(&match &"offset between fields '"
                                                                            {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &"radius" {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &"' and '" {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &"constant_object_thickness"
                                                                            {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &"' must be at least " {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &min_alignment.get() {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt)).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &" (currently: " {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &diff {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt)).deref_panic_vals(),
                                                        ::const_panic::StdWrapper(&match &")" {
                                                                            reff =>
                                                                                ::const_panic::__::PanicFmt::PROOF.infer(reff).coerce(reff),
                                                                        }.to_panicvals(fmt.set_display().set_alternate(false))).deref_panic_vals()]
                                            {
                                            args => ::const_panic::concat_panic(args),
                                        }
                                    }
                                }
                            }
                        }]);
    fn size(&self) -> ::core::num::NonZeroU64 {
        let mut offset = Self::METADATA.last_offset();
        offset +=
            bevy_render::render_resource::encase::private::ShaderType::size(&self.constant_object_thickness).get();
        bevy_render::render_resource::encase::private::SizeValue::new(Self::METADATA.alignment().round_up(offset)).0
    }
}
impl bevy_render::render_resource::encase::private::WriteInto for
    SsaoSettingsUniform where
    Self: bevy_render::render_resource::encase::private::ShaderType<ExtraMetadata
    = bevy_render::render_resource::encase::private::StructMetadata<2usize>>,
    for<'__> f32: bevy_render::render_resource::encase::private::WriteInto,
    for<'__> f32: bevy_render::render_resource::encase::private::WriteInto {
    #[inline]
    fn write_into<B: bevy_render::render_resource::encase::private::BufferMut>(&self,
        writer:
            &mut bevy_render::render_resource::encase::private::Writer<B>) {
        bevy_render::render_resource::encase::private::WriteInto::write_into(&self.radius,
            writer);
        bevy_render::render_resource::encase::private::Writer::advance(writer,
            <Self as
                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.padding(0usize)
                as ::core::primitive::usize);
        bevy_render::render_resource::encase::private::WriteInto::write_into(&self.constant_object_thickness,
            writer);
        bevy_render::render_resource::encase::private::Writer::advance(writer,
            <Self as
                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.padding(1usize)
                as ::core::primitive::usize);
    }
}
impl bevy_render::render_resource::encase::private::ReadFrom for
    SsaoSettingsUniform where
    Self: bevy_render::render_resource::encase::private::ShaderType<ExtraMetadata
    = bevy_render::render_resource::encase::private::StructMetadata<2usize>>,
    for<'__> f32: bevy_render::render_resource::encase::private::ReadFrom,
    for<'__> f32: bevy_render::render_resource::encase::private::ReadFrom {
    #[inline]
    fn read_from<B: bevy_render::render_resource::encase::private::BufferRef>(&mut self,
        reader:
            &mut bevy_render::render_resource::encase::private::Reader<B>) {
        bevy_render::render_resource::encase::private::ReadFrom::read_from(&mut self.radius,
            reader);
        bevy_render::render_resource::encase::private::Reader::advance(reader,
            <Self as
                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.padding(0usize)
                as ::core::primitive::usize);
        bevy_render::render_resource::encase::private::ReadFrom::read_from(&mut self.constant_object_thickness,
            reader);
        bevy_render::render_resource::encase::private::Reader::advance(reader,
            <Self as
                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.padding(1usize)
                as ::core::primitive::usize);
    }
}
impl bevy_render::render_resource::encase::private::CreateFrom for
    SsaoSettingsUniform where
    Self: bevy_render::render_resource::encase::private::ShaderType<ExtraMetadata
    = bevy_render::render_resource::encase::private::StructMetadata<2usize>>,
    for<'__> f32: bevy_render::render_resource::encase::private::CreateFrom,
    for<'__> f32: bevy_render::render_resource::encase::private::CreateFrom {
    #[inline]
    fn create_from<B: bevy_render::render_resource::encase::private::BufferRef>(reader:
            &mut bevy_render::render_resource::encase::private::Reader<B>)
        -> Self {
        let radius =
            bevy_render::render_resource::encase::private::CreateFrom::create_from(reader);
        bevy_render::render_resource::encase::private::Reader::advance(reader,
            <Self as
                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.padding(0usize)
                as ::core::primitive::usize);
        let constant_object_thickness =
            bevy_render::render_resource::encase::private::CreateFrom::create_from(reader);
        bevy_render::render_resource::encase::private::Reader::advance(reader,
            <Self as
                        bevy_render::render_resource::encase::private::ShaderType>::METADATA.padding(1usize)
                as ::core::primitive::usize);
        {
            let mut uninit_struct =
                ::core::mem::MaybeUninit::<Self>::uninit();
            let ptr =
                ::core::mem::MaybeUninit::as_mut_ptr(&mut uninit_struct);
            let field_ptr = unsafe { &raw mut (*ptr).radius };
            unsafe { field_ptr.write(radius) };
            ;
            let field_ptr =
                unsafe { &raw mut (*ptr).constant_object_thickness };
            unsafe { field_ptr.write(constant_object_thickness) };
            ;
            unsafe { ::core::mem::MaybeUninit::assume_init(uninit_struct) }
        }
    }
}
impl bevy_render::render_resource::encase::private::ShaderSize for
    SsaoSettingsUniform where
    f32: bevy_render::render_resource::encase::private::ShaderSize,
    f32: bevy_render::render_resource::encase::private::ShaderSize {}ShaderType)]
148struct SsaoSettingsUniform {
149    radius: f32,
150    constant_object_thickness: f32,
151}
152
153#[derive(const _: () =
    {
        impl bevy_reflect::GetTypeRegistration for
            ScreenSpaceAmbientOcclusionQualityLevel where  {
            fn get_type_registration() -> bevy_reflect::TypeRegistration {
                let mut registration =
                    bevy_reflect::TypeRegistration::of::<Self>();
                registration.insert(<bevy_reflect::ReflectFromPtr as
                            bevy_reflect::CreateTypeData<Self>>::create_type_data(()));
                registration.insert(<bevy_reflect::ReflectFromReflect as
                            bevy_reflect::CreateTypeData<Self>>::create_type_data(()));
                registration.register_type_data_with::<ReflectDefault, Self,
                    _>(());
                registration
            }
            #[inline(never)]
            fn register_type_dependencies(registry:
                    &mut bevy_reflect::TypeRegistry) {
                <u32 as
                        bevy_reflect::__macro_exports::RegisterForReflection>::__register(registry);
            }
        }
        impl bevy_reflect::Typed for ScreenSpaceAmbientOcclusionQualityLevel
            where  {
            #[inline(never)]
            fn type_info() -> &'static bevy_reflect::TypeInfo {
                static CELL: bevy_reflect::utility::NonGenericTypeInfoCell =
                    bevy_reflect::utility::NonGenericTypeInfoCell::new();
                CELL.get_or_set(#[inline(never)] ||
                        {
                            bevy_reflect::TypeInfo::Enum(bevy_reflect::enums::EnumInfo::new::<Self>(&[bevy_reflect::enums::VariantInfo::Unit(bevy_reflect::enums::UnitVariantInfo::new("Low")),
                                                bevy_reflect::enums::VariantInfo::Unit(bevy_reflect::enums::UnitVariantInfo::new("Medium")),
                                                bevy_reflect::enums::VariantInfo::Unit(bevy_reflect::enums::UnitVariantInfo::new("High")),
                                                bevy_reflect::enums::VariantInfo::Unit(bevy_reflect::enums::UnitVariantInfo::new("Ultra")),
                                                bevy_reflect::enums::VariantInfo::Struct(bevy_reflect::enums::StructVariantInfo::new("Custom",
                                                        &[bevy_reflect::NamedField::new::<u32>("slice_count"),
                                                                    bevy_reflect::NamedField::new::<u32>("samples_per_slice_side")]))]))
                        })
            }
        }
        #[allow(deprecated, reason =
        "derives on a deprecated type shouldn't be considered a usage")]
        impl bevy_reflect::TypePath for
            ScreenSpaceAmbientOcclusionQualityLevel where  {
            fn type_path() -> &'static str {
                "bevy_pbr::ssao::ScreenSpaceAmbientOcclusionQualityLevel"
            }
            fn short_type_path() -> &'static str {
                "ScreenSpaceAmbientOcclusionQualityLevel"
            }
            fn type_ident() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("ScreenSpaceAmbientOcclusionQualityLevel")
            }
            fn crate_name() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("bevy_pbr::ssao".split(':').next().unwrap())
            }
            fn module_path() -> ::core::option::Option<&'static str> {
                ::core::option::Option::Some("bevy_pbr::ssao")
            }
        }
        impl bevy_reflect::Reflect for ScreenSpaceAmbientOcclusionQualityLevel
            where  {
            #[inline]
            fn into_any(self:
                    bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
                ->
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn ::core::any::Any> {
                self
            }
            #[inline]
            fn as_any(&self) -> &dyn ::core::any::Any { self }
            #[inline]
            fn as_any_mut(&mut self) -> &mut dyn ::core::any::Any { self }
            #[inline]
            fn into_reflect(self:
                    bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
                ->
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect> {
                self
            }
            #[inline]
            fn as_reflect(&self) -> &dyn bevy_reflect::Reflect { self }
            #[inline]
            fn as_reflect_mut(&mut self) -> &mut dyn bevy_reflect::Reflect {
                self
            }
            #[inline]
            fn set(&mut self,
                value:
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>)
                ->
                    ::core::result::Result<(),
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>> {
                *self = <dyn bevy_reflect::Reflect>::take(value)?;
                ::core::result::Result::Ok(())
            }
        }
        #[allow(non_upper_case_globals)]
        const _: () =
            {
                static __INVENTORY: ::inventory::Node =
                    ::inventory::Node {
                        value: &{
                                bevy_reflect::__macro_exports::auto_register::AutomaticReflectRegistrations(<ScreenSpaceAmbientOcclusionQualityLevel
                                        as
                                        bevy_reflect::__macro_exports::auto_register::RegisterForReflection>::__register)
                            },
                        next: ::inventory::__private::UnsafeCell::new(::inventory::__private::Option::None),
                    };
                #[link_section = ".text.startup"]
                unsafe extern "C" fn __ctor() {
                    unsafe {
                        ::inventory::ErasedNode::submit(__INVENTORY.value,
                            &__INVENTORY)
                    }
                }
                #[used]
                #[link_section = ".init_array"]
                static __CTOR: unsafe extern "C" fn() = __ctor;
            };
        impl bevy_reflect::enums::Enum for
            ScreenSpaceAmbientOcclusionQualityLevel where  {
            fn field(&self, __name_param: &str)
                -> ::core::option::Option<&dyn bevy_reflect::PartialReflect> {
                match self {
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                        slice_count: __value, .. } if __name_param == "slice_count"
                        => ::core::option::Option::Some(__value),
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                        samples_per_slice_side: __value, .. } if
                        __name_param == "samples_per_slice_side" =>
                        ::core::option::Option::Some(__value),
                    _ => ::core::option::Option::None,
                }
            }
            fn field_at(&self, __index_param: usize)
                -> ::core::option::Option<&dyn bevy_reflect::PartialReflect> {
                match self {
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                        slice_count: __value, .. } if __index_param == 0usize =>
                        ::core::option::Option::Some(__value),
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                        samples_per_slice_side: __value, .. } if
                        __index_param == 1usize =>
                        ::core::option::Option::Some(__value),
                    _ => ::core::option::Option::None,
                }
            }
            fn field_mut(&mut self, __name_param: &str)
                ->
                    ::core::option::Option<&mut dyn bevy_reflect::PartialReflect> {
                match self {
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                        slice_count: __value, .. } if __name_param == "slice_count"
                        => ::core::option::Option::Some(__value),
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                        samples_per_slice_side: __value, .. } if
                        __name_param == "samples_per_slice_side" =>
                        ::core::option::Option::Some(__value),
                    _ => ::core::option::Option::None,
                }
            }
            fn field_at_mut(&mut self, __index_param: usize)
                ->
                    ::core::option::Option<&mut dyn bevy_reflect::PartialReflect> {
                match self {
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                        slice_count: __value, .. } if __index_param == 0usize =>
                        ::core::option::Option::Some(__value),
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                        samples_per_slice_side: __value, .. } if
                        __index_param == 1usize =>
                        ::core::option::Option::Some(__value),
                    _ => ::core::option::Option::None,
                }
            }
            fn index_of(&self, __name_param: &str)
                -> ::core::option::Option<usize> {
                match self {
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom { .. } if
                        __name_param == "slice_count" =>
                        ::core::option::Option::Some(0usize),
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom { .. } if
                        __name_param == "samples_per_slice_side" =>
                        ::core::option::Option::Some(1usize),
                    _ => ::core::option::Option::None,
                }
            }
            fn name_at(&self, __index_param: usize)
                -> ::core::option::Option<&str> {
                match self {
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom { .. } if
                        __index_param == 0usize =>
                        ::core::option::Option::Some("slice_count"),
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom { .. } if
                        __index_param == 1usize =>
                        ::core::option::Option::Some("samples_per_slice_side"),
                    _ => ::core::option::Option::None,
                }
            }
            fn iter_fields(&self) -> bevy_reflect::enums::VariantFieldIter {
                bevy_reflect::enums::VariantFieldIter::new(self)
            }
            #[inline]
            fn field_len(&self) -> usize {
                match self {
                    ScreenSpaceAmbientOcclusionQualityLevel::Low { .. } =>
                        0usize,
                    ScreenSpaceAmbientOcclusionQualityLevel::Medium { .. } =>
                        0usize,
                    ScreenSpaceAmbientOcclusionQualityLevel::High { .. } =>
                        0usize,
                    ScreenSpaceAmbientOcclusionQualityLevel::Ultra { .. } =>
                        0usize,
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom { .. } =>
                        2usize,
                    _ => 0,
                }
            }
            #[inline]
            fn variant_name(&self) -> &str {
                match self {
                    ScreenSpaceAmbientOcclusionQualityLevel::Low { .. } =>
                        "Low",
                    ScreenSpaceAmbientOcclusionQualityLevel::Medium { .. } =>
                        "Medium",
                    ScreenSpaceAmbientOcclusionQualityLevel::High { .. } =>
                        "High",
                    ScreenSpaceAmbientOcclusionQualityLevel::Ultra { .. } =>
                        "Ultra",
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom { .. } =>
                        "Custom",
                    _ => ::core::panicking::unreachable_default_msg(),
                }
            }
            #[inline]
            fn variant_index(&self) -> usize {
                match self {
                    ScreenSpaceAmbientOcclusionQualityLevel::Low { .. } =>
                        0usize,
                    ScreenSpaceAmbientOcclusionQualityLevel::Medium { .. } =>
                        1usize,
                    ScreenSpaceAmbientOcclusionQualityLevel::High { .. } =>
                        2usize,
                    ScreenSpaceAmbientOcclusionQualityLevel::Ultra { .. } =>
                        3usize,
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom { .. } =>
                        4usize,
                    _ => ::core::panicking::unreachable_default_msg(),
                }
            }
            #[inline]
            fn variant_type(&self) -> bevy_reflect::enums::VariantType {
                match self {
                    ScreenSpaceAmbientOcclusionQualityLevel::Low { .. } =>
                        bevy_reflect::enums::VariantType::Unit,
                    ScreenSpaceAmbientOcclusionQualityLevel::Medium { .. } =>
                        bevy_reflect::enums::VariantType::Unit,
                    ScreenSpaceAmbientOcclusionQualityLevel::High { .. } =>
                        bevy_reflect::enums::VariantType::Unit,
                    ScreenSpaceAmbientOcclusionQualityLevel::Ultra { .. } =>
                        bevy_reflect::enums::VariantType::Unit,
                    ScreenSpaceAmbientOcclusionQualityLevel::Custom { .. } =>
                        bevy_reflect::enums::VariantType::Struct,
                    _ => ::core::panicking::unreachable_default_msg(),
                }
            }
            fn to_dynamic_enum(&self)
                ->
                    ::core::result::Result<bevy_reflect::enums::DynamicEnum,
                    bevy_reflect::ReflectCloneError> {
                bevy_reflect::enums::DynamicEnum::try_from_ref::<Self>(self)
            }
        }
        impl bevy_reflect::PartialReflect for
            ScreenSpaceAmbientOcclusionQualityLevel where  {
            #[inline]
            fn get_represented_type_info(&self)
                -> ::core::option::Option<&'static bevy_reflect::TypeInfo> {
                ::core::option::Option::Some(<Self as
                            bevy_reflect::Typed>::type_info())
            }
            #[inline]
            fn try_apply(&mut self,
                __value_param: &dyn bevy_reflect::PartialReflect)
                -> ::core::result::Result<(), bevy_reflect::ApplyError> {
                if let bevy_reflect::ReflectRef::Enum(__value_param) =
                        bevy_reflect::PartialReflect::reflect_ref(__value_param) {
                    if bevy_reflect::enums::Enum::variant_name(self) ==
                            bevy_reflect::enums::Enum::variant_name(__value_param) {
                        match bevy_reflect::enums::Enum::variant_type(__value_param)
                            {
                            bevy_reflect::enums::VariantType::Struct => {
                                for field in
                                    bevy_reflect::enums::Enum::iter_fields(__value_param) {
                                    let name = field.name().unwrap();
                                    if let ::core::option::Option::Some(v) =
                                            bevy_reflect::enums::Enum::field_mut(self, name) {
                                        bevy_reflect::PartialReflect::try_apply(v, field.value())?;
                                    }
                                }
                            }
                            bevy_reflect::enums::VariantType::Tuple => {
                                for (index, field) in
                                    ::core::iter::Iterator::enumerate(bevy_reflect::enums::Enum::iter_fields(__value_param))
                                    {
                                    if let ::core::option::Option::Some(v) =
                                            bevy_reflect::enums::Enum::field_at_mut(self, index) {
                                        bevy_reflect::PartialReflect::try_apply(v, field.value())?;
                                    }
                                }
                            }
                            _ => {}
                        }
                    } else {
                        match bevy_reflect::enums::Enum::variant_name(__value_param)
                            {
                            "Low" => {
                                *self = ScreenSpaceAmbientOcclusionQualityLevel::Low {}
                            }
                            "Medium" => {
                                *self = ScreenSpaceAmbientOcclusionQualityLevel::Medium {}
                            }
                            "High" => {
                                *self = ScreenSpaceAmbientOcclusionQualityLevel::High {}
                            }
                            "Ultra" => {
                                *self = ScreenSpaceAmbientOcclusionQualityLevel::Ultra {}
                            }
                            "Custom" => {
                                *self =
                                    ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                                        slice_count: {
                                            let __slice_count = __value_param.field("slice_count");
                                            let __slice_count =
                                                __slice_count.ok_or(bevy_reflect::ApplyError::MissingEnumField {
                                                            variant_name: ::core::convert::Into::into("Custom"),
                                                            field_name: ::core::convert::Into::into("slice_count"),
                                                        })?;
                                            <u32 as
                                                            bevy_reflect::FromReflect>::from_reflect(__slice_count).ok_or(bevy_reflect::ApplyError::MismatchedTypes {
                                                        from_type: ::core::convert::Into::into(bevy_reflect::DynamicTypePath::reflect_type_path(__slice_count)),
                                                        to_type: ::core::convert::Into::into(<u32 as
                                                                    bevy_reflect::TypePath>::type_path()),
                                                    })?
                                        },
                                        samples_per_slice_side: {
                                            let __samples_per_slice_side =
                                                __value_param.field("samples_per_slice_side");
                                            let __samples_per_slice_side =
                                                __samples_per_slice_side.ok_or(bevy_reflect::ApplyError::MissingEnumField {
                                                            variant_name: ::core::convert::Into::into("Custom"),
                                                            field_name: ::core::convert::Into::into("samples_per_slice_side"),
                                                        })?;
                                            <u32 as
                                                            bevy_reflect::FromReflect>::from_reflect(__samples_per_slice_side).ok_or(bevy_reflect::ApplyError::MismatchedTypes {
                                                        from_type: ::core::convert::Into::into(bevy_reflect::DynamicTypePath::reflect_type_path(__samples_per_slice_side)),
                                                        to_type: ::core::convert::Into::into(<u32 as
                                                                    bevy_reflect::TypePath>::type_path()),
                                                    })?
                                        },
                                    }
                            }
                            name => {
                                return ::core::result::Result::Err(bevy_reflect::ApplyError::UnknownVariant {
                                            enum_name: ::core::convert::Into::into(bevy_reflect::DynamicTypePath::reflect_type_path(self)),
                                            variant_name: ::core::convert::Into::into(name),
                                        });
                            }
                        }
                    }
                } else {
                    return ::core::result::Result::Err(bevy_reflect::ApplyError::MismatchedKinds {
                                from_kind: bevy_reflect::PartialReflect::reflect_kind(__value_param),
                                to_kind: bevy_reflect::ReflectKind::Enum,
                            });
                }
                ::core::result::Result::Ok(())
            }
            fn reflect_kind(&self) -> bevy_reflect::ReflectKind {
                bevy_reflect::ReflectKind::Enum
            }
            fn reflect_ref(&self) -> bevy_reflect::ReflectRef {
                bevy_reflect::ReflectRef::Enum(self)
            }
            fn reflect_mut(&mut self) -> bevy_reflect::ReflectMut {
                bevy_reflect::ReflectMut::Enum(self)
            }
            fn reflect_owned(self:
                    bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
                -> bevy_reflect::ReflectOwned {
                bevy_reflect::ReflectOwned::Enum(self)
            }
            #[inline]
            fn try_into_reflect(self:
                    bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
                ->
                    ::core::result::Result<bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>,
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::PartialReflect>> {
                ::core::result::Result::Ok(self)
            }
            #[inline]
            fn try_as_reflect(&self)
                -> ::core::option::Option<&dyn bevy_reflect::Reflect> {
                ::core::option::Option::Some(self)
            }
            #[inline]
            fn try_as_reflect_mut(&mut self)
                -> ::core::option::Option<&mut dyn bevy_reflect::Reflect> {
                ::core::option::Option::Some(self)
            }
            #[inline]
            fn into_partial_reflect(self:
                    bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
                ->
                    bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::PartialReflect> {
                self
            }
            #[inline]
            fn as_partial_reflect(&self)
                -> &dyn bevy_reflect::PartialReflect {
                self
            }
            #[inline]
            fn as_partial_reflect_mut(&mut self)
                -> &mut dyn bevy_reflect::PartialReflect {
                self
            }
            fn reflect_hash(&self) -> ::core::option::Option<u64> {
                use ::core::hash::{Hash, Hasher};
                let mut hasher = bevy_reflect::utility::reflect_hasher();
                Hash::hash(&::core::any::Any::type_id(self), &mut hasher);
                Hash::hash(self, &mut hasher);
                ::core::option::Option::Some(Hasher::finish(&hasher))
            }
            fn reflect_partial_eq(&self,
                value: &dyn bevy_reflect::PartialReflect)
                -> ::core::option::Option<bool> {
                let value =
                    <dyn bevy_reflect::PartialReflect>::try_downcast_ref::<Self>(value);
                if let ::core::option::Option::Some(value) = value {
                    ::core::option::Option::Some(::core::cmp::PartialEq::eq(self,
                            value))
                } else { ::core::option::Option::Some(false) }
            }
            fn reflect_partial_cmp(&self,
                value: &dyn bevy_reflect::PartialReflect)
                -> ::core::option::Option<::core::cmp::Ordering> {
                (bevy_reflect::enums::enum_partial_cmp)(self, value)
            }
            #[inline]
            fn reflect_clone(&self)
                ->
                    ::core::result::Result<bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>,
                    bevy_reflect::ReflectCloneError> {
                ::core::result::Result::Ok(bevy_reflect::__macro_exports::alloc_utils::Box::new(::core::clone::Clone::clone(self)))
            }
        }
        impl bevy_reflect::FromReflect for
            ScreenSpaceAmbientOcclusionQualityLevel where  {
            fn from_reflect(__param0: &dyn bevy_reflect::PartialReflect)
                -> ::core::option::Option<Self> {
                if let bevy_reflect::ReflectRef::Enum(__param0) =
                        bevy_reflect::PartialReflect::reflect_ref(__param0) {
                    match bevy_reflect::enums::Enum::variant_name(__param0) {
                        "Low" =>
                            ::core::option::Option::Some(ScreenSpaceAmbientOcclusionQualityLevel::Low {}),
                        "Medium" =>
                            ::core::option::Option::Some(ScreenSpaceAmbientOcclusionQualityLevel::Medium {}),
                        "High" =>
                            ::core::option::Option::Some(ScreenSpaceAmbientOcclusionQualityLevel::High {}),
                        "Ultra" =>
                            ::core::option::Option::Some(ScreenSpaceAmbientOcclusionQualityLevel::Ultra {}),
                        "Custom" =>
                            ::core::option::Option::Some(ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                                    slice_count: {
                                        let __slice_count = __param0.field("slice_count");
                                        let __slice_count = __slice_count?;
                                        <u32 as
                                                    bevy_reflect::FromReflect>::from_reflect(__slice_count)?
                                    },
                                    samples_per_slice_side: {
                                        let __samples_per_slice_side =
                                            __param0.field("samples_per_slice_side");
                                        let __samples_per_slice_side = __samples_per_slice_side?;
                                        <u32 as
                                                    bevy_reflect::FromReflect>::from_reflect(__samples_per_slice_side)?
                                    },
                                }),
                        name => ::core::option::Option::None,
                    }
                } else { ::core::option::Option::None }
            }
        }
    };Reflect, #[automatically_derived]impl ::core::marker::StructuralPartialEq for
    ScreenSpaceAmbientOcclusionQualityLevel {
}
#[automatically_derived]impl ::core::cmp::PartialEq for
    ScreenSpaceAmbientOcclusionQualityLevel {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
                ::core::intrinsics::discriminant_value(other) &&
            match (self, other) {
                (Self::Custom {
                    slice_count: __self_0, samples_per_slice_side: __self_1 },
                    Self::Custom {
                    slice_count: __arg1_0, samples_per_slice_side: __arg1_1 })
                    => __self_0 == __arg1_0 && __self_1 == __arg1_1,
                _ => true,
            }
    }
}PartialEq, #[automatically_derived]impl ::core::cmp::Eq for
    ScreenSpaceAmbientOcclusionQualityLevel {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<u32>;
    }
}Eq, #[automatically_derived]impl ::core::hash::Hash for
    ScreenSpaceAmbientOcclusionQualityLevel {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&::core::intrinsics::discriminant_value(self),
            state);
        match self {
            Self::Custom {
                slice_count: __self_0, samples_per_slice_side: __self_1 } => {
                ::core::hash::Hash::hash(__self_0, state);
                ::core::hash::Hash::hash(__self_1, state)
            }
            _ => {}
        }
    }
}Hash, #[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
    for ScreenSpaceAmbientOcclusionQualityLevel {
}
#[automatically_derived]impl ::core::clone::Clone for
    ScreenSpaceAmbientOcclusionQualityLevel {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<u32>;
        *self
    }
}Clone, #[automatically_derived]impl ::core::marker::Copy for
    ScreenSpaceAmbientOcclusionQualityLevel {
}Copy, #[automatically_derived]impl ::core::default::Default for
    ScreenSpaceAmbientOcclusionQualityLevel {
    #[inline]
    fn default() -> Self { Self::High }
}Default, #[automatically_derived]impl ::core::fmt::Debug for
    ScreenSpaceAmbientOcclusionQualityLevel {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Low => ::core::fmt::Formatter::write_str(f, "Low"),
            Self::Medium => ::core::fmt::Formatter::write_str(f, "Medium"),
            Self::High => ::core::fmt::Formatter::write_str(f, "High"),
            Self::Ultra => ::core::fmt::Formatter::write_str(f, "Ultra"),
            Self::Custom {
                slice_count: __self_0, samples_per_slice_side: __self_1 } =>
                ::core::fmt::Formatter::debug_struct_field2_finish(f,
                    "Custom", "slice_count", __self_0, "samples_per_slice_side",
                    &__self_1),
        }
    }
}Debug)]
154#[reflect(PartialEq, Hash, Clone, Default)]
155pub enum ScreenSpaceAmbientOcclusionQualityLevel {
156    Low,
157    Medium,
158    #[default]
159    High,
160    Ultra,
161    Custom {
162        /// Higher slice count means less noise, but worse performance.
163        slice_count: u32,
164        /// Samples per slice side is also tweakable, but recommended to be left at 2 or 3.
165        samples_per_slice_side: u32,
166    },
167}
168
169impl ScreenSpaceAmbientOcclusionQualityLevel {
170    fn sample_counts(&self) -> (u32, u32) {
171        match self {
172            Self::Low => (1, 2),    // 4 spp (1 * (2 * 2)), plus optional temporal samples
173            Self::Medium => (2, 2), // 8 spp (2 * (2 * 2)), plus optional temporal samples
174            Self::High => (3, 3),   // 18 spp (3 * (3 * 2)), plus optional temporal samples
175            Self::Ultra => (9, 3),  // 54 spp (9 * (3 * 2)), plus optional temporal samples
176            Self::Custom {
177                slice_count: slices,
178                samples_per_slice_side,
179            } => (*slices, *samples_per_slice_side),
180        }
181    }
182}
183
184fn ssao(
185    view: ViewQuery<(
186        &ExtractedCamera,
187        &SsaoPipelineId,
188        &SsaoBindGroups,
189        &ViewUniformOffset,
190    )>,
191    pipelines: Res<SsaoPipelines>,
192    pipeline_cache: Res<PipelineCache>,
193    mut ctx: RenderContext,
194) {
195    let (camera, pipeline_id, bind_groups, view_uniform_offset) = view.into_inner();
196
197    let (
198        Some(camera_size),
199        Some(preprocess_depth_pipeline),
200        Some(spatial_denoise_pipeline),
201        Some(ssao_pipeline),
202    ) = (
203        camera.physical_viewport_size,
204        pipeline_cache.get_compute_pipeline(pipelines.preprocess_depth_pipeline),
205        pipeline_cache.get_compute_pipeline(pipelines.spatial_denoise_pipeline),
206        pipeline_cache.get_compute_pipeline(pipeline_id.0),
207    )
208    else {
209        return;
210    };
211
212    let diagnostics = ctx.diagnostic_recorder();
213    let diagnostics = diagnostics.as_deref();
214    let time_span = diagnostics.time_span(ctx.command_encoder(), "ssao");
215
216    let command_encoder = ctx.command_encoder();
217    command_encoder.push_debug_group("ssao");
218
219    {
220        let mut preprocess_depth_pass =
221            command_encoder.begin_compute_pass(&ComputePassDescriptor {
222                label: Some("ssao_preprocess_depth"),
223                timestamp_writes: None,
224            });
225        preprocess_depth_pass.set_pipeline(preprocess_depth_pipeline);
226        preprocess_depth_pass.set_bind_group(0, &bind_groups.preprocess_depth_bind_group, &[]);
227        preprocess_depth_pass.set_bind_group(
228            1,
229            &bind_groups.common_bind_group,
230            &[view_uniform_offset.offset],
231        );
232        preprocess_depth_pass.dispatch_workgroups(
233            camera_size.x.div_ceil(16),
234            camera_size.y.div_ceil(16),
235            1,
236        );
237    }
238
239    {
240        let mut ssao_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
241            label: Some("ssao"),
242            timestamp_writes: None,
243        });
244        ssao_pass.set_pipeline(ssao_pipeline);
245        ssao_pass.set_bind_group(0, &bind_groups.ssao_bind_group, &[]);
246        ssao_pass.set_bind_group(
247            1,
248            &bind_groups.common_bind_group,
249            &[view_uniform_offset.offset],
250        );
251        ssao_pass.dispatch_workgroups(camera_size.x.div_ceil(8), camera_size.y.div_ceil(8), 1);
252    }
253
254    {
255        let mut spatial_denoise_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
256            label: Some("ssao_spatial_denoise"),
257            timestamp_writes: None,
258        });
259        spatial_denoise_pass.set_pipeline(spatial_denoise_pipeline);
260        spatial_denoise_pass.set_bind_group(0, &bind_groups.spatial_denoise_bind_group, &[]);
261        spatial_denoise_pass.set_bind_group(
262            1,
263            &bind_groups.common_bind_group,
264            &[view_uniform_offset.offset],
265        );
266        spatial_denoise_pass.dispatch_workgroups(
267            camera_size.x.div_ceil(8),
268            camera_size.y.div_ceil(8),
269            1,
270        );
271    }
272
273    command_encoder.pop_debug_group();
274    time_span.end(ctx.command_encoder());
275}
276
277#[derive(impl bevy_ecs::component::Component for SsaoPipelines where
    Self: ::core::marker::Send + ::core::marker::Sync + 'static {
    const STORAGE_TYPE: bevy_ecs::component::StorageType =
        bevy_ecs::component::StorageType::SparseSet;
    type Mutability = bevy_ecs::component::Mutable;
    fn register_required_components(_requiree:
            bevy_ecs::component::ComponentId,
        required_components:
            &mut bevy_ecs::component::RequiredComponentsRegistrator) {
        let resource_component_id =
            if let ::core::option::Option::Some(id) =
                    required_components.components_registrator().component_id::<SsaoPipelines>()
                {
                id
            } else {
                required_components.components_registrator().register_component::<SsaoPipelines>()
            };
        required_components.register_required::<bevy_ecs::resource::IsResource>(move
                ||
                bevy_ecs::resource::IsResource::new(resource_component_id));
    }
    fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
        use bevy_ecs::component::{
            DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
        };
        (&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
    }
    fn relationship_accessor()
        ->
            ::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
        ::core::option::Option::None
    }
}
impl bevy_ecs::resource::Resource for SsaoPipelines where
    Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource)]
278struct SsaoPipelines {
279    preprocess_depth_pipeline: CachedComputePipelineId,
280    spatial_denoise_pipeline: CachedComputePipelineId,
281
282    common_bind_group_layout: BindGroupLayoutDescriptor,
283    preprocess_depth_bind_group_layout: BindGroupLayoutDescriptor,
284    ssao_bind_group_layout: BindGroupLayoutDescriptor,
285    spatial_denoise_bind_group_layout: BindGroupLayoutDescriptor,
286
287    hilbert_index_lut: TextureView,
288    point_clamp_sampler: Sampler,
289    linear_clamp_sampler: Sampler,
290
291    shader: Handle<Shader>,
292    depth_format: TextureFormat,
293}
294
295impl FromWorld for SsaoPipelines {
296    fn from_world(world: &mut World) -> Self {
297        let render_device = world.resource::<RenderDevice>();
298        let render_queue = world.resource::<RenderQueue>();
299        let pipeline_cache = world.resource::<PipelineCache>();
300
301        // Detect the depth format support
302        let render_adapter = world.resource::<RenderAdapter>();
303        let depth_format = if render_adapter
304            .get_texture_format_features(TextureFormat::R16Float)
305            .allowed_usages
306            .contains(TextureUsages::STORAGE_BINDING)
307        {
308            TextureFormat::R16Float
309        } else {
310            TextureFormat::R32Float
311        };
312
313        let hilbert_index_lut = render_device
314            .create_texture_with_data(
315                render_queue,
316                &(TextureDescriptor {
317                    label: Some("ssao_hilbert_index_lut"),
318                    size: Extent3d {
319                        width: HILBERT_WIDTH as u32,
320                        height: HILBERT_WIDTH as u32,
321                        depth_or_array_layers: 1,
322                    },
323                    mip_level_count: 1,
324                    sample_count: 1,
325                    dimension: TextureDimension::D2,
326                    format: TextureFormat::R16Uint,
327                    usage: TextureUsages::TEXTURE_BINDING,
328                    view_formats: &[],
329                }),
330                TextureDataOrder::default(),
331                bytemuck::cast_slice(&generate_hilbert_index_lut()),
332            )
333            .create_view(&TextureViewDescriptor::default());
334
335        let point_clamp_sampler = render_device.create_sampler(&SamplerDescriptor {
336            min_filter: FilterMode::Nearest,
337            mag_filter: FilterMode::Nearest,
338            mipmap_filter: MipmapFilterMode::Nearest,
339            address_mode_u: AddressMode::ClampToEdge,
340            address_mode_v: AddressMode::ClampToEdge,
341            ..Default::default()
342        });
343        let linear_clamp_sampler = render_device.create_sampler(&SamplerDescriptor {
344            min_filter: FilterMode::Linear,
345            mag_filter: FilterMode::Linear,
346            mipmap_filter: MipmapFilterMode::Nearest,
347            address_mode_u: AddressMode::ClampToEdge,
348            address_mode_v: AddressMode::ClampToEdge,
349            ..Default::default()
350        });
351
352        let common_bind_group_layout = BindGroupLayoutDescriptor::new(
353            "ssao_common_bind_group_layout",
354            &BindGroupLayoutEntries::sequential(
355                ShaderStages::COMPUTE,
356                (
357                    sampler(SamplerBindingType::NonFiltering),
358                    sampler(SamplerBindingType::Filtering),
359                    uniform_buffer::<ViewUniform>(true),
360                ),
361            ),
362        );
363
364        let preprocess_depth_bind_group_layout = BindGroupLayoutDescriptor::new(
365            "ssao_preprocess_depth_bind_group_layout",
366            &BindGroupLayoutEntries::sequential(
367                ShaderStages::COMPUTE,
368                (
369                    texture_depth_2d(),
370                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
371                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
372                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
373                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
374                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
375                ),
376            ),
377        );
378
379        let ssao_bind_group_layout = BindGroupLayoutDescriptor::new(
380            "ssao_ssao_bind_group_layout",
381            &BindGroupLayoutEntries::sequential(
382                ShaderStages::COMPUTE,
383                (
384                    texture_2d(TextureSampleType::Float { filterable: true }),
385                    texture_2d(TextureSampleType::Float { filterable: false }),
386                    texture_2d(TextureSampleType::Uint),
387                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
388                    texture_storage_2d(TextureFormat::R32Uint, StorageTextureAccess::WriteOnly),
389                    uniform_buffer::<GlobalsUniform>(false),
390                    uniform_buffer::<SsaoSettingsUniform>(false),
391                ),
392            ),
393        );
394
395        let spatial_denoise_bind_group_layout = BindGroupLayoutDescriptor::new(
396            "ssao_spatial_denoise_bind_group_layout",
397            &BindGroupLayoutEntries::sequential(
398                ShaderStages::COMPUTE,
399                (
400                    texture_2d(TextureSampleType::Float { filterable: false }),
401                    texture_2d(TextureSampleType::Uint),
402                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
403                ),
404            ),
405        );
406
407        let mut shader_defs = Vec::new();
408        if depth_format == TextureFormat::R16Float {
409            shader_defs.push("USE_R16FLOAT".into());
410        }
411
412        let preprocess_depth_pipeline =
413            pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
414                label: Some("ssao_preprocess_depth_pipeline".into()),
415                layout: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [preprocess_depth_bind_group_layout.clone(),
                common_bind_group_layout.clone()]))vec![
416                    preprocess_depth_bind_group_layout.clone(),
417                    common_bind_group_layout.clone(),
418                ],
419                shader: {
    let (path, asset_server) =
        {
            let path =
                {
                    {
                        let crate_name =
                            "bevy_pbr::ssao".split(':').next().unwrap();
                        ::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
                            "src".as_ref(), "src/ssao/mod.rs".as_ref(),
                            "preprocess_depth.wesl".as_ref())
                    }
                };
            let path =
                ::bevy_asset::AssetPath::from_path_buf(path).with_source("embedded");
            let asset_server =
                ::bevy_asset::io::embedded::GetAssetServer::get_asset_server(world);
            (path, asset_server)
        };
    asset_server.load(path)
}load_embedded_asset!(world, "preprocess_depth.wesl"),
420                shader_defs: shader_defs.clone(),
421                ..default()
422            });
423
424        let spatial_denoise_pipeline =
425            pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
426                label: Some("ssao_spatial_denoise_pipeline".into()),
427                layout: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [spatial_denoise_bind_group_layout.clone(),
                common_bind_group_layout.clone()]))vec![
428                    spatial_denoise_bind_group_layout.clone(),
429                    common_bind_group_layout.clone(),
430                ],
431                shader: {
    let (path, asset_server) =
        {
            let path =
                {
                    {
                        let crate_name =
                            "bevy_pbr::ssao".split(':').next().unwrap();
                        ::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
                            "src".as_ref(), "src/ssao/mod.rs".as_ref(),
                            "spatial_denoise.wesl".as_ref())
                    }
                };
            let path =
                ::bevy_asset::AssetPath::from_path_buf(path).with_source("embedded");
            let asset_server =
                ::bevy_asset::io::embedded::GetAssetServer::get_asset_server(world);
            (path, asset_server)
        };
    asset_server.load(path)
}load_embedded_asset!(world, "spatial_denoise.wesl"),
432                shader_defs,
433                ..default()
434            });
435
436        Self {
437            preprocess_depth_pipeline,
438            spatial_denoise_pipeline,
439
440            common_bind_group_layout,
441            preprocess_depth_bind_group_layout,
442            ssao_bind_group_layout,
443            spatial_denoise_bind_group_layout,
444
445            hilbert_index_lut,
446            point_clamp_sampler,
447            linear_clamp_sampler,
448
449            shader: {
    let (path, asset_server) =
        {
            let path =
                {
                    {
                        let crate_name =
                            "bevy_pbr::ssao".split(':').next().unwrap();
                        ::bevy_asset::io::embedded::_embedded_asset_path(crate_name,
                            "src".as_ref(), "src/ssao/mod.rs".as_ref(),
                            "ssao.wesl".as_ref())
                    }
                };
            let path =
                ::bevy_asset::AssetPath::from_path_buf(path).with_source("embedded");
            let asset_server =
                ::bevy_asset::io::embedded::GetAssetServer::get_asset_server(world);
            (path, asset_server)
        };
    asset_server.load(path)
}load_embedded_asset!(world, "ssao.wesl"),
450            depth_format,
451        }
452    }
453}
454
455#[derive(#[automatically_derived]impl ::core::marker::StructuralPartialEq for
    SsaoPipelineKey {
}
#[automatically_derived]impl ::core::cmp::PartialEq for SsaoPipelineKey {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.temporal_jitter == other.temporal_jitter &&
            self.quality_level == other.quality_level
    }
}PartialEq, #[automatically_derived]impl ::core::cmp::Eq for SsaoPipelineKey {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _:
                ::core::cmp::AssertParamIsEq<ScreenSpaceAmbientOcclusionQualityLevel>;
        let _: ::core::cmp::AssertParamIsEq<bool>;
    }
}Eq, #[automatically_derived]impl ::core::hash::Hash for SsaoPipelineKey {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.quality_level, state);
        ::core::hash::Hash::hash(&self.temporal_jitter, state)
    }
}Hash, #[automatically_derived]impl ::core::clone::Clone for SsaoPipelineKey {
    #[inline]
    fn clone(&self) -> Self {
        SsaoPipelineKey {
            quality_level: ::core::clone::Clone::clone(&self.quality_level),
            temporal_jitter: ::core::clone::Clone::clone(&self.temporal_jitter),
        }
    }
}Clone)]
456struct SsaoPipelineKey {
457    quality_level: ScreenSpaceAmbientOcclusionQualityLevel,
458    temporal_jitter: bool,
459}
460
461impl SpecializedComputePipeline for SsaoPipelines {
462    type Key = SsaoPipelineKey;
463
464    fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
465        let (slice_count, samples_per_slice_side) = key.quality_level.sample_counts();
466
467        let mut shader_defs = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [ShaderDefVal::Int("SLICE_COUNT".into(), slice_count as i32),
                ShaderDefVal::Int("SAMPLES_PER_SLICE_SIDE".into(),
                    samples_per_slice_side as i32)]))vec![
468            ShaderDefVal::Int("SLICE_COUNT".into(), slice_count as i32),
469            ShaderDefVal::Int(
470                "SAMPLES_PER_SLICE_SIDE".into(),
471                samples_per_slice_side as i32,
472            ),
473        ];
474
475        if key.temporal_jitter {
476            shader_defs.push("TEMPORAL_JITTER".into());
477        }
478
479        if self.depth_format == TextureFormat::R16Float {
480            shader_defs.push("USE_R16FLOAT".into());
481        }
482
483        ComputePipelineDescriptor {
484            label: Some("ssao_ssao_pipeline".into()),
485            layout: ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [self.ssao_bind_group_layout.clone(),
                self.common_bind_group_layout.clone()]))vec![
486                self.ssao_bind_group_layout.clone(),
487                self.common_bind_group_layout.clone(),
488            ],
489            shader: self.shader.clone(),
490            shader_defs,
491            ..default()
492        }
493    }
494}
495
496fn extract_ssao_settings(
497    mut commands: Commands,
498    cameras: Extract<
499        Query<
500            (RenderEntity, &Camera, &ScreenSpaceAmbientOcclusion, &Msaa),
501            (With<Camera3d>, With<DepthPrepass>, With<NormalPrepass>),
502        >,
503    >,
504) {
505    for (entity, camera, ssao_settings, msaa) in &cameras {
506        if *msaa != Msaa::Off {
507            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event src/ssao/mod.rs:507",
                        "bevy_pbr::ssao", ::tracing::Level::ERROR,
                        ::tracing_core::__macro_support::Option::Some("src/ssao/mod.rs"),
                        ::tracing_core::__macro_support::Option::Some(507u32),
                        ::tracing_core::__macro_support::Option::Some("bevy_pbr::ssao"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::ERROR <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::ERROR <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("SSAO is being used which requires Msaa::Off, but Msaa is currently set to Msaa::{0:?}",
                                                    *msaa) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};error!(
508                "SSAO is being used which requires Msaa::Off, but Msaa is currently set to Msaa::{:?}",
509                *msaa
510            );
511            continue;
512        }
513        let mut entity_commands = commands
514            .get_entity(entity)
515            .expect("SSAO entity wasn't synced.");
516        if camera.is_active {
517            entity_commands.insert(ssao_settings.clone());
518        } else {
519            entity_commands.remove::<ScreenSpaceAmbientOcclusion>();
520        }
521    }
522}
523
524#[derive(impl bevy_ecs::component::Component for ScreenSpaceAmbientOcclusionResources
    where Self: ::core::marker::Send + ::core::marker::Sync + 'static {
    const STORAGE_TYPE: bevy_ecs::component::StorageType =
        bevy_ecs::component::StorageType::Table;
    type Mutability = bevy_ecs::component::Mutable;
    fn register_required_components(_requiree:
            bevy_ecs::component::ComponentId,
        required_components:
            &mut bevy_ecs::component::RequiredComponentsRegistrator) {}
    fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
        use bevy_ecs::component::{
            DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
        };
        (&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
    }
    fn relationship_accessor()
        ->
            ::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
        ::core::option::Option::None
    }
}Component)]
525pub struct ScreenSpaceAmbientOcclusionResources {
526    preprocessed_depth_texture: CachedTexture,
527    ssao_noisy_texture: CachedTexture, // Pre-spatially denoised texture
528    pub screen_space_ambient_occlusion_texture: CachedTexture, // Spatially denoised texture
529    depth_differences_texture: CachedTexture,
530    settings_buffer: Buffer,
531}
532
533fn prepare_ssao_textures(
534    mut commands: Commands,
535    mut texture_cache: ResMut<TextureCache>,
536    render_device: Res<RenderDevice>,
537    pipelines: Res<SsaoPipelines>,
538    views: Query<(Entity, &ExtractedCamera, &ScreenSpaceAmbientOcclusion)>,
539) {
540    for (entity, camera, ssao_settings) in &views {
541        let Some(physical_viewport_size) = camera.physical_viewport_size else {
542            continue;
543        };
544        let size = physical_viewport_size.to_extents();
545
546        let preprocessed_depth_texture = texture_cache.get(
547            &render_device,
548            TextureDescriptor {
549                label: Some("ssao_preprocessed_depth_texture"),
550                size,
551                mip_level_count: 5,
552                sample_count: 1,
553                dimension: TextureDimension::D2,
554                format: pipelines.depth_format,
555                usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
556                view_formats: &[],
557            },
558        );
559
560        let ssao_noisy_texture = texture_cache.get(
561            &render_device,
562            TextureDescriptor {
563                label: Some("ssao_noisy_texture"),
564                size,
565                mip_level_count: 1,
566                sample_count: 1,
567                dimension: TextureDimension::D2,
568                format: pipelines.depth_format,
569                usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
570                view_formats: &[],
571            },
572        );
573
574        let ssao_texture = texture_cache.get(
575            &render_device,
576            TextureDescriptor {
577                label: Some("ssao_texture"),
578                size,
579                mip_level_count: 1,
580                sample_count: 1,
581                dimension: TextureDimension::D2,
582                format: pipelines.depth_format,
583                usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
584                view_formats: &[],
585            },
586        );
587
588        let depth_differences_texture = texture_cache.get(
589            &render_device,
590            TextureDescriptor {
591                label: Some("ssao_depth_differences_texture"),
592                size,
593                mip_level_count: 1,
594                sample_count: 1,
595                dimension: TextureDimension::D2,
596                format: TextureFormat::R32Uint,
597                usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
598                view_formats: &[],
599            },
600        );
601
602        let mut settings_uniform_buffer = encase::UniformBuffer::new(Vec::new());
603        settings_uniform_buffer
604            .write(&SsaoSettingsUniform {
605                radius: ssao_settings.radius,
606                constant_object_thickness: ssao_settings.constant_object_thickness,
607            })
608            .unwrap();
609
610        let settings_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
611            label: Some("ssao_settings_buffer"),
612            contents: settings_uniform_buffer.as_ref(),
613            usage: BufferUsages::UNIFORM,
614        });
615
616        commands
617            .entity(entity)
618            .insert(ScreenSpaceAmbientOcclusionResources {
619                preprocessed_depth_texture,
620                ssao_noisy_texture,
621                screen_space_ambient_occlusion_texture: ssao_texture,
622                depth_differences_texture,
623                settings_buffer,
624            });
625    }
626}
627
628/// A render world component that holds the cached pipeline id for Ssao.
629#[derive(impl bevy_ecs::component::Component for SsaoPipelineId where
    Self: ::core::marker::Send + ::core::marker::Sync + 'static {
    const STORAGE_TYPE: bevy_ecs::component::StorageType =
        bevy_ecs::component::StorageType::Table;
    type Mutability = bevy_ecs::component::Mutable;
    fn register_required_components(_requiree:
            bevy_ecs::component::ComponentId,
        required_components:
            &mut bevy_ecs::component::RequiredComponentsRegistrator) {}
    fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
        use bevy_ecs::component::{
            DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
        };
        (&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
    }
    fn relationship_accessor()
        ->
            ::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
        ::core::option::Option::None
    }
}Component)]
630pub struct SsaoPipelineId(pub CachedComputePipelineId);
631
632fn prepare_ssao_pipelines(
633    mut commands: Commands,
634    pipeline_cache: Res<PipelineCache>,
635    mut pipelines: ResMut<SpecializedComputePipelines<SsaoPipelines>>,
636    pipeline: Res<SsaoPipelines>,
637    views: Query<(Entity, &ScreenSpaceAmbientOcclusion, Has<TemporalJitter>)>,
638) {
639    for (entity, ssao_settings, temporal_jitter) in &views {
640        let pipeline_id = pipelines.specialize(
641            &pipeline_cache,
642            &pipeline,
643            SsaoPipelineKey {
644                quality_level: ssao_settings.quality_level,
645                temporal_jitter,
646            },
647        );
648
649        commands.entity(entity).insert(SsaoPipelineId(pipeline_id));
650    }
651}
652
653/// A render world component that stores the bind groups necessary to perform
654/// Screen Space Ambient Occlusion.
655///
656/// This is stored on each view.
657#[derive(impl bevy_ecs::component::Component for SsaoBindGroups where
    Self: ::core::marker::Send + ::core::marker::Sync + 'static {
    const STORAGE_TYPE: bevy_ecs::component::StorageType =
        bevy_ecs::component::StorageType::Table;
    type Mutability = bevy_ecs::component::Mutable;
    fn register_required_components(_requiree:
            bevy_ecs::component::ComponentId,
        required_components:
            &mut bevy_ecs::component::RequiredComponentsRegistrator) {}
    fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
        use bevy_ecs::component::{
            DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
        };
        (&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
    }
    fn relationship_accessor()
        ->
            ::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
        ::core::option::Option::None
    }
}Component)]
658pub struct SsaoBindGroups {
659    pub common_bind_group: BindGroup,
660    pub preprocess_depth_bind_group: BindGroup,
661    pub ssao_bind_group: BindGroup,
662    pub spatial_denoise_bind_group: BindGroup,
663}
664
665fn prepare_ssao_bind_groups(
666    mut commands: Commands,
667    render_device: Res<RenderDevice>,
668    pipelines: Res<SsaoPipelines>,
669    view_uniforms: Res<ViewUniforms>,
670    global_uniforms: Res<GlobalsBuffer>,
671    pipeline_cache: Res<PipelineCache>,
672    views: Query<(
673        Entity,
674        &ScreenSpaceAmbientOcclusionResources,
675        &ViewPrepassTextures,
676    )>,
677) {
678    let (Some(view_uniforms), Some(globals_uniforms)) = (
679        view_uniforms.uniforms.binding(),
680        global_uniforms.buffer.binding(),
681    ) else {
682        return;
683    };
684
685    for (entity, ssao_resources, prepass_textures) in &views {
686        let Some(depth_view) = prepass_textures.depth_only_view() else {
687            continue;
688        };
689        let common_bind_group = render_device.create_bind_group(
690            "ssao_common_bind_group",
691            &pipeline_cache.get_bind_group_layout(&pipelines.common_bind_group_layout),
692            &BindGroupEntries::sequential((
693                &pipelines.point_clamp_sampler,
694                &pipelines.linear_clamp_sampler,
695                view_uniforms.clone(),
696            )),
697        );
698
699        let create_depth_view = |mip_level| {
700            ssao_resources
701                .preprocessed_depth_texture
702                .texture
703                .create_view(&TextureViewDescriptor {
704                    label: Some("ssao_preprocessed_depth_texture_mip_view"),
705                    base_mip_level: mip_level,
706                    format: Some(pipelines.depth_format),
707                    dimension: Some(TextureViewDimension::D2),
708                    mip_level_count: Some(1),
709                    ..default()
710                })
711        };
712
713        let preprocess_depth_bind_group = render_device.create_bind_group(
714            "ssao_preprocess_depth_bind_group",
715            &pipeline_cache.get_bind_group_layout(&pipelines.preprocess_depth_bind_group_layout),
716            &BindGroupEntries::sequential((
717                depth_view,
718                &create_depth_view(0),
719                &create_depth_view(1),
720                &create_depth_view(2),
721                &create_depth_view(3),
722                &create_depth_view(4),
723            )),
724        );
725
726        let ssao_bind_group = render_device.create_bind_group(
727            "ssao_ssao_bind_group",
728            &pipeline_cache.get_bind_group_layout(&pipelines.ssao_bind_group_layout),
729            &BindGroupEntries::sequential((
730                &ssao_resources.preprocessed_depth_texture.default_view,
731                prepass_textures.normal_view().unwrap(),
732                &pipelines.hilbert_index_lut,
733                &ssao_resources.ssao_noisy_texture.default_view,
734                &ssao_resources.depth_differences_texture.default_view,
735                globals_uniforms.clone(),
736                ssao_resources.settings_buffer.as_entire_binding(),
737            )),
738        );
739
740        let spatial_denoise_bind_group = render_device.create_bind_group(
741            "ssao_spatial_denoise_bind_group",
742            &pipeline_cache.get_bind_group_layout(&pipelines.spatial_denoise_bind_group_layout),
743            &BindGroupEntries::sequential((
744                &ssao_resources.ssao_noisy_texture.default_view,
745                &ssao_resources.depth_differences_texture.default_view,
746                &ssao_resources
747                    .screen_space_ambient_occlusion_texture
748                    .default_view,
749            )),
750        );
751
752        commands.entity(entity).insert(SsaoBindGroups {
753            common_bind_group,
754            preprocess_depth_bind_group,
755            ssao_bind_group,
756            spatial_denoise_bind_group,
757        });
758    }
759}
760
761fn generate_hilbert_index_lut() -> [[u16; 64]; 64] {
762    use core::array::from_fn;
763    from_fn(|x| from_fn(|y| hilbert_index(x as u16, y as u16)))
764}
765
766// https://www.shadertoy.com/view/3tB3z3
767const HILBERT_WIDTH: u16 = 64;
768fn hilbert_index(mut x: u16, mut y: u16) -> u16 {
769    let mut index = 0;
770
771    let mut level: u16 = HILBERT_WIDTH / 2;
772    while level > 0 {
773        let region_x = (x & level > 0) as u16;
774        let region_y = (y & level > 0) as u16;
775        index += level * level * ((3 * region_x) ^ region_y);
776
777        if region_y == 0 {
778            if region_x == 1 {
779                x = HILBERT_WIDTH - 1 - x;
780                y = HILBERT_WIDTH - 1 - y;
781            }
782
783            mem::swap(&mut x, &mut y);
784        }
785
786        level /= 2;
787    }
788
789    index
790}