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
42pub 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#[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 pub quality_level: ScreenSpaceAmbientOcclusionQualityLevel,
119 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 slice_count: u32,
146 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), Self::Medium => (2, 2), Self::High => (3, 3), Self::Ultra => (9, 3), 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 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, pub screen_space_ambient_occlusion_texture: CachedTexture, 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#[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#[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
737const 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}