diff --git a/crates/bevy_anti_aliasing/src/smaa/mod.rs b/crates/bevy_anti_aliasing/src/smaa/mod.rs index d18e17da09..33a916e489 100644 --- a/crates/bevy_anti_aliasing/src/smaa/mod.rs +++ b/crates/bevy_anti_aliasing/src/smaa/mod.rs @@ -30,9 +30,7 @@ //! //! [SMAA]: https://www.iryoku.com/smaa/ use bevy_app::{App, Plugin}; -#[cfg(feature = "smaa_luts")] -use bevy_asset::load_internal_binary_asset; -use bevy_asset::{embedded_asset, load_embedded_asset, uuid_handle, AssetServer, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; #[cfg(not(feature = "smaa_luts"))] use bevy_core_pipeline::tonemapping::lut_placeholder; use bevy_core_pipeline::{ @@ -79,13 +77,6 @@ use bevy_render::{ }; 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 = - 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 = - uuid_handle!("43b97515-252e-4c8a-b9af-f2fc528a1c27"); - /// Adds support for subpixel morphological antialiasing, or SMAA. pub struct SmaaPlugin; @@ -125,6 +116,14 @@ pub enum SmaaPreset { Ultra, } +#[derive(Resource)] +struct SmaaLuts { + /// The handle of the area LUT, a KTX2 format texture that SMAA uses internally. + area_lut: Handle, + /// The handle of the search LUT, a KTX2 format texture that SMAA uses internally. + search_lut: Handle, +} + /// A render world resource that holds all render pipeline data needed for SMAA. /// /// There are three separate passes, so we need three separate pipelines. @@ -292,49 +291,26 @@ impl Plugin for SmaaPlugin { // Load the shader. embedded_asset!(app, "smaa.wgsl"); - // Load the two lookup textures. These are compressed textures in KTX2 - // format. #[cfg(feature = "smaa_luts")] - load_internal_binary_asset!( - app, - SMAA_AREA_LUT_TEXTURE_HANDLE, - "SMAAAreaLUT.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") - ); + let smaa_luts = { + // Load the two lookup textures. These are compressed textures in KTX2 format. + embedded_asset!(app, "SMAAAreaLUT.ktx2"); + embedded_asset!(app, "SMAASearchLUT.ktx2"); + SmaaLuts { + area_lut: load_embedded_asset!(app, "SMAAAreaLUT.ktx2"), + search_lut: load_embedded_asset!(app, "SMAASearchLUT.ktx2"), + } + }; #[cfg(not(feature = "smaa_luts"))] - app.world_mut() - .resource_mut::>() - .insert(SMAA_AREA_LUT_TEXTURE_HANDLE.id(), lut_placeholder()); - - #[cfg(not(feature = "smaa_luts"))] - app.world_mut() - .resource_mut::>() - .insert(SMAA_SEARCH_LUT_TEXTURE_HANDLE.id(), lut_placeholder()); + let smaa_luts = { + let mut images = app.world_mut().resource_mut::>(); + let handle = images.add(lut_placeholder()); + SmaaLuts { + area_lut: handle.clone(), + search_lut: handle.clone(), + } + }; app.add_plugins(ExtractComponentPlugin::::default()) .register_type::(); @@ -344,6 +320,7 @@ impl Plugin for SmaaPlugin { }; render_app + .insert_resource(smaa_luts) .init_resource::() .init_resource::() .add_systems(RenderStartup, init_smaa_pipelines) @@ -747,13 +724,14 @@ fn prepare_smaa_bind_groups( mut commands: Commands, render_device: Res, smaa_pipelines: Res, + smaa_luts: Res, images: Res>, view_targets: Query<(Entity, &SmaaTextures), (With, With)>, ) { // Fetch the two lookup textures. These are bundled in this library. let (Some(search_texture), Some(area_texture)) = ( - images.get(&SMAA_SEARCH_LUT_TEXTURE_HANDLE), - images.get(&SMAA_AREA_LUT_TEXTURE_HANDLE), + images.get(&smaa_luts.search_lut), + images.get(&smaa_luts.area_lut), ) else { return; }; diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index b9a20a7af8..838c618d8e 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -492,8 +492,8 @@ impl TryFrom for Handle { /// /// ``` /// # use bevy_asset::{Handle, uuid_handle}; -/// # type Shader = (); -/// const SHADER: Handle = uuid_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac"); +/// # type Image = (); +/// const IMAGE: Handle = uuid_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac"); /// ``` #[macro_export] macro_rules! uuid_handle { diff --git a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs index 1733743310..2080efdabb 100644 --- a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs @@ -12,7 +12,7 @@ use crate::core_3d::{ prepare_core_3d_depth_textures, }; 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_ecs::{ component::Component, @@ -51,8 +51,8 @@ use bitflags::bitflags; use tracing::debug; /// Identifies the `downsample_depth.wgsl` shader. -pub const DOWNSAMPLE_DEPTH_SHADER_HANDLE: Handle = - uuid_handle!("a09a149e-5922-4fa4-9170-3c1a13065364"); +#[derive(Resource, Deref)] +pub struct DownsampleDepthShader(Handle); /// The maximum number of mip levels that we can produce. /// @@ -69,18 +69,16 @@ pub struct MipGenerationPlugin; impl Plugin for MipGenerationPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - DOWNSAMPLE_DEPTH_SHADER_HANDLE, - "downsample_depth.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "downsample_depth.wgsl"); + + let downsample_depth_shader = load_embedded_asset!(app, "downsample_depth.wgsl"); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app + .insert_resource(DownsampleDepthShader(downsample_depth_shader)) .init_resource::>() .add_render_graph_node::(Core3d, Node3d::EarlyDownsampleDepth) .add_render_graph_node::(Core3d, Node3d::LateDownsampleDepth) @@ -294,17 +292,21 @@ pub struct DownsampleDepthPipeline { bind_group_layout: BindGroupLayout, /// A handle that identifies the compiled shader. pipeline_id: Option, + /// The shader asset handle. + shader: Handle, } 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 /// afterward. - fn new(bind_group_layout: BindGroupLayout) -> DownsampleDepthPipeline { + fn new(bind_group_layout: BindGroupLayout, shader: Handle) -> DownsampleDepthPipeline { DownsampleDepthPipeline { bind_group_layout, pipeline_id: None, + shader, } } } @@ -335,6 +337,7 @@ fn create_downsample_depth_pipelines( pipeline_cache: Res, mut specialized_compute_pipelines: ResMut>, gpu_preprocessing_support: Res, + downsample_depth_shader: Res, mut has_run: Local, ) { // Only run once. @@ -368,10 +371,22 @@ fn create_downsample_depth_pipelines( // Initialize the pipelines. let mut downsample_depth_pipelines = DownsampleDepthPipelines { - first: DownsampleDepthPipeline::new(standard_bind_group_layout.clone()), - second: DownsampleDepthPipeline::new(standard_bind_group_layout.clone()), - first_multisample: DownsampleDepthPipeline::new(multisampled_bind_group_layout.clone()), - second_multisample: DownsampleDepthPipeline::new(multisampled_bind_group_layout.clone()), + first: DownsampleDepthPipeline::new( + standard_bind_group_layout.clone(), + downsample_depth_shader.0.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, }; @@ -491,7 +506,7 @@ impl SpecializedComputePipeline for DownsampleDepthPipeline { stages: ShaderStages::COMPUTE, range: 0..4, }], - shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: Some(if key.contains(DownsampleDepthPipelineKey::SECOND_PHASE) { "downsample_depth_second".into() diff --git a/crates/bevy_core_pipeline/src/post_process/mod.rs b/crates/bevy_core_pipeline/src/post_process/mod.rs index 0077cebdf5..ce77fc1a75 100644 --- a/crates/bevy_core_pipeline/src/post_process/mod.rs +++ b/crates/bevy_core_pipeline/src/post_process/mod.rs @@ -3,7 +3,7 @@ //! Currently, this consists only of chromatic aberration. 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_ecs::{ component::Component, @@ -47,13 +47,6 @@ use crate::{ 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 = - uuid_handle!("dc3e3307-40a1-49bb-be6d-e0634e8836b2"); - /// The default chromatic aberration intensity amount, in a fraction of the /// window size. 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] = [255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255]; +#[derive(Resource)] +struct DefaultChromaticAberrationLut(Handle); + /// A plugin that implements a built-in postprocessing stack with some common /// effects. /// @@ -96,14 +92,14 @@ pub struct PostProcessingPlugin; pub struct ChromaticAberration { /// The lookup texture that determines the color gradient. /// - /// By default, this is a 3×1 texel texture consisting of one red pixel, one - /// green pixel, and one blue texel, in that order. This recreates the most - /// typical chromatic aberration pattern. However, you can change it to - /// achieve different artistic effects. + /// By default (if None), this is a 3×1 texel texture consisting of one red + /// pixel, one green pixel, and one blue texel, in that order. This + /// recreates the most typical chromatic aberration pattern. However, you + /// can change it to achieve different artistic effects. /// /// The texture is always sampled in its vertical center, so it should /// ordinarily have a height of 1 texel. - pub color_lut: Handle, + pub color_lut: Option>, /// The size of the streaks around the edges of objects, as a fraction of /// the window size. @@ -192,20 +188,17 @@ impl Plugin for PostProcessingPlugin { // Load the default chromatic aberration LUT. let mut assets = app.world_mut().resource_mut::>(); - assets.insert( - DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE.id(), - Image::new( - Extent3d { - width: 3, - height: 1, - depth_or_array_layers: 1, - }, - TextureDimension::D2, - DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(), - TextureFormat::Rgba8UnormSrgb, - RenderAssetUsages::RENDER_WORLD, - ), - ); + let default_lut = assets.add(Image::new( + Extent3d { + width: 3, + height: 1, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(), + TextureFormat::Rgba8UnormSrgb, + RenderAssetUsages::RENDER_WORLD, + )); app.register_type::(); app.add_plugins(ExtractComponentPlugin::::default()); @@ -215,6 +208,7 @@ impl Plugin for PostProcessingPlugin { }; render_app + .insert_resource(DefaultChromaticAberrationLut(default_lut)) .init_resource::>() .init_resource::() .add_systems( @@ -258,7 +252,7 @@ impl Plugin for PostProcessingPlugin { impl Default for ChromaticAberration { fn default() -> Self { Self { - color_lut: DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE, + color_lut: None, intensity: DEFAULT_CHROMATIC_ABERRATION_INTENSITY, max_samples: DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES, } @@ -357,6 +351,7 @@ impl ViewNode for PostProcessingNode { let post_processing_pipeline = world.resource::(); let post_processing_uniform_buffers = world.resource::(); let gpu_image_assets = world.resource::>(); + let default_lut = world.resource::(); // We need a render pipeline to be prepared. 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. - let Some(chromatic_aberration_lut) = gpu_image_assets.get(&chromatic_aberration.color_lut) - else { + let Some(chromatic_aberration_lut) = gpu_image_assets.get( + chromatic_aberration + .color_lut + .as_ref() + .unwrap_or(&default_lut.0), + ) else { return Ok(()); }; diff --git a/crates/bevy_core_widgets/src/lib.rs b/crates/bevy_core_widgets/src/lib.rs index 2a3fc1ac09..3fc13c5c0e 100644 --- a/crates/bevy_core_widgets/src/lib.rs +++ b/crates/bevy_core_widgets/src/lib.rs @@ -21,7 +21,7 @@ mod core_radio; mod core_scrollbar; mod core_slider; -use bevy_app::{App, Plugin}; +use bevy_app::{PluginGroup, PluginGroupBuilder}; pub use callback::{Callback, Notify}; pub use core_button::{CoreButton, CoreButtonPlugin}; @@ -36,18 +36,17 @@ pub use core_slider::{ SliderRange, SliderStep, SliderValue, TrackClick, }; -/// A plugin that registers the observers for all of the core widgets. If you don't want to +/// A plugin group that registers the observers for all of the core widgets. If you don't want to /// use all of the widgets, you can import the individual widget plugins instead. -pub struct CoreWidgetsPlugin; +pub struct CoreWidgetsPlugins; -impl Plugin for CoreWidgetsPlugin { - fn build(&self, app: &mut App) { - app.add_plugins(( - CoreButtonPlugin, - CoreCheckboxPlugin, - CoreRadioGroupPlugin, - CoreScrollbarPlugin, - CoreSliderPlugin, - )); +impl PluginGroup for CoreWidgetsPlugins { + fn build(self) -> PluginGroupBuilder { + PluginGroupBuilder::start::() + .add(CoreButtonPlugin) + .add(CoreCheckboxPlugin) + .add(CoreRadioGroupPlugin) + .add(CoreScrollbarPlugin) + .add(CoreSliderPlugin) } } diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 80189d58c1..d368e6b5e7 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -563,10 +563,21 @@ mod tests { use super::*; #[test] - #[should_panic] fn dependency_loop() { let mut schedule = Schedule::default(); schedule.configure_sets(TestSystems::X.after(TestSystems::X)); + let mut world = World::new(); + let result = schedule.initialize(&mut world); + assert!(matches!(result, Err(ScheduleBuildError::DependencyLoop(_)))); + } + + #[test] + fn dependency_loop_from_chain() { + let mut schedule = Schedule::default(); + schedule.configure_sets((TestSystems::X, TestSystems::X).chain()); + let mut world = World::new(); + let result = schedule.initialize(&mut world); + assert!(matches!(result, Err(ScheduleBuildError::DependencyLoop(_)))); } #[test] @@ -598,10 +609,12 @@ mod tests { } #[test] - #[should_panic] fn hierarchy_loop() { let mut schedule = Schedule::default(); schedule.configure_sets(TestSystems::X.in_set(TestSystems::X)); + let mut world = World::new(); + let result = schedule.initialize(&mut world); + assert!(matches!(result, Err(ScheduleBuildError::HierarchyLoop(_)))); } #[test] diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 0384377ab9..ed40f21fbd 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -390,14 +390,14 @@ impl Schedule { let a = a.into_system_set(); let b = b.into_system_set(); - let Some(&a_id) = self.graph.system_set_ids.get(&a.intern()) else { + let Some(&a_id) = self.graph.system_sets.ids.get(&a.intern()) else { panic!( "Could not mark system as ambiguous, `{:?}` was not found in the schedule. Did you try to call `ambiguous_with` before adding the system to the world?", a ); }; - let Some(&b_id) = self.graph.system_set_ids.get(&b.intern()) else { + let Some(&b_id) = self.graph.system_sets.ids.get(&b.intern()) else { panic!( "Could not mark system as ambiguous, `{:?}` was not found in the schedule. Did you try to call `ambiguous_with` before adding the system to the world?", @@ -760,6 +760,27 @@ enum UninitializedId { }, } +/// Metadata for system sets in a schedule. +#[derive(Default)] +struct SystemSets { + /// List of system sets in the schedule + sets: SlotMap, + /// List of conditions for each system set, in the same order as `system_sets` + conditions: SecondaryMap>, + /// Map from system set to node id + ids: HashMap, +} + +impl SystemSets { + fn get_or_add_set(&mut self, set: InternedSystemSet) -> SystemSetKey { + *self.ids.entry(set).or_insert_with(|| { + let key = self.sets.insert(SystemSetNode::new(set)); + self.conditions.insert(key, Vec::new()); + key + }) + } +} + /// Metadata for a [`Schedule`]. /// /// The order isn't optimized; calling `ScheduleGraph::build_schedule` will return a @@ -770,12 +791,8 @@ pub struct ScheduleGraph { pub systems: SlotMap, /// List of conditions for each system, in the same order as `systems` pub system_conditions: SecondaryMap>, - /// List of system sets in the schedule - system_sets: SlotMap, - /// List of conditions for each system set, in the same order as `system_sets` - system_set_conditions: SecondaryMap>, - /// Map from system set to node id - system_set_ids: HashMap, + /// Data about system sets in the schedule + system_sets: SystemSets, /// Systems that have not been initialized yet; for system sets, we store the index of the first uninitialized condition /// (all the conditions after that index still need to be initialized) uninit: Vec, @@ -800,9 +817,7 @@ impl ScheduleGraph { Self { systems: SlotMap::with_key(), system_conditions: SecondaryMap::new(), - system_sets: SlotMap::with_key(), - system_set_conditions: SecondaryMap::new(), - system_set_ids: HashMap::default(), + system_sets: SystemSets::default(), uninit: Vec::new(), hierarchy: Dag::new(), dependency: Dag::new(), @@ -826,7 +841,7 @@ impl ScheduleGraph { /// Returns `true` if the given system set is part of the graph. Otherwise, returns `false`. pub fn contains_set(&self, set: impl SystemSet) -> bool { - self.system_set_ids.contains_key(&set.intern()) + self.system_sets.ids.contains_key(&set.intern()) } /// Returns the system at the given [`NodeId`]. @@ -840,7 +855,7 @@ impl ScheduleGraph { /// Returns the set at the given [`NodeId`], if it exists. pub fn get_set_at(&self, key: SystemSetKey) -> Option<&dyn SystemSet> { - self.system_sets.get(key).map(|set| &*set.inner) + self.system_sets.sets.get(key).map(|set| &*set.inner) } /// Returns the set at the given [`NodeId`]. @@ -854,7 +869,7 @@ impl ScheduleGraph { /// Returns the conditions for the set at the given [`SystemSetKey`], if it exists. pub fn get_set_conditions_at(&self, key: SystemSetKey) -> Option<&[ConditionWithAccess]> { - self.system_set_conditions.get(key).map(Vec::as_slice) + self.system_sets.conditions.get(key).map(Vec::as_slice) } /// Returns the conditions for the set at the given [`SystemSetKey`]. @@ -882,9 +897,9 @@ impl ScheduleGraph { pub fn system_sets( &self, ) -> impl Iterator { - self.system_sets.iter().filter_map(|(key, set_node)| { + self.system_sets.sets.iter().filter_map(|(key, set_node)| { let set = &*set_node.inner; - let conditions = self.system_set_conditions.get(key)?.as_slice(); + let conditions = self.system_sets.conditions.get(key)?.as_slice(); Some((key, set, conditions)) }) } @@ -946,7 +961,7 @@ impl ScheduleGraph { } let mut set_config = InternedSystemSet::into_config(set.intern()); set_config.conditions.extend(collective_conditions); - self.configure_set_inner(set_config).unwrap(); + self.configure_set_inner(set_config); } } } @@ -1047,10 +1062,7 @@ impl ScheduleGraph { } /// Add a [`ScheduleConfig`] to the graph, including its dependencies and conditions. - fn add_system_inner( - &mut self, - config: ScheduleConfig, - ) -> Result { + fn add_system_inner(&mut self, config: ScheduleConfig) -> SystemKey { let key = self.systems.insert(SystemNode::new(config.node)); self.system_conditions.insert( key, @@ -1064,9 +1076,9 @@ impl ScheduleGraph { self.uninit.push(UninitializedId::System(key)); // graph updates are immediate - self.update_graphs(NodeId::System(key), config.metadata)?; + self.update_graphs(NodeId::System(key), config.metadata); - Ok(NodeId::System(key)) + key } #[track_caller] @@ -1075,39 +1087,26 @@ impl ScheduleGraph { } /// Add a single `ScheduleConfig` to the graph, including its dependencies and conditions. - fn configure_set_inner( - &mut self, - set: ScheduleConfig, - ) -> Result { + fn configure_set_inner(&mut self, set: ScheduleConfig) -> SystemSetKey { let ScheduleConfig { node: set, metadata, conditions, } = set; - let key = match self.system_set_ids.get(&set) { - Some(&id) => id, - None => self.add_set(set), - }; + let key = self.system_sets.get_or_add_set(set); // graph updates are immediate - self.update_graphs(NodeId::Set(key), metadata)?; + self.update_graphs(NodeId::Set(key), metadata); // system init has to be deferred (need `&mut World`) - let system_set_conditions = self.system_set_conditions.entry(key).unwrap().or_default(); + let system_set_conditions = self.system_sets.conditions.entry(key).unwrap().or_default(); self.uninit.push(UninitializedId::Set { key, first_uninit_condition: system_set_conditions.len(), }); system_set_conditions.extend(conditions.into_iter().map(ConditionWithAccess::new)); - Ok(NodeId::Set(key)) - } - - fn add_set(&mut self, set: InternedSystemSet) -> SystemSetKey { - let key = self.system_sets.insert(SystemSetNode::new(set)); - self.system_set_conditions.insert(key, Vec::new()); - self.system_set_ids.insert(set, key); key } @@ -1117,78 +1116,8 @@ impl ScheduleGraph { AnonymousSet::new(id) } - /// Check that no set is included in itself. - /// Add all the sets from the [`GraphInfo`]'s hierarchy to the graph. - fn check_hierarchy_sets( - &mut self, - id: NodeId, - graph_info: &GraphInfo, - ) -> Result<(), ScheduleBuildError> { - for &set in &graph_info.hierarchy { - if let Some(&set_id) = self.system_set_ids.get(&set) { - if let NodeId::Set(key) = id - && set_id == key - { - { - return Err(ScheduleBuildError::HierarchyLoop( - self.get_node_name(&NodeId::Set(key)), - )); - } - } - } else { - // If the set is not in the graph, we add it - self.add_set(set); - } - } - - Ok(()) - } - - /// Checks that no system set is dependent on itself. - /// Add all the sets from the [`GraphInfo`]'s dependencies to the graph. - fn check_edges( - &mut self, - id: NodeId, - graph_info: &GraphInfo, - ) -> Result<(), ScheduleBuildError> { - for Dependency { set, .. } in &graph_info.dependencies { - if let Some(&set_id) = self.system_set_ids.get(set) { - if let NodeId::Set(key) = id - && set_id == key - { - return Err(ScheduleBuildError::DependencyLoop( - self.get_node_name(&NodeId::Set(key)), - )); - } - } else { - // If the set is not in the graph, we add it - self.add_set(*set); - } - } - - Ok(()) - } - - /// Add all the sets from the [`GraphInfo`]'s ambiguity to the graph. - fn add_ambiguities(&mut self, graph_info: &GraphInfo) { - if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with { - for set in ambiguous_with { - if !self.system_set_ids.contains_key(set) { - self.add_set(*set); - } - } - } - } - /// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`] - fn update_graphs( - &mut self, - id: NodeId, - graph_info: GraphInfo, - ) -> Result<(), ScheduleBuildError> { - self.check_hierarchy_sets(id, &graph_info)?; - self.check_edges(id, &graph_info)?; - self.add_ambiguities(&graph_info); + fn update_graphs(&mut self, id: NodeId, graph_info: GraphInfo) { self.changed = true; let GraphInfo { @@ -1201,16 +1130,22 @@ impl ScheduleGraph { self.hierarchy.graph.add_node(id); self.dependency.graph.add_node(id); - for key in sets.into_iter().map(|set| self.system_set_ids[&set]) { + for key in sets + .into_iter() + .map(|set| self.system_sets.get_or_add_set(set)) + { self.hierarchy.graph.add_edge(NodeId::Set(key), id); // ensure set also appears in dependency graph self.dependency.graph.add_node(NodeId::Set(key)); } - for (kind, key, options) in dependencies - .into_iter() - .map(|Dependency { kind, set, options }| (kind, self.system_set_ids[&set], options)) + for (kind, key, options) in + dependencies + .into_iter() + .map(|Dependency { kind, set, options }| { + (kind, self.system_sets.get_or_add_set(set), options) + }) { let (lhs, rhs) = match kind { DependencyKind::Before => (id, NodeId::Set(key)), @@ -1230,7 +1165,7 @@ impl ScheduleGraph { Ambiguity::IgnoreWithSet(ambiguous_with) => { for key in ambiguous_with .into_iter() - .map(|set| self.system_set_ids[&set]) + .map(|set| self.system_sets.get_or_add_set(set)) { self.ambiguous_with.add_edge(id, NodeId::Set(key)); } @@ -1239,8 +1174,6 @@ impl ScheduleGraph { self.ambiguous_with_all.insert(id); } } - - Ok(()) } /// Initializes any newly-added systems and conditions by calling [`System::initialize`](crate::system::System) @@ -1258,7 +1191,7 @@ impl ScheduleGraph { key, first_uninit_condition, } => { - for condition in self.system_set_conditions[key] + for condition in self.system_sets.conditions[key] .iter_mut() .skip(first_uninit_condition) { @@ -1358,9 +1291,9 @@ impl ScheduleGraph { HashMap>, ) { let mut set_systems: HashMap> = - HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default()); + HashMap::with_capacity_and_hasher(self.system_sets.sets.len(), Default::default()); let mut set_system_sets: HashMap> = - HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default()); + HashMap::with_capacity_and_hasher(self.system_sets.sets.len(), Default::default()); for &id in hierarchy_topsort.iter().rev() { let NodeId::Set(set_key) = id else { continue; @@ -1559,7 +1492,7 @@ impl ScheduleGraph { // ignore system sets that have no conditions // ignore system type sets (already covered, they don't have conditions) let key = id.as_set()?; - (!self.system_set_conditions[key].is_empty()).then_some((i, key)) + (!self.system_sets.conditions[key].is_empty()).then_some((i, key)) }) .unzip(); @@ -1659,7 +1592,7 @@ impl ScheduleGraph { .drain(..) .zip(schedule.set_conditions.drain(..)) { - self.system_set_conditions[key] = conditions; + self.system_sets.conditions[key] = conditions; } *schedule = self.build_schedule(world, schedule_label, ignored_ambiguities)?; @@ -1673,7 +1606,7 @@ impl ScheduleGraph { } for &key in &schedule.set_ids { - let conditions = core::mem::take(&mut self.system_set_conditions[key]); + let conditions = core::mem::take(&mut self.system_sets.conditions[key]); schedule.set_conditions.push(conditions); } @@ -1700,13 +1633,13 @@ trait ProcessScheduleConfig: Schedulable + Sized { impl ProcessScheduleConfig for ScheduleSystem { fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig) -> NodeId { - schedule_graph.add_system_inner(config).unwrap() + NodeId::System(schedule_graph.add_system_inner(config)) } } impl ProcessScheduleConfig for InternedSystemSet { fn process_config(schedule_graph: &mut ScheduleGraph, config: ScheduleConfig) -> NodeId { - schedule_graph.configure_set_inner(config).unwrap() + NodeId::Set(schedule_graph.configure_set_inner(config)) } } @@ -1748,7 +1681,7 @@ impl ScheduleGraph { } } NodeId::Set(key) => { - let set = &self.system_sets[key]; + let set = &self.system_sets.sets[key]; if set.is_anonymous() { self.anonymous_set_name(id) } else { @@ -1833,6 +1766,17 @@ impl ScheduleGraph { graph: &DiGraph, report: ReportCycles, ) -> Result, ScheduleBuildError> { + // Check explicitly for self-edges. + // `iter_sccs` won't report them as cycles because they still form components of one node. + if let Some((node, _)) = graph.all_edges().find(|(left, right)| left == right) { + let name = self.get_node_name(&node); + let error = match report { + ReportCycles::Hierarchy => ScheduleBuildError::HierarchyLoop(name), + ReportCycles::Dependency => ScheduleBuildError::DependencyLoop(name), + }; + return Err(error); + } + // Tarjan's SCC algorithm returns elements in *reverse* topological order. let mut top_sorted_nodes = Vec::with_capacity(graph.node_count()); let mut sccs_with_cycles = Vec::new(); @@ -1963,7 +1907,7 @@ impl ScheduleGraph { set_systems: &HashMap>, ) -> Result<(), ScheduleBuildError> { for (&key, systems) in set_systems { - let set = &self.system_sets[key]; + let set = &self.system_sets.sets[key]; if set.is_system_type() { let instances = systems.len(); let ambiguous_with = self.ambiguous_with.edges(NodeId::Set(key)); @@ -2070,7 +2014,7 @@ impl ScheduleGraph { fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec { let mut sets = >::default(); self.traverse_sets_containing_node(*id, &mut |key| { - !self.system_sets[key].is_system_type() && sets.insert(key) + !self.system_sets.sets[key].is_system_type() && sets.insert(key) }); let mut sets: Vec<_> = sets .into_iter() diff --git a/crates/bevy_math/src/primitives/polygon.rs b/crates/bevy_math/src/primitives/polygon.rs index 9aa261b297..096a19ecfb 100644 --- a/crates/bevy_math/src/primitives/polygon.rs +++ b/crates/bevy_math/src/primitives/polygon.rs @@ -2,17 +2,13 @@ use { super::{Measured2d, Triangle2d}, alloc::{collections::BTreeMap, vec::Vec}, + core::cmp::Ordering, }; -use core::cmp::Ordering; - use crate::Vec2; -#[cfg_attr( - not(feature = "alloc"), - expect(dead_code, reason = "this type is only used with the alloc feature") -)] #[derive(Debug, Clone, Copy)] +#[cfg(feature = "alloc")] enum Endpoint { Left, Right, @@ -24,22 +20,16 @@ enum Endpoint { /// If `e1.position().x == e2.position().x` the events are ordered from bottom to top. /// /// This is the order expected by the [`SweepLine`]. +#[cfg(feature = "alloc")] #[derive(Debug, Clone, Copy)] -#[cfg_attr( - not(feature = "alloc"), - allow(dead_code, reason = "this type is only used with the alloc feature") -)] struct SweepLineEvent { segment: Segment, /// Type of the vertex (left or right) endpoint: Endpoint, } +#[cfg(feature = "alloc")] impl SweepLineEvent { - #[cfg_attr( - not(feature = "alloc"), - allow(dead_code, reason = "this type is only used with the alloc feature") - )] fn position(&self) -> Vec2 { match self.endpoint { Endpoint::Left => self.segment.left, @@ -48,20 +38,24 @@ impl SweepLineEvent { } } +#[cfg(feature = "alloc")] impl PartialEq for SweepLineEvent { fn eq(&self, other: &Self) -> bool { self.position() == other.position() } } +#[cfg(feature = "alloc")] impl Eq for SweepLineEvent {} +#[cfg(feature = "alloc")] impl PartialOrd for SweepLineEvent { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } +#[cfg(feature = "alloc")] impl Ord for SweepLineEvent { fn cmp(&self, other: &Self) -> Ordering { xy_order(self.position(), other.position()) @@ -69,10 +63,7 @@ impl Ord for SweepLineEvent { } /// Orders 2D points according to the order expected by the sweep line and event queue from -X to +X and then -Y to Y. -#[cfg_attr( - not(feature = "alloc"), - allow(dead_code, reason = "this type is only used with the alloc feature") -)] +#[cfg(feature = "alloc")] fn xy_order(a: Vec2, b: Vec2) -> Ordering { a.x.total_cmp(&b.x).then_with(|| a.y.total_cmp(&b.y)) } @@ -129,26 +120,31 @@ impl EventQueue { /// Segments are ordered from bottom to top based on their left vertices if possible. /// If their y values are identical, the segments are ordered based on the y values of their right vertices. #[derive(Debug, Clone, Copy)] +#[cfg(feature = "alloc")] struct Segment { edge_index: usize, left: Vec2, right: Vec2, } +#[cfg(feature = "alloc")] impl PartialEq for Segment { fn eq(&self, other: &Self) -> bool { self.edge_index == other.edge_index } } +#[cfg(feature = "alloc")] impl Eq for Segment {} +#[cfg(feature = "alloc")] impl PartialOrd for Segment { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } +#[cfg(feature = "alloc")] impl Ord for Segment { fn cmp(&self, other: &Self) -> Ordering { self.left @@ -159,10 +155,7 @@ impl Ord for Segment { } /// Holds information about which segment is above and which is below a given [`Segment`] -#[cfg_attr( - not(feature = "alloc"), - expect(dead_code, reason = "this type is only used with the alloc feature") -)] +#[cfg(feature = "alloc")] #[derive(Debug, Clone, Copy)] struct SegmentOrder { above: Option, @@ -173,8 +166,8 @@ struct SegmentOrder { /// /// It can be thought of as a vertical line sweeping from -X to +X across the polygon that keeps track of the order of the segments /// the sweep line is intersecting at any given moment. -#[cfg(feature = "alloc")] #[derive(Debug, Clone)] +#[cfg(feature = "alloc")] struct SweepLine<'a> { vertices: &'a [Vec2], tree: BTreeMap, diff --git a/crates/bevy_pbr/src/meshlet/pipelines.rs b/crates/bevy_pbr/src/meshlet/pipelines.rs index 6ac22f0fba..0fe9905d32 100644 --- a/crates/bevy_pbr/src/meshlet/pipelines.rs +++ b/crates/bevy_pbr/src/meshlet/pipelines.rs @@ -1,7 +1,7 @@ use super::resource_manager::ResourceManager; use bevy_asset::{load_embedded_asset, Handle}; 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, }; use bevy_ecs::{ @@ -84,6 +84,7 @@ impl FromWorld for MeshletPipelines { .remap_1d_to_2d_dispatch_bind_group_layout .clone(); + let downsample_depth_shader = (*world.resource::()).clone(); let vertex_state = world.resource::().to_vertex_state(); let fill_counts_layout = resource_manager.fill_counts_bind_group_layout.clone(); @@ -230,7 +231,7 @@ impl FromWorld for MeshletPipelines { stages: ShaderStages::COMPUTE, range: 0..4, }], - shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, + shader: downsample_depth_shader.clone(), shader_defs: vec![ "MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into(), "MESHLET".into(), @@ -248,7 +249,7 @@ impl FromWorld for MeshletPipelines { stages: ShaderStages::COMPUTE, range: 0..4, }], - shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, + shader: downsample_depth_shader.clone(), shader_defs: vec![ "MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into(), "MESHLET".into(), @@ -266,7 +267,7 @@ impl FromWorld for MeshletPipelines { stages: ShaderStages::COMPUTE, range: 0..4, }], - shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, + shader: downsample_depth_shader.clone(), shader_defs: vec!["MESHLET".into()], entry_point: Some("downsample_depth_first".into()), ..default() @@ -281,7 +282,7 @@ impl FromWorld for MeshletPipelines { stages: ShaderStages::COMPUTE, range: 0..4, }], - shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, + shader: downsample_depth_shader, shader_defs: vec!["MESHLET".into()], entry_point: Some("downsample_depth_second".into()), zero_initialize_workgroup_memory: false, diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index a4099aeb62..e28412d7bd 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -30,12 +30,12 @@ //! [Henyey-Greenstein phase function]: https://www.pbr-book.org/4ed/Volume_Scattering/Phase_Functions#TheHenyeyndashGreensteinPhaseFunction use bevy_app::{App, Plugin}; -use bevy_asset::{embedded_asset, Assets}; +use bevy_asset::{embedded_asset, Assets, Handle}; use bevy_core_pipeline::core_3d::{ graph::{Core3d, Node3d}, 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_math::{ primitives::{Cuboid, Plane3d}, @@ -48,9 +48,7 @@ use bevy_render::{ sync_component::SyncComponentPlugin, ExtractSchedule, Render, RenderApp, RenderSystems, }; -use render::{ - VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer, CUBE_MESH, PLANE_MESH, -}; +use render::{VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer}; use crate::graph::NodePbr; @@ -59,13 +57,19 @@ pub mod render; /// A plugin that implements volumetric fog. pub struct VolumetricFogPlugin; +#[derive(Resource)] +pub struct FogAssets { + plane_mesh: Handle, + cube_mesh: Handle, +} + impl Plugin for VolumetricFogPlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "volumetric_fog.wgsl"); let mut meshes = app.world_mut().resource_mut::>(); - meshes.insert(&PLANE_MESH, Plane3d::new(Vec3::Z, Vec2::ONE).mesh().into()); - meshes.insert(&CUBE_MESH, Cuboid::new(1.0, 1.0, 1.0).mesh().into()); + let plane_mesh = meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE).mesh()); + let cube_mesh = meshes.add(Cuboid::new(1.0, 1.0, 1.0).mesh()); app.add_plugins(SyncComponentPlugin::::default()); @@ -74,6 +78,10 @@ impl Plugin for VolumetricFogPlugin { }; render_app + .insert_resource(FogAssets { + plane_mesh, + cube_mesh, + }) .init_resource::>() .init_resource::() .add_systems(ExtractSchedule, render::extract_volumetric_fog) diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index feb04a2983..f24550a456 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -2,7 +2,7 @@ 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_core_pipeline::{ core_3d::Camera3d, @@ -54,6 +54,8 @@ use crate::{ VolumetricLight, }; +use super::FogAssets; + bitflags! { /// Flags that describe the bind group layout used to render volumetric fog. #[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 = 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 = uuid_handle!("4a1dd661-2d91-4377-a17a-a914e21e277e"); - /// The total number of bind group layouts. /// /// This is the total number of combinations of all @@ -370,6 +358,7 @@ impl ViewNode for VolumetricFogNode { return Ok(()); }; + let fog_assets = world.resource::(); let render_meshes = world.resource::>(); 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 // effectively rendering a full-screen quad. let mesh_handle = if view_fog_volume.exterior { - CUBE_MESH.clone() + fog_assets.cube_mesh.clone() } else { - PLANE_MESH.clone() + fog_assets.plane_mesh.clone() }; 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, mut pipelines: ResMut>, volumetric_lighting_pipeline: Res, + fog_assets: Res, view_targets: Query< ( Entity, @@ -629,7 +619,7 @@ pub fn prepare_volumetric_fog_pipelines( >, meshes: Res>, ) { - 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. return; }; diff --git a/crates/bevy_remote/Cargo.toml b/crates/bevy_remote/Cargo.toml index e7a40c65ba..899ac8b846 100644 --- a/crates/bevy_remote/Cargo.toml +++ b/crates/bevy_remote/Cargo.toml @@ -30,6 +30,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-fea "serialize", ] } bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev", optional = true } +bevy_log = { path = "../bevy_log", version = "0.17.0-dev" } # other anyhow = "1" @@ -38,7 +39,6 @@ serde = { version = "1", features = ["derive"] } serde_json = "1.0.140" http-body-util = "0.1" async-channel = "2" -bevy_log = { version = "0.17.0-dev", path = "../bevy_log" } # dependencies that will not compile on wasm [target.'cfg(not(target_family = "wasm"))'.dependencies] diff --git a/crates/bevy_render/macros/Cargo.toml b/crates/bevy_render/macros/Cargo.toml index 016fe88765..99d1dca5f9 100644 --- a/crates/bevy_render/macros/Cargo.toml +++ b/crates/bevy_render/macros/Cargo.toml @@ -14,7 +14,7 @@ proc-macro = true [dependencies] bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" } -syn = "2.0" +syn = { version = "2.0", features = ["full"] } proc-macro2 = "1.0" quote = "1.0" diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 7012e61d92..58ec9b5ceb 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -6,7 +6,6 @@ //! [`Material2d`]: bevy::sprite::Material2d use bevy::{ - asset::uuid_handle, color::palettes::basic::YELLOW, core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}, math::{ops, FloatOrd}, @@ -129,12 +128,16 @@ pub struct ColoredMesh2d; pub struct ColoredMesh2dPipeline { /// This pipeline wraps the standard [`Mesh2dPipeline`] mesh2d_pipeline: Mesh2dPipeline, + /// The shader asset handle. + shader: Handle, } impl FromWorld for ColoredMesh2dPipeline { fn from_world(world: &mut World) -> Self { Self { mesh2d_pipeline: Mesh2dPipeline::from_world(world), + // Get the shader from the shader resource we inserted in the plugin. + shader: world.resource::().0.clone(), } } } @@ -164,14 +167,14 @@ impl SpecializedRenderPipeline for ColoredMesh2dPipeline { RenderPipelineDescriptor { vertex: VertexState { // Use our custom shader - shader: COLORED_MESH2D_SHADER_HANDLE, + shader: self.shader.clone(), // Use our custom vertex buffer buffers: vec![vertex_layout], ..default() }, fragment: Some(FragmentState { // Use our custom shader - shader: COLORED_MESH2D_SHADER_HANDLE, + shader: self.shader.clone(), targets: vec![Some(ColorTargetState { format, blend: Some(BlendState::ALPHA_BLENDING), @@ -278,9 +281,10 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { /// Plugin that renders [`ColoredMesh2d`]s pub struct ColoredMesh2dPlugin; -/// Handle to the custom shader with a unique random ID -pub const COLORED_MESH2D_SHADER_HANDLE: Handle = - uuid_handle!("f48b148f-7373-4638-9900-392b3b3ccc66"); +/// A resource holding the shader asset handle for the pipeline to take. There are many ways to get +/// the shader into the pipeline - this is just one option. +#[derive(Resource)] +struct ColoredMesh2dShader(Handle); /// Our custom pipeline needs its own instance storage #[derive(Resource, Deref, DerefMut, Default)] @@ -290,15 +294,16 @@ impl Plugin for ColoredMesh2dPlugin { fn build(&self, app: &mut App) { // Load our custom shader let mut shaders = app.world_mut().resource_mut::>(); - shaders.insert( - &COLORED_MESH2D_SHADER_HANDLE, - Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()), - ); + // Here, we construct and add the shader asset manually. There are many ways to load this + // shader, including `embedded_asset`/`load_embedded_asset`. + let shader = shaders.add(Shader::from_wgsl(COLORED_MESH2D_SHADER, file!())); + app.add_plugins(SyncComponentPlugin::::default()); // Register our custom draw function, and add our render systems app.get_sub_app_mut(RenderApp) .unwrap() + .insert_resource(ColoredMesh2dShader(shader)) .add_render_command::() .init_resource::>() .init_resource::() diff --git a/examples/ui/core_widgets.rs b/examples/ui/core_widgets.rs index 86aaa820f8..7f99bdd848 100644 --- a/examples/ui/core_widgets.rs +++ b/examples/ui/core_widgets.rs @@ -4,7 +4,7 @@ use bevy::{ color::palettes::basic::*, core_widgets::{ Callback, CoreButton, CoreCheckbox, CoreRadio, CoreRadioGroup, CoreSlider, - CoreSliderDragState, CoreSliderThumb, CoreWidgetsPlugin, SliderRange, SliderValue, + CoreSliderDragState, CoreSliderThumb, CoreWidgetsPlugins, SliderRange, SliderValue, TrackClick, }, input_focus::{ @@ -21,7 +21,7 @@ fn main() { App::new() .add_plugins(( DefaultPlugins, - CoreWidgetsPlugin, + CoreWidgetsPlugins, InputDispatchPlugin, TabNavigationPlugin, )) diff --git a/examples/ui/core_widgets_observers.rs b/examples/ui/core_widgets_observers.rs index 1ab4cda3b0..c12edee08d 100644 --- a/examples/ui/core_widgets_observers.rs +++ b/examples/ui/core_widgets_observers.rs @@ -3,7 +3,7 @@ use bevy::{ color::palettes::basic::*, core_widgets::{ - Callback, CoreButton, CoreCheckbox, CoreSlider, CoreSliderThumb, CoreWidgetsPlugin, + Callback, CoreButton, CoreCheckbox, CoreSlider, CoreSliderThumb, CoreWidgetsPlugins, SliderRange, SliderValue, }, ecs::system::SystemId, @@ -21,7 +21,7 @@ fn main() { App::new() .add_plugins(( DefaultPlugins, - CoreWidgetsPlugin, + CoreWidgetsPlugins, InputDispatchPlugin, TabNavigationPlugin, )) diff --git a/examples/ui/feathers.rs b/examples/ui/feathers.rs index ae6ec31f4c..da8b1faf27 100644 --- a/examples/ui/feathers.rs +++ b/examples/ui/feathers.rs @@ -1,7 +1,7 @@ //! This example shows off the various Bevy Feathers widgets. use bevy::{ - core_widgets::{Callback, CoreRadio, CoreRadioGroup, CoreWidgetsPlugin, SliderStep}, + core_widgets::{Callback, CoreRadio, CoreRadioGroup, CoreWidgetsPlugins, SliderStep}, feathers::{ controls::{ button, checkbox, radio, slider, toggle_switch, ButtonProps, ButtonVariant, @@ -25,7 +25,7 @@ fn main() { App::new() .add_plugins(( DefaultPlugins, - CoreWidgetsPlugin, + CoreWidgetsPlugins, InputDispatchPlugin, TabNavigationPlugin, FeathersPlugin, diff --git a/release-content/migration-guides/chromatic_aberration_option.md b/release-content/migration-guides/chromatic_aberration_option.md new file mode 100644 index 0000000000..3fa1aa4ccd --- /dev/null +++ b/release-content/migration-guides/chromatic_aberration_option.md @@ -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`. Now, it +is an `Option>` which falls back to the default image when `None`. For users assigning +a custom LUT, just wrap the value in `Some`. diff --git a/release-content/release-notes/headless-widgets.md b/release-content/release-notes/headless-widgets.md index 5e2a91c556..5b3ff3dc17 100644 --- a/release-content/release-notes/headless-widgets.md +++ b/release-content/release-notes/headless-widgets.md @@ -1,7 +1,7 @@ --- title: Headless Widgets authors: ["@viridia", "@ickshonpe", "@alice-i-cecile"] -pull_requests: [19366, 19584, 19665, 19778, 19803] +pull_requests: [19366, 19584, 19665, 19778, 19803, 20036] --- Bevy's `Button` and `Interaction` components have been around for a long time. Unfortunately