diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index a6cdad28ca896..ec51c88d47cc6 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -31,6 +31,7 @@ pub mod contact_shadows; pub mod gltf; use bevy_light::cluster::GlobalClusterSettings; use bevy_render::{ + renderer::{RenderAdapter, RenderDevice}, sync_component::SyncComponent, view::{ RenderExtractedShadowMapVisibleEntities, RenderShadowLodOrigin, @@ -499,6 +500,25 @@ pub fn area_light_luts_placeholder() -> Image { } } +pub(crate) fn texture_format_contains_feature_flags( + format: TextureFormat, + render_device: &RenderDevice, + render_adapter: &RenderAdapter, + flags: wgpu_types::TextureFormatFeatureFlags, +) -> bool { + format + .guaranteed_format_features(render_device.features()) + .flags + .contains(flags) + || (render_device + .features() + .contains(wgpu_types::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES) + && render_adapter + .get_texture_format_features(format) + .flags + .contains(flags)) +} + impl SyncComponent for DirectionalLight { type Target = ( Self, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 5b085f48e5a56..5e51ef0443093 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -2734,6 +2734,9 @@ pub struct MeshPipeline { /// Whether mesh metadata will use uniform buffers on account of storage buffers /// being unavailable on this platform. pub metadata_use_uniform_buffers: bool, + + /// Whether depth texture is filterable. + pub depth_filterable: bool, } fn init_mesh_pipeline( @@ -2765,6 +2768,12 @@ fn init_mesh_pipeline( metadata_use_uniform_buffers: bevy_render::storage_buffers_are_unsupported( &render_device.limits(), ), + depth_filterable: texture_format_contains_feature_flags( + CORE_3D_DEPTH_FORMAT, + &render_device, + &render_adapter, + TextureFormatFeatureFlags::FILTERABLE, + ), }; commands.insert_resource(res); @@ -3570,8 +3579,9 @@ impl SpecializedMeshPipeline for MeshPipeline { #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] shader_defs.push("WEBGL2".into()); - #[cfg(feature = "experimental_pbr_pcss")] - shader_defs.push("PCSS_SAMPLERS_AVAILABLE".into()); + if cfg!(feature = "experimental_pbr_pcss") && self.depth_filterable { + shader_defs.push("PCSS_SAMPLERS_AVAILABLE".into()); + } if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".into()); diff --git a/crates/bevy_pbr/src/render/utils.wgsl b/crates/bevy_pbr/src/render/utils.wgsl index 60f1adc5c2b86..452c7a3850d8e 100644 --- a/crates/bevy_pbr/src/render/utils.wgsl +++ b/crates/bevy_pbr/src/render/utils.wgsl @@ -185,3 +185,21 @@ fn dir_to_cube_uv(dir: vec3f) -> CubeUV { fn porter_duff_over(bg: vec4, fg: vec4) -> vec4 { return vec4(mix(bg.rgb * bg.a, fg.rgb, fg.a), bg.a + fg.a * (1.0 - bg.a)); } + +// Weights can be used in `dot(textureGather(..), bilinear_weights(texel_coord))` +// to emulate linear sampling for unfilterable textures. +fn bilinear_weights(texel_coord: vec2f) -> vec4f { + let a: vec2f = fract(texel_coord - vec2f(0.5)); + let b: vec2f = vec2f(1.0) - a; + // +-----------+-----------+ + // | W=b.x*b.y | Z=a.x*b.y | + // +-----------+-----------+ + // | X=b.x*a.y | Y=a.x*a.y | + // +-----------+-----------+ + return vec4f( + b.x * a.y, // x + a.x * a.y, // y + a.x * b.y, // z + b.x * b.y, // w + ); +} diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 0b7053d64e551..bd700f60bc558 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -5,7 +5,7 @@ use core::ops::Range; use bevy_app::{App, Plugin}; use bevy_asset::{load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::{ - core_3d::{main_opaque_pass_3d, DEPTH_PREPASS_TEXTURE_SUPPORTED}, + core_3d::{main_opaque_pass_3d, CORE_3D_DEPTH_FORMAT, DEPTH_PREPASS_TEXTURE_SUPPORTED}, prepass::{DeferredPrepass, DepthPrepass}, schedule::{Core3d, Core3dSystems}, FullscreenShader, @@ -45,8 +45,9 @@ use bevy_utils::{once, prelude::default}; use tracing::info; use crate::{ - binding_arrays_are_usable, deferred::deferred_lighting, Bluenoise, MeshPipelineSystems, - MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, ViewKeyCache, + binding_arrays_are_usable, deferred::deferred_lighting, texture_format_contains_feature_flags, + Bluenoise, MeshPipelineSystems, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, + MeshViewBindGroup, ViewKeyCache, }; /// Enables screen-space reflections for a camera. @@ -188,6 +189,7 @@ pub struct ViewScreenSpaceReflectionsUniformOffset(u32); pub struct ScreenSpaceReflectionsPipelineKey { mesh_pipeline_view_key: MeshPipelineViewLayoutKey, target_format: TextureFormat, + depth_filterable: bool, } impl Plugin for ScreenSpaceReflectionsPlugin { @@ -388,6 +390,8 @@ pub fn init_screen_space_reflections_pipeline( /// Sets up screen space reflection pipelines for each applicable view. pub fn prepare_ssr_pipelines( mut commands: Commands, + render_device: Res, + render_adapter: Res, pipeline_cache: Res, view_key_cache: Res, mut pipelines: ResMut>, @@ -412,6 +416,12 @@ pub fn prepare_ssr_pipelines( ScreenSpaceReflectionsPipelineKey { mesh_pipeline_view_key: (*view_key).into(), target_format: extracted_view.target_format, + depth_filterable: texture_format_contains_feature_flags( + CORE_3D_DEPTH_FORMAT, + &render_device, + &render_adapter, + wgpu_types::TextureFormatFeatureFlags::FILTERABLE, + ), }, ); @@ -518,9 +528,9 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline { shader_defs.push("AREA_LIGHT_LUTS".into()); } - #[cfg(not(target_arch = "wasm32"))] - shader_defs.push("USE_DEPTH_SAMPLERS".into()); - + if key.depth_filterable { + shader_defs.push("USE_DEPTH_SAMPLERS".into()); + } RenderPipelineDescriptor { label: Some("SSR pipeline".into()), layout, diff --git a/crates/bevy_pbr/src/ssr/raymarch.wgsl b/crates/bevy_pbr/src/ssr/raymarch.wgsl index 20a76afa03202..370d96d432df3 100644 --- a/crates/bevy_pbr/src/ssr/raymarch.wgsl +++ b/crates/bevy_pbr/src/ssr/raymarch.wgsl @@ -27,56 +27,26 @@ #ifdef USE_DEPTH_SAMPLERS // Allows us to sample from the depth buffer with bilinear filtering. @group(2) @binding(2) var depth_linear_sampler: sampler; +#endif // Allows us to sample from the depth buffer with nearest-neighbor filtering. @group(2) @binding(3) var depth_nearest_sampler: sampler; -#endif - -// Manual depth fetch helpers used on WebGPU where depth + filtering sampler is invalid. -#ifndef USE_DEPTH_SAMPLERS -fn depth_texel_clamped(texel: vec2) -> f32 { - let dims = textureDimensions(depth_prepass_texture); - let max_coord = vec2(i32(dims.x) - 1, i32(dims.y) - 1); - let clamped = clamp(texel, vec2(0), max_coord); - return textureLoad(depth_prepass_texture, clamped, 0); -} - -fn depth_sample_nearest_clamped(uv: vec2, tex_size: vec2) -> f32 { - // Match nearest sampling by snapping to the closest texel center. - let coord = uv * tex_size - vec2(0.5); - return depth_texel_clamped(vec2(floor(coord + vec2(0.5)))); -} - -fn depth_sample_bilinear_clamped(uv: vec2, tex_size: vec2) -> f32 { - let coord = uv * tex_size - vec2(0.5); - let base = vec2(floor(coord)); - let frac = coord - floor(coord); - - let d00 = depth_texel_clamped(base); - let d10 = depth_texel_clamped(base + vec2(1, 0)); - let d01 = depth_texel_clamped(base + vec2(0, 1)); - let d11 = depth_texel_clamped(base + vec2(1, 1)); - - let d0 = mix(d00, d10, frac.x); - let d1 = mix(d01, d11, frac.x); - return mix(d0, d1, frac.y); -} -#endif fn depth_sample_linear(uv: vec2, tex_size: vec2) -> f32 { #ifdef USE_DEPTH_SAMPLERS return textureSampleLevel(depth_prepass_texture, depth_linear_sampler, uv, 0u); #else - return depth_sample_bilinear_clamped(uv, tex_size); + // Manual depth fetch helpers used on WebGPU where depth + filtering sampler is invalid. + let texel_coord = uv * vec2f(tex_size); + // load the 4 texels + let texel = textureGather(depth_prepass_texture, depth_nearest_sampler, uv); + let m = bevy_pbr::utils::bilinear_weights(texel_coord); + return dot(texel, m); #endif } fn depth_sample_nearest(uv: vec2, tex_size: vec2) -> f32 { -#ifdef USE_DEPTH_SAMPLERS return textureSampleLevel(depth_prepass_texture, depth_nearest_sampler, uv, 0u); -#else - return depth_sample_nearest_clamped(uv, tex_size); -#endif } // Main code diff --git a/crates/bevy_render/src/settings.rs b/crates/bevy_render/src/settings.rs index 07eff236f5f7d..8127e5d9e925f 100644 --- a/crates/bevy_render/src/settings.rs +++ b/crates/bevy_render/src/settings.rs @@ -148,7 +148,7 @@ impl Default for WgpuSettings { backends, power_preference, priority, - features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + features: wgpu::Features::empty(), disabled_features: None, limits, constrained_limits: None, diff --git a/examples/3d/ssr.rs b/examples/3d/ssr.rs index bddb8c9553384..cba77eef83bfb 100644 --- a/examples/3d/ssr.rs +++ b/examples/3d/ssr.rs @@ -3,6 +3,7 @@ use std::fmt; use std::ops::Range; +use bevy::render::renderer::RenderDevice; use bevy::{ anti_alias::taa::TemporalAntiAliasing, camera::Hdr, @@ -233,6 +234,7 @@ fn setup( mut water_materials: ResMut>>, asset_server: Res, app_settings: Res, + render_device: Res, ) { spawn_cube( &mut commands, @@ -250,7 +252,7 @@ fn setup( &mut meshes, &mut water_materials, ); - spawn_camera(&mut commands, &asset_server, &app_settings); + spawn_camera(&mut commands, &asset_server, &app_settings, &render_device); spawn_buttons(&mut commands, &app_settings); } @@ -409,12 +411,17 @@ fn spawn_water( } // Spawns the camera. -fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer, app_settings: &AppSettings) { +fn spawn_camera( + commands: &mut Commands, + asset_server: &AssetServer, + app_settings: &AppSettings, + render_device: &RenderDevice, +) { // Create the camera. Add an environment map and skybox so the water has // something interesting to reflect, other than the cube. Enable deferred // rendering by adding depth and deferred prepasses. Turn on FXAA to make // the scene look a little nicer. Finally, add screen space reflections. - commands.spawn(( + let mut entity = commands.spawn(( Camera3d::default(), Transform::from_translation(vec3(-1.25, 2.25, 4.5)).looking_at(Vec3::ZERO, Vec3::Y), Hdr, @@ -426,7 +433,6 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer, app_setting edge_fadeout: app_settings.edge_fadeout.clone(), ..default() }, - ScreenSpaceAmbientOcclusion::default(), EnvironmentMapLight { diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), @@ -439,6 +445,10 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer, app_setting ..default() }, )); + + if render_device.limits().max_sampled_textures_per_shader_stage >= 17 { + entity.insert(ScreenSpaceAmbientOcclusion::default()); + } } fn spawn_buttons(commands: &mut Commands, app_settings: &AppSettings) {