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(),
                    "ssao_utils.wgsl".as_ref())
            };
        let watched_path =
            ::bevy_asset::io::embedded::watched_path("src/ssao/mod.rs",
                "ssao_utils.wgsl");
        embedded.insert_asset(watched_path, &path,
            b"#define_import_path bevy_pbr::ssao_utils\n\n#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(),
                                "ssao_utils.wgsl".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, "ssao_utils.wgsl");
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.wgsl".as_ref())
            };
        let watched_path =
            ::bevy_asset::io::embedded::watched_path("src/ssao/mod.rs",
                "preprocess_depth.wgsl");
        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\n#import bevy_render::view::View\n\n@group(0) @binding(0) var input_depth: texture_depth_2d;\n#ifdef 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#else\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#endif\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\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 depths_uv = vec2<f32>(pixel_coordinates0) / view.viewport.zw;\n    let depths = textureGather(0, 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.wgsl");
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.wgsl".as_ref())
            };
        let watched_path =
            ::bevy_asset::io::embedded::watched_path("src/ssao/mod.rs",
                "ssao.wgsl");
        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\n#import bevy_render::maths::fast_acos\n\n#import 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#ifdef 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#endif\n@group(0) @binding(4) var depth_differences: texture_storage_2d<r32uint, write>;\n@group(0) @binding(5) var<uniform> globals: Globals;\n@group(0) @binding(6) var<uniform> thickness: f32;\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#ifdef TEMPORAL_JITTER\n    index += 288u * (globals.frame_count % 64u);\n#endif\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: vec2<f32>) -> vec3<f32> {\n    var world_normal = textureSampleLevel(normals, point_clamp_sampler, uv, 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 * 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, 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(#SLICE_COUNT);\n    let samples_per_slice_side = f32(#SAMPLES_PER_SLICE_SIDE);\n    let effect_radius = 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 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 pixel_normal = load_normal_view_space(uv);\n    let view_vec = normalize(-pixel_position);\n\n    let noise = load_noise(pixel_coordinates);\n    let sample_scale = (-0.5 * effect_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.wgsl");
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.wgsl".as_ref())
            };
        let watched_path =
            ::bevy_asset::io::embedded::watched_path("src/ssao/mod.rs",
                "spatial_denoise.wgsl");
        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\n#import 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#ifdef 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#endif\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 = visibility0.z;\n    let top_visibility = visibility1.x;\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;\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.wgsl");
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_render::extract_component::ExtractComponent 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>(bevy_reflect::FromType::<Self>::from_type());
                registration.insert::<bevy_reflect::ReflectFromReflect>(bevy_reflect::FromType::<Self>::from_type());
                registration.register_type_data::<ReflectComponent, Self>();
                registration.register_type_data::<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]
            fn type_info() -> &'static bevy_reflect::TypeInfo {
                static CELL: bevy_reflect::utility::NonGenericTypeInfoCell =
                    bevy_reflect::utility::NonGenericTypeInfoCell::new();
                CELL.get_or_set(||
                        {
                            bevy_reflect::TypeInfo::Struct(bevy_reflect::structs::StructInfo::new::<Self>(&[bevy_reflect::NamedField::new::<ScreenSpaceAmbientOcclusionQualityLevel>("quality_level"),
                                                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),
                    "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),
                    "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.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.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("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),
                    "constant_object_thickness" =>
                        ::core::option::Option::Some(1usize),
                    _ => ::core::option::Option::None,
                }
            }
            fn field_len(&self) -> usize { 2usize }
            fn iter_fields(&self) -> bevy_reflect::structs::FieldIter {
                bevy_reflect::structs::FieldIter::new(self)
            }
            fn to_dynamic_struct(&self)
                -> bevy_reflect::structs::DynamicStruct {
                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("constant_object_thickness",
                    bevy_reflect::PartialReflect::to_dynamic(&self.constant_object_thickness));
                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,
                                                    "constant_object_thickness")?))() {
                        __this.constant_object_thickness = __field;
                    }
                    ::core::option::Option::Some(__this)
                } else { ::core::option::Option::None }
            }
        }
    };Reflect, #[automatically_derived]
impl ::core::cmp::PartialEq for ScreenSpaceAmbientOcclusion {
    #[inline]
    fn eq(&self, other: &ScreenSpaceAmbientOcclusion) -> bool {
        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) -> ScreenSpaceAmbientOcclusion {
        ScreenSpaceAmbientOcclusion {
            quality_level: ::core::clone::Clone::clone(&self.quality_level),
            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_field2_finish(f,
            "ScreenSpaceAmbientOcclusion", "quality_level",
            &self.quality_level, "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")]
116pub struct ScreenSpaceAmbientOcclusion {
117    /// Quality of the SSAO effect.
118    pub quality_level: ScreenSpaceAmbientOcclusionQualityLevel,
119    /// A constant estimated thickness of objects.
120    ///
121    /// This value is used to decide how far behind an object a ray of light needs to be in order
122    /// to pass behind it. Any ray closer than that will be occluded.
123    pub constant_object_thickness: f32,
124}
125
126impl Default for ScreenSpaceAmbientOcclusion {
127    fn default() -> Self {
128        Self {
129            quality_level: ScreenSpaceAmbientOcclusionQualityLevel::default(),
130            constant_object_thickness: 0.25,
131        }
132    }
133}
134
135#[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>(bevy_reflect::FromType::<Self>::from_type());
                registration.insert::<bevy_reflect::ReflectFromReflect>(bevy_reflect::FromType::<Self>::from_type());
                registration.register_type_data::<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]
            fn type_info() -> &'static bevy_reflect::TypeInfo {
                static CELL: bevy_reflect::utility::NonGenericTypeInfoCell =
                    bevy_reflect::utility::NonGenericTypeInfoCell::new();
                CELL.get_or_set(||
                        {
                            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::panic("internal error: entered unreachable code"),
                }
            }
            #[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::panic("internal error: entered unreachable code"),
                }
            }
            #[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::panic("internal error: entered unreachable code"),
                }
            }
            fn to_dynamic_enum(&self) -> bevy_reflect::enums::DynamicEnum {
                bevy_reflect::enums::DynamicEnum::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::cmp::PartialEq for ScreenSpaceAmbientOcclusionQualityLevel {
    #[inline]
    fn eq(&self, other: &ScreenSpaceAmbientOcclusionQualityLevel) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (ScreenSpaceAmbientOcclusionQualityLevel::Custom {
                    slice_count: __self_0, samples_per_slice_side: __self_1 },
                    ScreenSpaceAmbientOcclusionQualityLevel::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) {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        ::core::hash::Hash::hash(&__self_discr, state);
        match self {
            ScreenSpaceAmbientOcclusionQualityLevel::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]
impl ::core::clone::Clone for ScreenSpaceAmbientOcclusionQualityLevel {
    #[inline]
    fn clone(&self) -> ScreenSpaceAmbientOcclusionQualityLevel {
        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() -> ScreenSpaceAmbientOcclusionQualityLevel { 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 {
            ScreenSpaceAmbientOcclusionQualityLevel::Low =>
                ::core::fmt::Formatter::write_str(f, "Low"),
            ScreenSpaceAmbientOcclusionQualityLevel::Medium =>
                ::core::fmt::Formatter::write_str(f, "Medium"),
            ScreenSpaceAmbientOcclusionQualityLevel::High =>
                ::core::fmt::Formatter::write_str(f, "High"),
            ScreenSpaceAmbientOcclusionQualityLevel::Ultra =>
                ::core::fmt::Formatter::write_str(f, "Ultra"),
            ScreenSpaceAmbientOcclusionQualityLevel::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)]
136#[reflect(PartialEq, Hash, Clone, Default)]
137pub enum ScreenSpaceAmbientOcclusionQualityLevel {
138    Low,
139    Medium,
140    #[default]
141    High,
142    Ultra,
143    Custom {
144        /// Higher slice count means less noise, but worse performance.
145        slice_count: u32,
146        /// Samples per slice side is also tweakable, but recommended to be left at 2 or 3.
147        samples_per_slice_side: u32,
148    },
149}
150
151impl ScreenSpaceAmbientOcclusionQualityLevel {
152    fn sample_counts(&self) -> (u32, u32) {
153        match self {
154            Self::Low => (1, 2),    // 4 spp (1 * (2 * 2)), plus optional temporal samples
155            Self::Medium => (2, 2), // 8 spp (2 * (2 * 2)), plus optional temporal samples
156            Self::High => (3, 3),   // 18 spp (3 * (3 * 2)), plus optional temporal samples
157            Self::Ultra => (9, 3),  // 54 spp (9 * (3 * 2)), plus optional temporal samples
158            Self::Custom {
159                slice_count: slices,
160                samples_per_slice_side,
161            } => (*slices, *samples_per_slice_side),
162        }
163    }
164}
165
166fn ssao(
167    view: ViewQuery<(
168        &ExtractedCamera,
169        &SsaoPipelineId,
170        &SsaoBindGroups,
171        &ViewUniformOffset,
172    )>,
173    pipelines: Res<SsaoPipelines>,
174    pipeline_cache: Res<PipelineCache>,
175    mut ctx: RenderContext,
176) {
177    let (camera, pipeline_id, bind_groups, view_uniform_offset) = view.into_inner();
178
179    let (
180        Some(camera_size),
181        Some(preprocess_depth_pipeline),
182        Some(spatial_denoise_pipeline),
183        Some(ssao_pipeline),
184    ) = (
185        camera.physical_viewport_size,
186        pipeline_cache.get_compute_pipeline(pipelines.preprocess_depth_pipeline),
187        pipeline_cache.get_compute_pipeline(pipelines.spatial_denoise_pipeline),
188        pipeline_cache.get_compute_pipeline(pipeline_id.0),
189    )
190    else {
191        return;
192    };
193
194    let diagnostics = ctx.diagnostic_recorder();
195    let diagnostics = diagnostics.as_deref();
196    let time_span = diagnostics.time_span(ctx.command_encoder(), "ssao");
197
198    let command_encoder = ctx.command_encoder();
199    command_encoder.push_debug_group("ssao");
200
201    {
202        let mut preprocess_depth_pass =
203            command_encoder.begin_compute_pass(&ComputePassDescriptor {
204                label: Some("ssao_preprocess_depth"),
205                timestamp_writes: None,
206            });
207        preprocess_depth_pass.set_pipeline(preprocess_depth_pipeline);
208        preprocess_depth_pass.set_bind_group(0, &bind_groups.preprocess_depth_bind_group, &[]);
209        preprocess_depth_pass.set_bind_group(
210            1,
211            &bind_groups.common_bind_group,
212            &[view_uniform_offset.offset],
213        );
214        preprocess_depth_pass.dispatch_workgroups(
215            camera_size.x.div_ceil(16),
216            camera_size.y.div_ceil(16),
217            1,
218        );
219    }
220
221    {
222        let mut ssao_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
223            label: Some("ssao"),
224            timestamp_writes: None,
225        });
226        ssao_pass.set_pipeline(ssao_pipeline);
227        ssao_pass.set_bind_group(0, &bind_groups.ssao_bind_group, &[]);
228        ssao_pass.set_bind_group(
229            1,
230            &bind_groups.common_bind_group,
231            &[view_uniform_offset.offset],
232        );
233        ssao_pass.dispatch_workgroups(camera_size.x.div_ceil(8), camera_size.y.div_ceil(8), 1);
234    }
235
236    {
237        let mut spatial_denoise_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
238            label: Some("ssao_spatial_denoise"),
239            timestamp_writes: None,
240        });
241        spatial_denoise_pass.set_pipeline(spatial_denoise_pipeline);
242        spatial_denoise_pass.set_bind_group(0, &bind_groups.spatial_denoise_bind_group, &[]);
243        spatial_denoise_pass.set_bind_group(
244            1,
245            &bind_groups.common_bind_group,
246            &[view_uniform_offset.offset],
247        );
248        spatial_denoise_pass.dispatch_workgroups(
249            camera_size.x.div_ceil(8),
250            camera_size.y.div_ceil(8),
251            1,
252        );
253    }
254
255    command_encoder.pop_debug_group();
256    time_span.end(ctx.command_encoder());
257}
258
259#[derive(impl bevy_ecs::resource::Resource for SsaoPipelines where
    Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource)]
260struct SsaoPipelines {
261    preprocess_depth_pipeline: CachedComputePipelineId,
262    spatial_denoise_pipeline: CachedComputePipelineId,
263
264    common_bind_group_layout: BindGroupLayoutDescriptor,
265    preprocess_depth_bind_group_layout: BindGroupLayoutDescriptor,
266    ssao_bind_group_layout: BindGroupLayoutDescriptor,
267    spatial_denoise_bind_group_layout: BindGroupLayoutDescriptor,
268
269    hilbert_index_lut: TextureView,
270    point_clamp_sampler: Sampler,
271    linear_clamp_sampler: Sampler,
272
273    shader: Handle<Shader>,
274    depth_format: TextureFormat,
275}
276
277impl FromWorld for SsaoPipelines {
278    fn from_world(world: &mut World) -> Self {
279        let render_device = world.resource::<RenderDevice>();
280        let render_queue = world.resource::<RenderQueue>();
281        let pipeline_cache = world.resource::<PipelineCache>();
282
283        // Detect the depth format support
284        let render_adapter = world.resource::<RenderAdapter>();
285        let depth_format = if render_adapter
286            .get_texture_format_features(TextureFormat::R16Float)
287            .allowed_usages
288            .contains(TextureUsages::STORAGE_BINDING)
289        {
290            TextureFormat::R16Float
291        } else {
292            TextureFormat::R32Float
293        };
294
295        let hilbert_index_lut = render_device
296            .create_texture_with_data(
297                render_queue,
298                &(TextureDescriptor {
299                    label: Some("ssao_hilbert_index_lut"),
300                    size: Extent3d {
301                        width: HILBERT_WIDTH as u32,
302                        height: HILBERT_WIDTH as u32,
303                        depth_or_array_layers: 1,
304                    },
305                    mip_level_count: 1,
306                    sample_count: 1,
307                    dimension: TextureDimension::D2,
308                    format: TextureFormat::R16Uint,
309                    usage: TextureUsages::TEXTURE_BINDING,
310                    view_formats: &[],
311                }),
312                TextureDataOrder::default(),
313                bytemuck::cast_slice(&generate_hilbert_index_lut()),
314            )
315            .create_view(&TextureViewDescriptor::default());
316
317        let point_clamp_sampler = render_device.create_sampler(&SamplerDescriptor {
318            min_filter: FilterMode::Nearest,
319            mag_filter: FilterMode::Nearest,
320            mipmap_filter: MipmapFilterMode::Nearest,
321            address_mode_u: AddressMode::ClampToEdge,
322            address_mode_v: AddressMode::ClampToEdge,
323            ..Default::default()
324        });
325        let linear_clamp_sampler = render_device.create_sampler(&SamplerDescriptor {
326            min_filter: FilterMode::Linear,
327            mag_filter: FilterMode::Linear,
328            mipmap_filter: MipmapFilterMode::Nearest,
329            address_mode_u: AddressMode::ClampToEdge,
330            address_mode_v: AddressMode::ClampToEdge,
331            ..Default::default()
332        });
333
334        let common_bind_group_layout = BindGroupLayoutDescriptor::new(
335            "ssao_common_bind_group_layout",
336            &BindGroupLayoutEntries::sequential(
337                ShaderStages::COMPUTE,
338                (
339                    sampler(SamplerBindingType::NonFiltering),
340                    sampler(SamplerBindingType::Filtering),
341                    uniform_buffer::<ViewUniform>(true),
342                ),
343            ),
344        );
345
346        let preprocess_depth_bind_group_layout = BindGroupLayoutDescriptor::new(
347            "ssao_preprocess_depth_bind_group_layout",
348            &BindGroupLayoutEntries::sequential(
349                ShaderStages::COMPUTE,
350                (
351                    texture_depth_2d(),
352                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
353                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
354                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
355                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
356                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
357                ),
358            ),
359        );
360
361        let ssao_bind_group_layout = BindGroupLayoutDescriptor::new(
362            "ssao_ssao_bind_group_layout",
363            &BindGroupLayoutEntries::sequential(
364                ShaderStages::COMPUTE,
365                (
366                    texture_2d(TextureSampleType::Float { filterable: true }),
367                    texture_2d(TextureSampleType::Float { filterable: false }),
368                    texture_2d(TextureSampleType::Uint),
369                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
370                    texture_storage_2d(TextureFormat::R32Uint, StorageTextureAccess::WriteOnly),
371                    uniform_buffer::<GlobalsUniform>(false),
372                    uniform_buffer::<f32>(false),
373                ),
374            ),
375        );
376
377        let spatial_denoise_bind_group_layout = BindGroupLayoutDescriptor::new(
378            "ssao_spatial_denoise_bind_group_layout",
379            &BindGroupLayoutEntries::sequential(
380                ShaderStages::COMPUTE,
381                (
382                    texture_2d(TextureSampleType::Float { filterable: false }),
383                    texture_2d(TextureSampleType::Uint),
384                    texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly),
385                ),
386            ),
387        );
388
389        let mut shader_defs = Vec::new();
390        if depth_format == TextureFormat::R16Float {
391            shader_defs.push("USE_R16FLOAT".into());
392        }
393
394        let preprocess_depth_pipeline =
395            pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
396                label: Some("ssao_preprocess_depth_pipeline".into()),
397                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![
398                    preprocess_depth_bind_group_layout.clone(),
399                    common_bind_group_layout.clone(),
400                ],
401                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.wgsl".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.wgsl"),
402                shader_defs: shader_defs.clone(),
403                ..default()
404            });
405
406        let spatial_denoise_pipeline =
407            pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
408                label: Some("ssao_spatial_denoise_pipeline".into()),
409                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![
410                    spatial_denoise_bind_group_layout.clone(),
411                    common_bind_group_layout.clone(),
412                ],
413                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.wgsl".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.wgsl"),
414                shader_defs,
415                ..default()
416            });
417
418        Self {
419            preprocess_depth_pipeline,
420            spatial_denoise_pipeline,
421
422            common_bind_group_layout,
423            preprocess_depth_bind_group_layout,
424            ssao_bind_group_layout,
425            spatial_denoise_bind_group_layout,
426
427            hilbert_index_lut,
428            point_clamp_sampler,
429            linear_clamp_sampler,
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(),
                            "ssao.wgsl".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.wgsl"),
432            depth_format,
433        }
434    }
435}
436
437#[derive(#[automatically_derived]
impl ::core::cmp::PartialEq for SsaoPipelineKey {
    #[inline]
    fn eq(&self, other: &SsaoPipelineKey) -> 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) -> SsaoPipelineKey {
        SsaoPipelineKey {
            quality_level: ::core::clone::Clone::clone(&self.quality_level),
            temporal_jitter: ::core::clone::Clone::clone(&self.temporal_jitter),
        }
    }
}Clone)]
438struct SsaoPipelineKey {
439    quality_level: ScreenSpaceAmbientOcclusionQualityLevel,
440    temporal_jitter: bool,
441}
442
443impl SpecializedComputePipeline for SsaoPipelines {
444    type Key = SsaoPipelineKey;
445
446    fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
447        let (slice_count, samples_per_slice_side) = key.quality_level.sample_counts();
448
449        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".to_string(), slice_count as i32),
                ShaderDefVal::Int("SAMPLES_PER_SLICE_SIDE".to_string(),
                    samples_per_slice_side as i32)]))vec![
450            ShaderDefVal::Int("SLICE_COUNT".to_string(), slice_count as i32),
451            ShaderDefVal::Int(
452                "SAMPLES_PER_SLICE_SIDE".to_string(),
453                samples_per_slice_side as i32,
454            ),
455        ];
456
457        if key.temporal_jitter {
458            shader_defs.push("TEMPORAL_JITTER".into());
459        }
460
461        if self.depth_format == TextureFormat::R16Float {
462            shader_defs.push("USE_R16FLOAT".into());
463        }
464
465        ComputePipelineDescriptor {
466            label: Some("ssao_ssao_pipeline".into()),
467            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![
468                self.ssao_bind_group_layout.clone(),
469                self.common_bind_group_layout.clone(),
470            ],
471            shader: self.shader.clone(),
472            shader_defs,
473            ..default()
474        }
475    }
476}
477
478fn extract_ssao_settings(
479    mut commands: Commands,
480    cameras: Extract<
481        Query<
482            (RenderEntity, &Camera, &ScreenSpaceAmbientOcclusion, &Msaa),
483            (With<Camera3d>, With<DepthPrepass>, With<NormalPrepass>),
484        >,
485    >,
486) {
487    for (entity, camera, ssao_settings, msaa) in &cameras {
488        if *msaa != Msaa::Off {
489            {
    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:489",
                        "bevy_pbr::ssao", ::tracing::Level::ERROR,
                        ::tracing_core::__macro_support::Option::Some("src/ssao/mod.rs"),
                        ::tracing_core::__macro_support::Option::Some(489u32),
                        ::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!(
490                "SSAO is being used which requires Msaa::Off, but Msaa is currently set to Msaa::{:?}",
491                *msaa
492            );
493            return;
494        }
495        let mut entity_commands = commands
496            .get_entity(entity)
497            .expect("SSAO entity wasn't synced.");
498        if camera.is_active {
499            entity_commands.insert(ssao_settings.clone());
500        } else {
501            entity_commands.remove::<ScreenSpaceAmbientOcclusion>();
502        }
503    }
504}
505
506#[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)]
507pub struct ScreenSpaceAmbientOcclusionResources {
508    preprocessed_depth_texture: CachedTexture,
509    ssao_noisy_texture: CachedTexture, // Pre-spatially denoised texture
510    pub screen_space_ambient_occlusion_texture: CachedTexture, // Spatially denoised texture
511    depth_differences_texture: CachedTexture,
512    thickness_buffer: Buffer,
513}
514
515fn prepare_ssao_textures(
516    mut commands: Commands,
517    mut texture_cache: ResMut<TextureCache>,
518    render_device: Res<RenderDevice>,
519    pipelines: Res<SsaoPipelines>,
520    views: Query<(Entity, &ExtractedCamera, &ScreenSpaceAmbientOcclusion)>,
521) {
522    for (entity, camera, ssao_settings) in &views {
523        let Some(physical_viewport_size) = camera.physical_viewport_size else {
524            continue;
525        };
526        let size = physical_viewport_size.to_extents();
527
528        let preprocessed_depth_texture = texture_cache.get(
529            &render_device,
530            TextureDescriptor {
531                label: Some("ssao_preprocessed_depth_texture"),
532                size,
533                mip_level_count: 5,
534                sample_count: 1,
535                dimension: TextureDimension::D2,
536                format: pipelines.depth_format,
537                usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
538                view_formats: &[],
539            },
540        );
541
542        let ssao_noisy_texture = texture_cache.get(
543            &render_device,
544            TextureDescriptor {
545                label: Some("ssao_noisy_texture"),
546                size,
547                mip_level_count: 1,
548                sample_count: 1,
549                dimension: TextureDimension::D2,
550                format: pipelines.depth_format,
551                usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
552                view_formats: &[],
553            },
554        );
555
556        let ssao_texture = texture_cache.get(
557            &render_device,
558            TextureDescriptor {
559                label: Some("ssao_texture"),
560                size,
561                mip_level_count: 1,
562                sample_count: 1,
563                dimension: TextureDimension::D2,
564                format: pipelines.depth_format,
565                usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
566                view_formats: &[],
567            },
568        );
569
570        let depth_differences_texture = texture_cache.get(
571            &render_device,
572            TextureDescriptor {
573                label: Some("ssao_depth_differences_texture"),
574                size,
575                mip_level_count: 1,
576                sample_count: 1,
577                dimension: TextureDimension::D2,
578                format: TextureFormat::R32Uint,
579                usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
580                view_formats: &[],
581            },
582        );
583
584        let thickness_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
585            label: Some("thickness_buffer"),
586            contents: &ssao_settings.constant_object_thickness.to_le_bytes(),
587            usage: BufferUsages::UNIFORM,
588        });
589
590        commands
591            .entity(entity)
592            .insert(ScreenSpaceAmbientOcclusionResources {
593                preprocessed_depth_texture,
594                ssao_noisy_texture,
595                screen_space_ambient_occlusion_texture: ssao_texture,
596                depth_differences_texture,
597                thickness_buffer,
598            });
599    }
600}
601
602/// A render world component that holds the cached pipeline id for Ssao.
603#[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)]
604pub struct SsaoPipelineId(pub CachedComputePipelineId);
605
606fn prepare_ssao_pipelines(
607    mut commands: Commands,
608    pipeline_cache: Res<PipelineCache>,
609    mut pipelines: ResMut<SpecializedComputePipelines<SsaoPipelines>>,
610    pipeline: Res<SsaoPipelines>,
611    views: Query<(Entity, &ScreenSpaceAmbientOcclusion, Has<TemporalJitter>)>,
612) {
613    for (entity, ssao_settings, temporal_jitter) in &views {
614        let pipeline_id = pipelines.specialize(
615            &pipeline_cache,
616            &pipeline,
617            SsaoPipelineKey {
618                quality_level: ssao_settings.quality_level,
619                temporal_jitter,
620            },
621        );
622
623        commands.entity(entity).insert(SsaoPipelineId(pipeline_id));
624    }
625}
626
627/// A render world component that stores the bind groups necessary to perform
628/// Screen Space Ambient Occlusion.
629///
630/// This is stored on each view.
631#[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)]
632pub struct SsaoBindGroups {
633    pub common_bind_group: BindGroup,
634    pub preprocess_depth_bind_group: BindGroup,
635    pub ssao_bind_group: BindGroup,
636    pub spatial_denoise_bind_group: BindGroup,
637}
638
639fn prepare_ssao_bind_groups(
640    mut commands: Commands,
641    render_device: Res<RenderDevice>,
642    pipelines: Res<SsaoPipelines>,
643    view_uniforms: Res<ViewUniforms>,
644    global_uniforms: Res<GlobalsBuffer>,
645    pipeline_cache: Res<PipelineCache>,
646    views: Query<(
647        Entity,
648        &ScreenSpaceAmbientOcclusionResources,
649        &ViewPrepassTextures,
650    )>,
651) {
652    let (Some(view_uniforms), Some(globals_uniforms)) = (
653        view_uniforms.uniforms.binding(),
654        global_uniforms.buffer.binding(),
655    ) else {
656        return;
657    };
658
659    for (entity, ssao_resources, prepass_textures) in &views {
660        let common_bind_group = render_device.create_bind_group(
661            "ssao_common_bind_group",
662            &pipeline_cache.get_bind_group_layout(&pipelines.common_bind_group_layout),
663            &BindGroupEntries::sequential((
664                &pipelines.point_clamp_sampler,
665                &pipelines.linear_clamp_sampler,
666                view_uniforms.clone(),
667            )),
668        );
669
670        let create_depth_view = |mip_level| {
671            ssao_resources
672                .preprocessed_depth_texture
673                .texture
674                .create_view(&TextureViewDescriptor {
675                    label: Some("ssao_preprocessed_depth_texture_mip_view"),
676                    base_mip_level: mip_level,
677                    format: Some(pipelines.depth_format),
678                    dimension: Some(TextureViewDimension::D2),
679                    mip_level_count: Some(1),
680                    ..default()
681                })
682        };
683
684        let preprocess_depth_bind_group = render_device.create_bind_group(
685            "ssao_preprocess_depth_bind_group",
686            &pipeline_cache.get_bind_group_layout(&pipelines.preprocess_depth_bind_group_layout),
687            &BindGroupEntries::sequential((
688                prepass_textures.depth_view().unwrap(),
689                &create_depth_view(0),
690                &create_depth_view(1),
691                &create_depth_view(2),
692                &create_depth_view(3),
693                &create_depth_view(4),
694            )),
695        );
696
697        let ssao_bind_group = render_device.create_bind_group(
698            "ssao_ssao_bind_group",
699            &pipeline_cache.get_bind_group_layout(&pipelines.ssao_bind_group_layout),
700            &BindGroupEntries::sequential((
701                &ssao_resources.preprocessed_depth_texture.default_view,
702                prepass_textures.normal_view().unwrap(),
703                &pipelines.hilbert_index_lut,
704                &ssao_resources.ssao_noisy_texture.default_view,
705                &ssao_resources.depth_differences_texture.default_view,
706                globals_uniforms.clone(),
707                ssao_resources.thickness_buffer.as_entire_binding(),
708            )),
709        );
710
711        let spatial_denoise_bind_group = render_device.create_bind_group(
712            "ssao_spatial_denoise_bind_group",
713            &pipeline_cache.get_bind_group_layout(&pipelines.spatial_denoise_bind_group_layout),
714            &BindGroupEntries::sequential((
715                &ssao_resources.ssao_noisy_texture.default_view,
716                &ssao_resources.depth_differences_texture.default_view,
717                &ssao_resources
718                    .screen_space_ambient_occlusion_texture
719                    .default_view,
720            )),
721        );
722
723        commands.entity(entity).insert(SsaoBindGroups {
724            common_bind_group,
725            preprocess_depth_bind_group,
726            ssao_bind_group,
727            spatial_denoise_bind_group,
728        });
729    }
730}
731
732fn generate_hilbert_index_lut() -> [[u16; 64]; 64] {
733    use core::array::from_fn;
734    from_fn(|x| from_fn(|y| hilbert_index(x as u16, y as u16)))
735}
736
737// https://www.shadertoy.com/view/3tB3z3
738const HILBERT_WIDTH: u16 = 64;
739fn hilbert_index(mut x: u16, mut y: u16) -> u16 {
740    let mut index = 0;
741
742    let mut level: u16 = HILBERT_WIDTH / 2;
743    while level > 0 {
744        let region_x = (x & level > 0) as u16;
745        let region_y = (y & level > 0) as u16;
746        index += level * level * ((3 * region_x) ^ region_y);
747
748        if region_y == 0 {
749            if region_x == 1 {
750                x = HILBERT_WIDTH - 1 - x;
751                y = HILBERT_WIDTH - 1 - y;
752            }
753
754            mem::swap(&mut x, &mut y);
755        }
756
757        level /= 2;
758    }
759
760    index
761}