Clean up several miscellaneous uses of weak_handle. (#19408)

# Objective

- Related to #19024.

## Solution

- This is a mix of several ways to get rid of weak handles. The primary
strategy is putting strong asset handles in resources that the rendering
code clones into its pipelines (or whatever).
- This does not handle every remaining case, but we are slowly clearing
them out.

## Testing

- `anti_aliasing` example still works.
- `fog_volumes` example still works.
This commit is contained in:
andriyDev 2025-07-07 23:45:40 -07:00 committed by GitHub
parent 5e3927ba48
commit 09ccedd244
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 144 additions and 140 deletions

View File

@ -30,9 +30,7 @@
//! //!
//! [SMAA]: https://www.iryoku.com/smaa/ //! [SMAA]: https://www.iryoku.com/smaa/
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
#[cfg(feature = "smaa_luts")] use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
use bevy_asset::load_internal_binary_asset;
use bevy_asset::{embedded_asset, load_embedded_asset, uuid_handle, AssetServer, Handle};
#[cfg(not(feature = "smaa_luts"))] #[cfg(not(feature = "smaa_luts"))]
use bevy_core_pipeline::tonemapping::lut_placeholder; use bevy_core_pipeline::tonemapping::lut_placeholder;
use bevy_core_pipeline::{ use bevy_core_pipeline::{
@ -79,13 +77,6 @@ use bevy_render::{
}; };
use bevy_utils::prelude::default; use bevy_utils::prelude::default;
/// The handle of the area LUT, a KTX2 format texture that SMAA uses internally.
const SMAA_AREA_LUT_TEXTURE_HANDLE: Handle<Image> =
uuid_handle!("569c4d67-c7fa-4958-b1af-0836023603c0");
/// The handle of the search LUT, a KTX2 format texture that SMAA uses internally.
const SMAA_SEARCH_LUT_TEXTURE_HANDLE: Handle<Image> =
uuid_handle!("43b97515-252e-4c8a-b9af-f2fc528a1c27");
/// Adds support for subpixel morphological antialiasing, or SMAA. /// Adds support for subpixel morphological antialiasing, or SMAA.
pub struct SmaaPlugin; pub struct SmaaPlugin;
@ -125,6 +116,14 @@ pub enum SmaaPreset {
Ultra, Ultra,
} }
#[derive(Resource)]
struct SmaaLuts {
/// The handle of the area LUT, a KTX2 format texture that SMAA uses internally.
area_lut: Handle<Image>,
/// The handle of the search LUT, a KTX2 format texture that SMAA uses internally.
search_lut: Handle<Image>,
}
/// A render world resource that holds all render pipeline data needed for SMAA. /// A render world resource that holds all render pipeline data needed for SMAA.
/// ///
/// There are three separate passes, so we need three separate pipelines. /// There are three separate passes, so we need three separate pipelines.
@ -292,49 +291,26 @@ impl Plugin for SmaaPlugin {
// Load the shader. // Load the shader.
embedded_asset!(app, "smaa.wgsl"); embedded_asset!(app, "smaa.wgsl");
// Load the two lookup textures. These are compressed textures in KTX2
// format.
#[cfg(feature = "smaa_luts")] #[cfg(feature = "smaa_luts")]
load_internal_binary_asset!( let smaa_luts = {
app, // Load the two lookup textures. These are compressed textures in KTX2 format.
SMAA_AREA_LUT_TEXTURE_HANDLE, embedded_asset!(app, "SMAAAreaLUT.ktx2");
"SMAAAreaLUT.ktx2", embedded_asset!(app, "SMAASearchLUT.ktx2");
|bytes, _: String| Image::from_buffer(
bytes,
bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2),
bevy_image::CompressedImageFormats::NONE,
false,
bevy_image::ImageSampler::Default,
bevy_asset::RenderAssetUsages::RENDER_WORLD,
)
.expect("Failed to load SMAA area LUT")
);
#[cfg(feature = "smaa_luts")]
load_internal_binary_asset!(
app,
SMAA_SEARCH_LUT_TEXTURE_HANDLE,
"SMAASearchLUT.ktx2",
|bytes, _: String| Image::from_buffer(
bytes,
bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2),
bevy_image::CompressedImageFormats::NONE,
false,
bevy_image::ImageSampler::Default,
bevy_asset::RenderAssetUsages::RENDER_WORLD,
)
.expect("Failed to load SMAA search LUT")
);
SmaaLuts {
area_lut: load_embedded_asset!(app, "SMAAAreaLUT.ktx2"),
search_lut: load_embedded_asset!(app, "SMAASearchLUT.ktx2"),
}
};
#[cfg(not(feature = "smaa_luts"))] #[cfg(not(feature = "smaa_luts"))]
app.world_mut() let smaa_luts = {
.resource_mut::<bevy_asset::Assets<Image>>() let mut images = app.world_mut().resource_mut::<bevy_asset::Assets<Image>>();
.insert(SMAA_AREA_LUT_TEXTURE_HANDLE.id(), lut_placeholder()); let handle = images.add(lut_placeholder());
SmaaLuts {
#[cfg(not(feature = "smaa_luts"))] area_lut: handle.clone(),
app.world_mut() search_lut: handle.clone(),
.resource_mut::<bevy_asset::Assets<Image>>() }
.insert(SMAA_SEARCH_LUT_TEXTURE_HANDLE.id(), lut_placeholder()); };
app.add_plugins(ExtractComponentPlugin::<Smaa>::default()) app.add_plugins(ExtractComponentPlugin::<Smaa>::default())
.register_type::<Smaa>(); .register_type::<Smaa>();
@ -344,6 +320,7 @@ impl Plugin for SmaaPlugin {
}; };
render_app render_app
.insert_resource(smaa_luts)
.init_resource::<SmaaSpecializedRenderPipelines>() .init_resource::<SmaaSpecializedRenderPipelines>()
.init_resource::<SmaaInfoUniformBuffer>() .init_resource::<SmaaInfoUniformBuffer>()
.add_systems(RenderStartup, init_smaa_pipelines) .add_systems(RenderStartup, init_smaa_pipelines)
@ -747,13 +724,14 @@ fn prepare_smaa_bind_groups(
mut commands: Commands, mut commands: Commands,
render_device: Res<RenderDevice>, render_device: Res<RenderDevice>,
smaa_pipelines: Res<SmaaPipelines>, smaa_pipelines: Res<SmaaPipelines>,
smaa_luts: Res<SmaaLuts>,
images: Res<RenderAssets<GpuImage>>, images: Res<RenderAssets<GpuImage>>,
view_targets: Query<(Entity, &SmaaTextures), (With<ExtractedView>, With<Smaa>)>, view_targets: Query<(Entity, &SmaaTextures), (With<ExtractedView>, With<Smaa>)>,
) { ) {
// Fetch the two lookup textures. These are bundled in this library. // Fetch the two lookup textures. These are bundled in this library.
let (Some(search_texture), Some(area_texture)) = ( let (Some(search_texture), Some(area_texture)) = (
images.get(&SMAA_SEARCH_LUT_TEXTURE_HANDLE), images.get(&smaa_luts.search_lut),
images.get(&SMAA_AREA_LUT_TEXTURE_HANDLE), images.get(&smaa_luts.area_lut),
) else { ) else {
return; return;
}; };

View File

@ -492,8 +492,8 @@ impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
/// ///
/// ``` /// ```
/// # use bevy_asset::{Handle, uuid_handle}; /// # use bevy_asset::{Handle, uuid_handle};
/// # type Shader = (); /// # type Image = ();
/// const SHADER: Handle<Shader> = uuid_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac"); /// const IMAGE: Handle<Image> = uuid_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac");
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! uuid_handle { macro_rules! uuid_handle {

View File

@ -12,7 +12,7 @@ use crate::core_3d::{
prepare_core_3d_depth_textures, prepare_core_3d_depth_textures,
}; };
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, uuid_handle, Handle}; use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
use bevy_derive::{Deref, DerefMut}; use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{ use bevy_ecs::{
component::Component, component::Component,
@ -51,8 +51,8 @@ use bitflags::bitflags;
use tracing::debug; use tracing::debug;
/// Identifies the `downsample_depth.wgsl` shader. /// Identifies the `downsample_depth.wgsl` shader.
pub const DOWNSAMPLE_DEPTH_SHADER_HANDLE: Handle<Shader> = #[derive(Resource, Deref)]
uuid_handle!("a09a149e-5922-4fa4-9170-3c1a13065364"); pub struct DownsampleDepthShader(Handle<Shader>);
/// The maximum number of mip levels that we can produce. /// The maximum number of mip levels that we can produce.
/// ///
@ -69,18 +69,16 @@ pub struct MipGenerationPlugin;
impl Plugin for MipGenerationPlugin { impl Plugin for MipGenerationPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
load_internal_asset!( embedded_asset!(app, "downsample_depth.wgsl");
app,
DOWNSAMPLE_DEPTH_SHADER_HANDLE, let downsample_depth_shader = load_embedded_asset!(app, "downsample_depth.wgsl");
"downsample_depth.wgsl",
Shader::from_wgsl
);
let Some(render_app) = app.get_sub_app_mut(RenderApp) else { let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return; return;
}; };
render_app render_app
.insert_resource(DownsampleDepthShader(downsample_depth_shader))
.init_resource::<SpecializedComputePipelines<DownsampleDepthPipeline>>() .init_resource::<SpecializedComputePipelines<DownsampleDepthPipeline>>()
.add_render_graph_node::<DownsampleDepthNode>(Core3d, Node3d::EarlyDownsampleDepth) .add_render_graph_node::<DownsampleDepthNode>(Core3d, Node3d::EarlyDownsampleDepth)
.add_render_graph_node::<DownsampleDepthNode>(Core3d, Node3d::LateDownsampleDepth) .add_render_graph_node::<DownsampleDepthNode>(Core3d, Node3d::LateDownsampleDepth)
@ -294,17 +292,21 @@ pub struct DownsampleDepthPipeline {
bind_group_layout: BindGroupLayout, bind_group_layout: BindGroupLayout,
/// A handle that identifies the compiled shader. /// A handle that identifies the compiled shader.
pipeline_id: Option<CachedComputePipelineId>, pipeline_id: Option<CachedComputePipelineId>,
/// The shader asset handle.
shader: Handle<Shader>,
} }
impl DownsampleDepthPipeline { impl DownsampleDepthPipeline {
/// Creates a new [`DownsampleDepthPipeline`] from a bind group layout. /// Creates a new [`DownsampleDepthPipeline`] from a bind group layout and the downsample
/// shader.
/// ///
/// This doesn't actually specialize the pipeline; that must be done /// This doesn't actually specialize the pipeline; that must be done
/// afterward. /// afterward.
fn new(bind_group_layout: BindGroupLayout) -> DownsampleDepthPipeline { fn new(bind_group_layout: BindGroupLayout, shader: Handle<Shader>) -> DownsampleDepthPipeline {
DownsampleDepthPipeline { DownsampleDepthPipeline {
bind_group_layout, bind_group_layout,
pipeline_id: None, pipeline_id: None,
shader,
} }
} }
} }
@ -335,6 +337,7 @@ fn create_downsample_depth_pipelines(
pipeline_cache: Res<PipelineCache>, pipeline_cache: Res<PipelineCache>,
mut specialized_compute_pipelines: ResMut<SpecializedComputePipelines<DownsampleDepthPipeline>>, mut specialized_compute_pipelines: ResMut<SpecializedComputePipelines<DownsampleDepthPipeline>>,
gpu_preprocessing_support: Res<GpuPreprocessingSupport>, gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
downsample_depth_shader: Res<DownsampleDepthShader>,
mut has_run: Local<bool>, mut has_run: Local<bool>,
) { ) {
// Only run once. // Only run once.
@ -368,10 +371,22 @@ fn create_downsample_depth_pipelines(
// Initialize the pipelines. // Initialize the pipelines.
let mut downsample_depth_pipelines = DownsampleDepthPipelines { let mut downsample_depth_pipelines = DownsampleDepthPipelines {
first: DownsampleDepthPipeline::new(standard_bind_group_layout.clone()), first: DownsampleDepthPipeline::new(
second: DownsampleDepthPipeline::new(standard_bind_group_layout.clone()), standard_bind_group_layout.clone(),
first_multisample: DownsampleDepthPipeline::new(multisampled_bind_group_layout.clone()), downsample_depth_shader.0.clone(),
second_multisample: DownsampleDepthPipeline::new(multisampled_bind_group_layout.clone()), ),
second: DownsampleDepthPipeline::new(
standard_bind_group_layout.clone(),
downsample_depth_shader.0.clone(),
),
first_multisample: DownsampleDepthPipeline::new(
multisampled_bind_group_layout.clone(),
downsample_depth_shader.0.clone(),
),
second_multisample: DownsampleDepthPipeline::new(
multisampled_bind_group_layout.clone(),
downsample_depth_shader.0.clone(),
),
sampler, sampler,
}; };
@ -491,7 +506,7 @@ impl SpecializedComputePipeline for DownsampleDepthPipeline {
stages: ShaderStages::COMPUTE, stages: ShaderStages::COMPUTE,
range: 0..4, range: 0..4,
}], }],
shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, shader: self.shader.clone(),
shader_defs, shader_defs,
entry_point: Some(if key.contains(DownsampleDepthPipelineKey::SECOND_PHASE) { entry_point: Some(if key.contains(DownsampleDepthPipelineKey::SECOND_PHASE) {
"downsample_depth_second".into() "downsample_depth_second".into()

View File

@ -3,7 +3,7 @@
//! Currently, this consists only of chromatic aberration. //! Currently, this consists only of chromatic aberration.
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{embedded_asset, load_embedded_asset, uuid_handle, Assets, Handle}; use bevy_asset::{embedded_asset, load_embedded_asset, Assets, Handle};
use bevy_derive::{Deref, DerefMut}; use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{ use bevy_ecs::{
component::Component, component::Component,
@ -47,13 +47,6 @@ use crate::{
FullscreenShader, FullscreenShader,
}; };
/// The handle to the default chromatic aberration lookup texture.
///
/// This is just a 3x1 image consisting of one red pixel, one green pixel, and
/// one blue pixel, in that order.
const DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE: Handle<Image> =
uuid_handle!("dc3e3307-40a1-49bb-be6d-e0634e8836b2");
/// The default chromatic aberration intensity amount, in a fraction of the /// The default chromatic aberration intensity amount, in a fraction of the
/// window size. /// window size.
const DEFAULT_CHROMATIC_ABERRATION_INTENSITY: f32 = 0.02; const DEFAULT_CHROMATIC_ABERRATION_INTENSITY: f32 = 0.02;
@ -68,6 +61,9 @@ const DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES: u32 = 8;
static DEFAULT_CHROMATIC_ABERRATION_LUT_DATA: [u8; 12] = static DEFAULT_CHROMATIC_ABERRATION_LUT_DATA: [u8; 12] =
[255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255]; [255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255];
#[derive(Resource)]
struct DefaultChromaticAberrationLut(Handle<Image>);
/// A plugin that implements a built-in postprocessing stack with some common /// A plugin that implements a built-in postprocessing stack with some common
/// effects. /// effects.
/// ///
@ -96,14 +92,14 @@ pub struct PostProcessingPlugin;
pub struct ChromaticAberration { pub struct ChromaticAberration {
/// The lookup texture that determines the color gradient. /// The lookup texture that determines the color gradient.
/// ///
/// By default, this is a 3×1 texel texture consisting of one red pixel, one /// By default (if None), this is a 3×1 texel texture consisting of one red
/// green pixel, and one blue texel, in that order. This recreates the most /// pixel, one green pixel, and one blue texel, in that order. This
/// typical chromatic aberration pattern. However, you can change it to /// recreates the most typical chromatic aberration pattern. However, you
/// achieve different artistic effects. /// can change it to achieve different artistic effects.
/// ///
/// The texture is always sampled in its vertical center, so it should /// The texture is always sampled in its vertical center, so it should
/// ordinarily have a height of 1 texel. /// ordinarily have a height of 1 texel.
pub color_lut: Handle<Image>, pub color_lut: Option<Handle<Image>>,
/// The size of the streaks around the edges of objects, as a fraction of /// The size of the streaks around the edges of objects, as a fraction of
/// the window size. /// the window size.
@ -192,9 +188,7 @@ impl Plugin for PostProcessingPlugin {
// Load the default chromatic aberration LUT. // Load the default chromatic aberration LUT.
let mut assets = app.world_mut().resource_mut::<Assets<_>>(); let mut assets = app.world_mut().resource_mut::<Assets<_>>();
assets.insert( let default_lut = assets.add(Image::new(
DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE.id(),
Image::new(
Extent3d { Extent3d {
width: 3, width: 3,
height: 1, height: 1,
@ -204,8 +198,7 @@ impl Plugin for PostProcessingPlugin {
DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(), DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(),
TextureFormat::Rgba8UnormSrgb, TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::RENDER_WORLD, RenderAssetUsages::RENDER_WORLD,
), ));
);
app.register_type::<ChromaticAberration>(); app.register_type::<ChromaticAberration>();
app.add_plugins(ExtractComponentPlugin::<ChromaticAberration>::default()); app.add_plugins(ExtractComponentPlugin::<ChromaticAberration>::default());
@ -215,6 +208,7 @@ impl Plugin for PostProcessingPlugin {
}; };
render_app render_app
.insert_resource(DefaultChromaticAberrationLut(default_lut))
.init_resource::<SpecializedRenderPipelines<PostProcessingPipeline>>() .init_resource::<SpecializedRenderPipelines<PostProcessingPipeline>>()
.init_resource::<PostProcessingUniformBuffers>() .init_resource::<PostProcessingUniformBuffers>()
.add_systems( .add_systems(
@ -258,7 +252,7 @@ impl Plugin for PostProcessingPlugin {
impl Default for ChromaticAberration { impl Default for ChromaticAberration {
fn default() -> Self { fn default() -> Self {
Self { Self {
color_lut: DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE, color_lut: None,
intensity: DEFAULT_CHROMATIC_ABERRATION_INTENSITY, intensity: DEFAULT_CHROMATIC_ABERRATION_INTENSITY,
max_samples: DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES, max_samples: DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES,
} }
@ -357,6 +351,7 @@ impl ViewNode for PostProcessingNode {
let post_processing_pipeline = world.resource::<PostProcessingPipeline>(); let post_processing_pipeline = world.resource::<PostProcessingPipeline>();
let post_processing_uniform_buffers = world.resource::<PostProcessingUniformBuffers>(); let post_processing_uniform_buffers = world.resource::<PostProcessingUniformBuffers>();
let gpu_image_assets = world.resource::<RenderAssets<GpuImage>>(); let gpu_image_assets = world.resource::<RenderAssets<GpuImage>>();
let default_lut = world.resource::<DefaultChromaticAberrationLut>();
// We need a render pipeline to be prepared. // We need a render pipeline to be prepared.
let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else { let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else {
@ -364,8 +359,12 @@ impl ViewNode for PostProcessingNode {
}; };
// We need the chromatic aberration LUT to be present. // We need the chromatic aberration LUT to be present.
let Some(chromatic_aberration_lut) = gpu_image_assets.get(&chromatic_aberration.color_lut) let Some(chromatic_aberration_lut) = gpu_image_assets.get(
else { chromatic_aberration
.color_lut
.as_ref()
.unwrap_or(&default_lut.0),
) else {
return Ok(()); return Ok(());
}; };

View File

@ -1,7 +1,7 @@
use super::resource_manager::ResourceManager; use super::resource_manager::ResourceManager;
use bevy_asset::{load_embedded_asset, Handle}; use bevy_asset::{load_embedded_asset, Handle};
use bevy_core_pipeline::{ use bevy_core_pipeline::{
core_3d::CORE_3D_DEPTH_FORMAT, experimental::mip_generation::DOWNSAMPLE_DEPTH_SHADER_HANDLE, core_3d::CORE_3D_DEPTH_FORMAT, experimental::mip_generation::DownsampleDepthShader,
FullscreenShader, FullscreenShader,
}; };
use bevy_ecs::{ use bevy_ecs::{
@ -84,6 +84,7 @@ impl FromWorld for MeshletPipelines {
.remap_1d_to_2d_dispatch_bind_group_layout .remap_1d_to_2d_dispatch_bind_group_layout
.clone(); .clone();
let downsample_depth_shader = (*world.resource::<DownsampleDepthShader>()).clone();
let vertex_state = world.resource::<FullscreenShader>().to_vertex_state(); let vertex_state = world.resource::<FullscreenShader>().to_vertex_state();
let fill_counts_layout = resource_manager.fill_counts_bind_group_layout.clone(); let fill_counts_layout = resource_manager.fill_counts_bind_group_layout.clone();
@ -230,7 +231,7 @@ impl FromWorld for MeshletPipelines {
stages: ShaderStages::COMPUTE, stages: ShaderStages::COMPUTE,
range: 0..4, range: 0..4,
}], }],
shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, shader: downsample_depth_shader.clone(),
shader_defs: vec![ shader_defs: vec![
"MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into(), "MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into(),
"MESHLET".into(), "MESHLET".into(),
@ -248,7 +249,7 @@ impl FromWorld for MeshletPipelines {
stages: ShaderStages::COMPUTE, stages: ShaderStages::COMPUTE,
range: 0..4, range: 0..4,
}], }],
shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, shader: downsample_depth_shader.clone(),
shader_defs: vec![ shader_defs: vec![
"MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into(), "MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into(),
"MESHLET".into(), "MESHLET".into(),
@ -266,7 +267,7 @@ impl FromWorld for MeshletPipelines {
stages: ShaderStages::COMPUTE, stages: ShaderStages::COMPUTE,
range: 0..4, range: 0..4,
}], }],
shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, shader: downsample_depth_shader.clone(),
shader_defs: vec!["MESHLET".into()], shader_defs: vec!["MESHLET".into()],
entry_point: Some("downsample_depth_first".into()), entry_point: Some("downsample_depth_first".into()),
..default() ..default()
@ -281,7 +282,7 @@ impl FromWorld for MeshletPipelines {
stages: ShaderStages::COMPUTE, stages: ShaderStages::COMPUTE,
range: 0..4, range: 0..4,
}], }],
shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, shader: downsample_depth_shader,
shader_defs: vec!["MESHLET".into()], shader_defs: vec!["MESHLET".into()],
entry_point: Some("downsample_depth_second".into()), entry_point: Some("downsample_depth_second".into()),
zero_initialize_workgroup_memory: false, zero_initialize_workgroup_memory: false,

View File

@ -30,12 +30,12 @@
//! [Henyey-Greenstein phase function]: https://www.pbr-book.org/4ed/Volume_Scattering/Phase_Functions#TheHenyeyndashGreensteinPhaseFunction //! [Henyey-Greenstein phase function]: https://www.pbr-book.org/4ed/Volume_Scattering/Phase_Functions#TheHenyeyndashGreensteinPhaseFunction
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{embedded_asset, Assets}; use bevy_asset::{embedded_asset, Assets, Handle};
use bevy_core_pipeline::core_3d::{ use bevy_core_pipeline::core_3d::{
graph::{Core3d, Node3d}, graph::{Core3d, Node3d},
prepare_core_3d_depth_textures, prepare_core_3d_depth_textures,
}; };
use bevy_ecs::schedule::IntoScheduleConfigs as _; use bevy_ecs::{resource::Resource, schedule::IntoScheduleConfigs as _};
use bevy_light::FogVolume; use bevy_light::FogVolume;
use bevy_math::{ use bevy_math::{
primitives::{Cuboid, Plane3d}, primitives::{Cuboid, Plane3d},
@ -48,9 +48,7 @@ use bevy_render::{
sync_component::SyncComponentPlugin, sync_component::SyncComponentPlugin,
ExtractSchedule, Render, RenderApp, RenderSystems, ExtractSchedule, Render, RenderApp, RenderSystems,
}; };
use render::{ use render::{VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer};
VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer, CUBE_MESH, PLANE_MESH,
};
use crate::graph::NodePbr; use crate::graph::NodePbr;
@ -59,13 +57,19 @@ pub mod render;
/// A plugin that implements volumetric fog. /// A plugin that implements volumetric fog.
pub struct VolumetricFogPlugin; pub struct VolumetricFogPlugin;
#[derive(Resource)]
pub struct FogAssets {
plane_mesh: Handle<Mesh>,
cube_mesh: Handle<Mesh>,
}
impl Plugin for VolumetricFogPlugin { impl Plugin for VolumetricFogPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
embedded_asset!(app, "volumetric_fog.wgsl"); embedded_asset!(app, "volumetric_fog.wgsl");
let mut meshes = app.world_mut().resource_mut::<Assets<Mesh>>(); let mut meshes = app.world_mut().resource_mut::<Assets<Mesh>>();
meshes.insert(&PLANE_MESH, Plane3d::new(Vec3::Z, Vec2::ONE).mesh().into()); let plane_mesh = meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE).mesh());
meshes.insert(&CUBE_MESH, Cuboid::new(1.0, 1.0, 1.0).mesh().into()); let cube_mesh = meshes.add(Cuboid::new(1.0, 1.0, 1.0).mesh());
app.add_plugins(SyncComponentPlugin::<FogVolume>::default()); app.add_plugins(SyncComponentPlugin::<FogVolume>::default());
@ -74,6 +78,10 @@ impl Plugin for VolumetricFogPlugin {
}; };
render_app render_app
.insert_resource(FogAssets {
plane_mesh,
cube_mesh,
})
.init_resource::<SpecializedRenderPipelines<VolumetricFogPipeline>>() .init_resource::<SpecializedRenderPipelines<VolumetricFogPipeline>>()
.init_resource::<VolumetricFogUniformBuffer>() .init_resource::<VolumetricFogUniformBuffer>()
.add_systems(ExtractSchedule, render::extract_volumetric_fog) .add_systems(ExtractSchedule, render::extract_volumetric_fog)

View File

@ -2,7 +2,7 @@
use core::array; use core::array;
use bevy_asset::{load_embedded_asset, uuid_handle, AssetId, Handle}; use bevy_asset::{load_embedded_asset, AssetId, Handle};
use bevy_color::ColorToComponents as _; use bevy_color::ColorToComponents as _;
use bevy_core_pipeline::{ use bevy_core_pipeline::{
core_3d::Camera3d, core_3d::Camera3d,
@ -54,6 +54,8 @@ use crate::{
VolumetricLight, VolumetricLight,
}; };
use super::FogAssets;
bitflags! { bitflags! {
/// Flags that describe the bind group layout used to render volumetric fog. /// Flags that describe the bind group layout used to render volumetric fog.
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
@ -77,20 +79,6 @@ bitflags! {
} }
} }
/// The plane mesh, which is used to render a fog volume that the camera is
/// inside.
///
/// This mesh is simply stretched to the size of the framebuffer, as when the
/// camera is inside a fog volume it's essentially a full-screen effect.
pub const PLANE_MESH: Handle<Mesh> = uuid_handle!("92523617-c708-4fd0-b42f-ceb4300c930b");
/// The cube mesh, which is used to render a fog volume that the camera is
/// outside.
///
/// Note that only the front faces of this cuboid will be rasterized in
/// hardware. The back faces will be calculated in the shader via raytracing.
pub const CUBE_MESH: Handle<Mesh> = uuid_handle!("4a1dd661-2d91-4377-a17a-a914e21e277e");
/// The total number of bind group layouts. /// The total number of bind group layouts.
/// ///
/// This is the total number of combinations of all /// This is the total number of combinations of all
@ -370,6 +358,7 @@ impl ViewNode for VolumetricFogNode {
return Ok(()); return Ok(());
}; };
let fog_assets = world.resource::<FogAssets>();
let render_meshes = world.resource::<RenderAssets<RenderMesh>>(); let render_meshes = world.resource::<RenderAssets<RenderMesh>>();
for view_fog_volume in view_fog_volumes.iter() { for view_fog_volume in view_fog_volumes.iter() {
@ -377,9 +366,9 @@ impl ViewNode for VolumetricFogNode {
// otherwise, pick the plane mesh. In the latter case we'll be // otherwise, pick the plane mesh. In the latter case we'll be
// effectively rendering a full-screen quad. // effectively rendering a full-screen quad.
let mesh_handle = if view_fog_volume.exterior { let mesh_handle = if view_fog_volume.exterior {
CUBE_MESH.clone() fog_assets.cube_mesh.clone()
} else { } else {
PLANE_MESH.clone() fog_assets.plane_mesh.clone()
}; };
let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(&mesh_handle.id()) let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(&mesh_handle.id())
@ -615,6 +604,7 @@ pub fn prepare_volumetric_fog_pipelines(
pipeline_cache: Res<PipelineCache>, pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<VolumetricFogPipeline>>, mut pipelines: ResMut<SpecializedRenderPipelines<VolumetricFogPipeline>>,
volumetric_lighting_pipeline: Res<VolumetricFogPipeline>, volumetric_lighting_pipeline: Res<VolumetricFogPipeline>,
fog_assets: Res<FogAssets>,
view_targets: Query< view_targets: Query<
( (
Entity, Entity,
@ -629,7 +619,7 @@ pub fn prepare_volumetric_fog_pipelines(
>, >,
meshes: Res<RenderAssets<RenderMesh>>, meshes: Res<RenderAssets<RenderMesh>>,
) { ) {
let Some(plane_mesh) = meshes.get(&PLANE_MESH) else { let Some(plane_mesh) = meshes.get(&fog_assets.plane_mesh) else {
// There's an off chance that the mesh won't be prepared yet if `RenderAssetBytesPerFrame` limiting is in use. // There's an off chance that the mesh won't be prepared yet if `RenderAssetBytesPerFrame` limiting is in use.
return; return;
}; };

View File

@ -6,7 +6,6 @@
//! [`Material2d`]: bevy::sprite::Material2d //! [`Material2d`]: bevy::sprite::Material2d
use bevy::{ use bevy::{
asset::uuid_handle,
color::palettes::basic::YELLOW, color::palettes::basic::YELLOW,
core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}, core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},
math::{ops, FloatOrd}, math::{ops, FloatOrd},
@ -129,12 +128,16 @@ pub struct ColoredMesh2d;
pub struct ColoredMesh2dPipeline { pub struct ColoredMesh2dPipeline {
/// This pipeline wraps the standard [`Mesh2dPipeline`] /// This pipeline wraps the standard [`Mesh2dPipeline`]
mesh2d_pipeline: Mesh2dPipeline, mesh2d_pipeline: Mesh2dPipeline,
/// The shader asset handle.
shader: Handle<Shader>,
} }
impl FromWorld for ColoredMesh2dPipeline { impl FromWorld for ColoredMesh2dPipeline {
fn from_world(world: &mut World) -> Self { fn from_world(world: &mut World) -> Self {
Self { Self {
mesh2d_pipeline: Mesh2dPipeline::from_world(world), mesh2d_pipeline: Mesh2dPipeline::from_world(world),
// Get the shader from the shader resource we inserted in the plugin.
shader: world.resource::<ColoredMesh2dShader>().0.clone(),
} }
} }
} }
@ -164,14 +167,14 @@ impl SpecializedRenderPipeline for ColoredMesh2dPipeline {
RenderPipelineDescriptor { RenderPipelineDescriptor {
vertex: VertexState { vertex: VertexState {
// Use our custom shader // Use our custom shader
shader: COLORED_MESH2D_SHADER_HANDLE, shader: self.shader.clone(),
// Use our custom vertex buffer // Use our custom vertex buffer
buffers: vec![vertex_layout], buffers: vec![vertex_layout],
..default() ..default()
}, },
fragment: Some(FragmentState { fragment: Some(FragmentState {
// Use our custom shader // Use our custom shader
shader: COLORED_MESH2D_SHADER_HANDLE, shader: self.shader.clone(),
targets: vec![Some(ColorTargetState { targets: vec![Some(ColorTargetState {
format, format,
blend: Some(BlendState::ALPHA_BLENDING), blend: Some(BlendState::ALPHA_BLENDING),
@ -278,9 +281,10 @@ fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
/// Plugin that renders [`ColoredMesh2d`]s /// Plugin that renders [`ColoredMesh2d`]s
pub struct ColoredMesh2dPlugin; pub struct ColoredMesh2dPlugin;
/// Handle to the custom shader with a unique random ID /// A resource holding the shader asset handle for the pipeline to take. There are many ways to get
pub const COLORED_MESH2D_SHADER_HANDLE: Handle<Shader> = /// the shader into the pipeline - this is just one option.
uuid_handle!("f48b148f-7373-4638-9900-392b3b3ccc66"); #[derive(Resource)]
struct ColoredMesh2dShader(Handle<Shader>);
/// Our custom pipeline needs its own instance storage /// Our custom pipeline needs its own instance storage
#[derive(Resource, Deref, DerefMut, Default)] #[derive(Resource, Deref, DerefMut, Default)]
@ -290,15 +294,16 @@ impl Plugin for ColoredMesh2dPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
// Load our custom shader // Load our custom shader
let mut shaders = app.world_mut().resource_mut::<Assets<Shader>>(); let mut shaders = app.world_mut().resource_mut::<Assets<Shader>>();
shaders.insert( // Here, we construct and add the shader asset manually. There are many ways to load this
&COLORED_MESH2D_SHADER_HANDLE, // shader, including `embedded_asset`/`load_embedded_asset`.
Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()), let shader = shaders.add(Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()));
);
app.add_plugins(SyncComponentPlugin::<ColoredMesh2d>::default()); app.add_plugins(SyncComponentPlugin::<ColoredMesh2d>::default());
// Register our custom draw function, and add our render systems // Register our custom draw function, and add our render systems
app.get_sub_app_mut(RenderApp) app.get_sub_app_mut(RenderApp)
.unwrap() .unwrap()
.insert_resource(ColoredMesh2dShader(shader))
.add_render_command::<Transparent2d, DrawColoredMesh2d>() .add_render_command::<Transparent2d, DrawColoredMesh2d>()
.init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>() .init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
.init_resource::<RenderColoredMesh2dInstances>() .init_resource::<RenderColoredMesh2dInstances>()

View File

@ -0,0 +1,8 @@
---
title: ChromaticAberration LUT is now Option
pull_requests: [19408]
---
The `ChromaticAberration` component `color_lut` field use to be a regular `Handle<Image>`. Now, it
is an `Option<Handle<Image>>` which falls back to the default image when `None`. For users assigning
a custom LUT, just wrap the value in `Some`.