Use RenderStartup in bevy_ui. (#19901)

# Objective

- Progress towards #19887.

## Solution

- Convert `FromWorld` impls into systems that run in `RenderStartup`.
- Move `UiPipeline` init to `build_ui_render` instead of doing it
separately in `finish`.

Note: I am making several of these systems pub so that users could order
their systems relative to them. This is to match the fact that these
types previously were FromWorld so users could initialize them.

## Testing

- Ran `ui_material`, `ui_texture_slice`, `box_shadow`, and `gradients`
examples and it still worked.
This commit is contained in:
andriyDev 2025-07-04 21:07:23 -07:00 committed by GitHub
parent bd5f924290
commit 29779e1e18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 126 additions and 154 deletions

View File

@ -16,7 +16,6 @@ use bevy_ecs::{
use bevy_image::BevyDefault as _; use bevy_image::BevyDefault as _;
use bevy_math::{vec2, Affine2, FloatOrd, Rect, Vec2}; use bevy_math::{vec2, Affine2, FloatOrd, Rect, Vec2};
use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity}; use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity};
use bevy_render::RenderApp;
use bevy_render::{ use bevy_render::{
render_phase::*, render_phase::*,
render_resource::{binding_types::uniform_buffer, *}, render_resource::{binding_types::uniform_buffer, *},
@ -24,6 +23,7 @@ use bevy_render::{
view::*, view::*,
Extract, ExtractSchedule, Render, RenderSystems, Extract, ExtractSchedule, Render, RenderSystems,
}; };
use bevy_render::{RenderApp, RenderStartup};
use bevy_ui::{ use bevy_ui::{
BoxShadow, CalculatedClip, ComputedNode, ComputedNodeTarget, ResolvedBorderRadius, BoxShadow, CalculatedClip, ComputedNode, ComputedNodeTarget, ResolvedBorderRadius,
UiGlobalTransform, Val, UiGlobalTransform, Val,
@ -48,6 +48,7 @@ impl Plugin for BoxShadowPlugin {
.init_resource::<ExtractedBoxShadows>() .init_resource::<ExtractedBoxShadows>()
.init_resource::<BoxShadowMeta>() .init_resource::<BoxShadowMeta>()
.init_resource::<SpecializedRenderPipelines<BoxShadowPipeline>>() .init_resource::<SpecializedRenderPipelines<BoxShadowPipeline>>()
.add_systems(RenderStartup, init_box_shadow_pipeline)
.add_systems( .add_systems(
ExtractSchedule, ExtractSchedule,
extract_shadows.in_set(RenderUiSystems::ExtractBoxShadows), extract_shadows.in_set(RenderUiSystems::ExtractBoxShadows),
@ -61,12 +62,6 @@ impl Plugin for BoxShadowPlugin {
); );
} }
} }
fn finish(&self, app: &mut App) {
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<BoxShadowPipeline>();
}
}
} }
#[repr(C)] #[repr(C)]
@ -111,23 +106,23 @@ pub struct BoxShadowPipeline {
pub shader: Handle<Shader>, pub shader: Handle<Shader>,
} }
impl FromWorld for BoxShadowPipeline { pub fn init_box_shadow_pipeline(
fn from_world(world: &mut World) -> Self { mut commands: Commands,
let render_device = world.resource::<RenderDevice>(); render_device: Res<RenderDevice>,
asset_server: Res<AssetServer>,
) {
let view_layout = render_device.create_bind_group_layout(
"box_shadow_view_layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX_FRAGMENT,
uniform_buffer::<ViewUniform>(true),
),
);
let view_layout = render_device.create_bind_group_layout( commands.insert_resource(BoxShadowPipeline {
"box_shadow_view_layout", view_layout,
&BindGroupLayoutEntries::single( shader: load_embedded_asset!(asset_server.as_ref(), "box_shadow.wgsl"),
ShaderStages::VERTEX_FRAGMENT, });
uniform_buffer::<ViewUniform>(true),
),
);
BoxShadowPipeline {
view_layout,
shader: load_embedded_asset!(world, "box_shadow.wgsl"),
}
}
} }
#[derive(Clone, Copy, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Hash, PartialEq, Eq)]

View File

@ -21,7 +21,6 @@ use bevy_math::{
FloatOrd, Rect, Vec2, FloatOrd, Rect, Vec2,
}; };
use bevy_math::{Affine2, Vec2Swizzles}; use bevy_math::{Affine2, Vec2Swizzles};
use bevy_render::sync_world::MainEntity;
use bevy_render::{ use bevy_render::{
render_phase::*, render_phase::*,
render_resource::{binding_types::uniform_buffer, *}, render_resource::{binding_types::uniform_buffer, *},
@ -30,6 +29,7 @@ use bevy_render::{
view::*, view::*,
Extract, ExtractSchedule, Render, RenderSystems, Extract, ExtractSchedule, Render, RenderSystems,
}; };
use bevy_render::{sync_world::MainEntity, RenderStartup};
use bevy_sprite::BorderRect; use bevy_sprite::BorderRect;
use bevy_ui::{ use bevy_ui::{
BackgroundGradient, BorderGradient, ColorStop, ConicGradient, Gradient, BackgroundGradient, BorderGradient, ColorStop, ConicGradient, Gradient,
@ -51,6 +51,7 @@ impl Plugin for GradientPlugin {
.init_resource::<ExtractedColorStops>() .init_resource::<ExtractedColorStops>()
.init_resource::<GradientMeta>() .init_resource::<GradientMeta>()
.init_resource::<SpecializedRenderPipelines<GradientPipeline>>() .init_resource::<SpecializedRenderPipelines<GradientPipeline>>()
.add_systems(RenderStartup, init_gradient_pipeline)
.add_systems( .add_systems(
ExtractSchedule, ExtractSchedule,
extract_gradients extract_gradients
@ -66,12 +67,6 @@ impl Plugin for GradientPlugin {
); );
} }
} }
fn finish(&self, app: &mut App) {
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<GradientPipeline>();
}
}
} }
#[derive(Component)] #[derive(Component)]
@ -102,23 +97,23 @@ pub struct GradientPipeline {
pub shader: Handle<Shader>, pub shader: Handle<Shader>,
} }
impl FromWorld for GradientPipeline { pub fn init_gradient_pipeline(
fn from_world(world: &mut World) -> Self { mut commands: Commands,
let render_device = world.resource::<RenderDevice>(); render_device: Res<RenderDevice>,
asset_server: Res<AssetServer>,
) {
let view_layout = render_device.create_bind_group_layout(
"ui_gradient_view_layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX_FRAGMENT,
uniform_buffer::<ViewUniform>(true),
),
);
let view_layout = render_device.create_bind_group_layout( commands.insert_resource(GradientPipeline {
"ui_gradient_view_layout", view_layout,
&BindGroupLayoutEntries::single( shader: load_embedded_asset!(asset_server.as_ref(), "gradient.wgsl"),
ShaderStages::VERTEX_FRAGMENT, });
uniform_buffer::<ViewUniform>(true),
),
);
GradientPipeline {
view_layout,
shader: load_embedded_asset!(world, "gradient.wgsl"),
}
}
} }
pub fn compute_gradient_line_length(angle: f32, size: Vec2) -> f32 { pub fn compute_gradient_line_length(angle: f32, size: Vec2) -> f32 {

View File

@ -36,7 +36,6 @@ use bevy_ecs::prelude::*;
use bevy_ecs::system::SystemParam; use bevy_ecs::system::SystemParam;
use bevy_image::prelude::*; use bevy_image::prelude::*;
use bevy_math::{Affine2, FloatOrd, Mat4, Rect, UVec4, Vec2}; use bevy_math::{Affine2, FloatOrd, Mat4, Rect, UVec4, Vec2};
use bevy_render::load_shader_library;
use bevy_render::render_graph::{NodeRunError, RenderGraphContext}; use bevy_render::render_graph::{NodeRunError, RenderGraphContext};
use bevy_render::render_phase::ViewSortedRenderPhases; use bevy_render::render_phase::ViewSortedRenderPhases;
use bevy_render::renderer::RenderContext; use bevy_render::renderer::RenderContext;
@ -53,6 +52,7 @@ use bevy_render::{
view::{ExtractedView, ViewUniforms}, view::{ExtractedView, ViewUniforms},
Extract, RenderApp, RenderSystems, Extract, RenderApp, RenderSystems,
}; };
use bevy_render::{load_shader_library, RenderStartup};
use bevy_render::{ use bevy_render::{
render_phase::{PhaseItem, PhaseItemExtraIndex}, render_phase::{PhaseItem, PhaseItemExtraIndex},
sync_world::{RenderEntity, TemporaryRenderEntity}, sync_world::{RenderEntity, TemporaryRenderEntity},
@ -243,6 +243,7 @@ impl Plugin for UiRenderPlugin {
) )
.chain(), .chain(),
) )
.add_systems(RenderStartup, init_ui_pipeline)
.add_systems( .add_systems(
ExtractSchedule, ExtractSchedule,
( (
@ -292,14 +293,6 @@ impl Plugin for UiRenderPlugin {
app.add_plugins(GradientPlugin); app.add_plugins(GradientPlugin);
app.add_plugins(BoxShadowPlugin); app.add_plugins(BoxShadowPlugin);
} }
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<UiPipeline>();
}
} }
fn get_ui_graph(render_app: &mut SubApp) -> RenderGraph { fn get_ui_graph(render_app: &mut SubApp) -> RenderGraph {

View File

@ -1,4 +1,4 @@
use bevy_asset::{load_embedded_asset, Handle}; use bevy_asset::{load_embedded_asset, AssetServer, Handle};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_image::BevyDefault as _; use bevy_image::BevyDefault as _;
use bevy_render::{ use bevy_render::{
@ -18,35 +18,35 @@ pub struct UiPipeline {
pub shader: Handle<Shader>, pub shader: Handle<Shader>,
} }
impl FromWorld for UiPipeline { pub fn init_ui_pipeline(
fn from_world(world: &mut World) -> Self { mut commands: Commands,
let render_device = world.resource::<RenderDevice>(); render_device: Res<RenderDevice>,
asset_server: Res<AssetServer>,
) {
let view_layout = render_device.create_bind_group_layout(
"ui_view_layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX_FRAGMENT,
uniform_buffer::<ViewUniform>(true),
),
);
let view_layout = render_device.create_bind_group_layout( let image_layout = render_device.create_bind_group_layout(
"ui_view_layout", "ui_image_layout",
&BindGroupLayoutEntries::single( &BindGroupLayoutEntries::sequential(
ShaderStages::VERTEX_FRAGMENT, ShaderStages::FRAGMENT,
uniform_buffer::<ViewUniform>(true), (
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
), ),
); ),
);
let image_layout = render_device.create_bind_group_layout( commands.insert_resource(UiPipeline {
"ui_image_layout", view_layout,
&BindGroupLayoutEntries::sequential( image_layout,
ShaderStages::FRAGMENT, shader: load_embedded_asset!(asset_server.as_ref(), "ui.wgsl"),
( });
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
),
),
);
UiPipeline {
view_layout,
image_layout,
shader: load_embedded_asset!(world, "ui.wgsl"),
}
}
} }
#[derive(Clone, Copy, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Hash, PartialEq, Eq)]

View File

@ -8,11 +8,9 @@ use bevy_ecs::{
lifetimeless::{Read, SRes}, lifetimeless::{Read, SRes},
*, *,
}, },
world::{FromWorld, World},
}; };
use bevy_image::BevyDefault as _; use bevy_image::BevyDefault as _;
use bevy_math::{Affine2, FloatOrd, Rect, Vec2}; use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
use bevy_render::RenderApp;
use bevy_render::{ use bevy_render::{
globals::{GlobalsBuffer, GlobalsUniform}, globals::{GlobalsBuffer, GlobalsUniform},
load_shader_library, load_shader_library,
@ -24,6 +22,7 @@ use bevy_render::{
view::*, view::*,
Extract, ExtractSchedule, Render, RenderSystems, Extract, ExtractSchedule, Render, RenderSystems,
}; };
use bevy_render::{RenderApp, RenderStartup};
use bevy_sprite::BorderRect; use bevy_sprite::BorderRect;
use bevy_utils::default; use bevy_utils::default;
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
@ -61,6 +60,7 @@ where
.init_resource::<ExtractedUiMaterialNodes<M>>() .init_resource::<ExtractedUiMaterialNodes<M>>()
.init_resource::<UiMaterialMeta<M>>() .init_resource::<UiMaterialMeta<M>>()
.init_resource::<SpecializedRenderPipelines<UiMaterialPipeline<M>>>() .init_resource::<SpecializedRenderPipelines<UiMaterialPipeline<M>>>()
.add_systems(RenderStartup, init_ui_material_pipeline::<M>)
.add_systems( .add_systems(
ExtractSchedule, ExtractSchedule,
extract_ui_material_nodes::<M>.in_set(RenderUiSystems::ExtractBackgrounds), extract_ui_material_nodes::<M>.in_set(RenderUiSystems::ExtractBackgrounds),
@ -74,12 +74,6 @@ where
); );
} }
} }
fn finish(&self, app: &mut App) {
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<UiMaterialPipeline<M>>();
}
}
} }
#[derive(Resource)] #[derive(Resource)]
@ -185,41 +179,41 @@ where
} }
} }
impl<M: UiMaterial> FromWorld for UiMaterialPipeline<M> { pub fn init_ui_material_pipeline<M: UiMaterial>(
fn from_world(world: &mut World) -> Self { mut commands: Commands,
let asset_server = world.resource::<AssetServer>(); render_device: Res<RenderDevice>,
let render_device = world.resource::<RenderDevice>(); asset_server: Res<AssetServer>,
let ui_layout = M::bind_group_layout(render_device); ) {
let ui_layout = M::bind_group_layout(&render_device);
let view_layout = render_device.create_bind_group_layout( let view_layout = render_device.create_bind_group_layout(
"ui_view_layout", "ui_view_layout",
&BindGroupLayoutEntries::sequential( &BindGroupLayoutEntries::sequential(
ShaderStages::VERTEX_FRAGMENT, ShaderStages::VERTEX_FRAGMENT,
( (
uniform_buffer::<ViewUniform>(true), uniform_buffer::<ViewUniform>(true),
uniform_buffer::<GlobalsUniform>(false), uniform_buffer::<GlobalsUniform>(false),
),
), ),
); ),
);
let load_default = || load_embedded_asset!(asset_server, "ui_material.wgsl"); let load_default = || load_embedded_asset!(asset_server.as_ref(), "ui_material.wgsl");
UiMaterialPipeline { commands.insert_resource(UiMaterialPipeline::<M> {
ui_layout, ui_layout,
view_layout, view_layout,
vertex_shader: match M::vertex_shader() { vertex_shader: match M::vertex_shader() {
ShaderRef::Default => load_default(), ShaderRef::Default => load_default(),
ShaderRef::Handle(handle) => handle, ShaderRef::Handle(handle) => handle,
ShaderRef::Path(path) => asset_server.load(path), ShaderRef::Path(path) => asset_server.load(path),
}, },
fragment_shader: match M::fragment_shader() { fragment_shader: match M::fragment_shader() {
ShaderRef::Default => load_default(), ShaderRef::Default => load_default(),
ShaderRef::Handle(handle) => handle, ShaderRef::Handle(handle) => handle,
ShaderRef::Path(path) => asset_server.load(path), ShaderRef::Path(path) => asset_server.load(path),
}, },
marker: PhantomData, marker: PhantomData,
} });
}
} }
pub type DrawUiMaterial<M> = ( pub type DrawUiMaterial<M> = (

View File

@ -13,7 +13,6 @@ use bevy_ecs::{
use bevy_image::prelude::*; use bevy_image::prelude::*;
use bevy_math::{Affine2, FloatOrd, Rect, Vec2}; use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
use bevy_platform::collections::HashMap; use bevy_platform::collections::HashMap;
use bevy_render::sync_world::MainEntity;
use bevy_render::{ use bevy_render::{
render_asset::RenderAssets, render_asset::RenderAssets,
render_phase::*, render_phase::*,
@ -23,6 +22,7 @@ use bevy_render::{
view::*, view::*,
Extract, ExtractSchedule, Render, RenderSystems, Extract, ExtractSchedule, Render, RenderSystems,
}; };
use bevy_render::{sync_world::MainEntity, RenderStartup};
use bevy_sprite::{SliceScaleMode, SpriteAssetEvents, SpriteImageMode, TextureSlicer}; use bevy_sprite::{SliceScaleMode, SpriteAssetEvents, SpriteImageMode, TextureSlicer};
use bevy_ui::widget; use bevy_ui::widget;
use bevy_utils::default; use bevy_utils::default;
@ -42,6 +42,7 @@ impl Plugin for UiTextureSlicerPlugin {
.init_resource::<UiTextureSliceMeta>() .init_resource::<UiTextureSliceMeta>()
.init_resource::<UiTextureSliceImageBindGroups>() .init_resource::<UiTextureSliceImageBindGroups>()
.init_resource::<SpecializedRenderPipelines<UiTextureSlicePipeline>>() .init_resource::<SpecializedRenderPipelines<UiTextureSlicePipeline>>()
.add_systems(RenderStartup, init_ui_texture_slice_pipeline)
.add_systems( .add_systems(
ExtractSchedule, ExtractSchedule,
extract_ui_texture_slices.in_set(RenderUiSystems::ExtractTextureSlice), extract_ui_texture_slices.in_set(RenderUiSystems::ExtractTextureSlice),
@ -55,12 +56,6 @@ impl Plugin for UiTextureSlicerPlugin {
); );
} }
} }
fn finish(&self, app: &mut App) {
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<UiTextureSlicePipeline>();
}
}
} }
#[repr(C)] #[repr(C)]
@ -110,35 +105,35 @@ pub struct UiTextureSlicePipeline {
pub shader: Handle<Shader>, pub shader: Handle<Shader>,
} }
impl FromWorld for UiTextureSlicePipeline { pub fn init_ui_texture_slice_pipeline(
fn from_world(world: &mut World) -> Self { mut commands: Commands,
let render_device = world.resource::<RenderDevice>(); render_device: Res<RenderDevice>,
asset_server: Res<AssetServer>,
) {
let view_layout = render_device.create_bind_group_layout(
"ui_texture_slice_view_layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX_FRAGMENT,
uniform_buffer::<ViewUniform>(true),
),
);
let view_layout = render_device.create_bind_group_layout( let image_layout = render_device.create_bind_group_layout(
"ui_texture_slice_view_layout", "ui_texture_slice_image_layout",
&BindGroupLayoutEntries::single( &BindGroupLayoutEntries::sequential(
ShaderStages::VERTEX_FRAGMENT, ShaderStages::FRAGMENT,
uniform_buffer::<ViewUniform>(true), (
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
), ),
); ),
);
let image_layout = render_device.create_bind_group_layout( commands.insert_resource(UiTextureSlicePipeline {
"ui_texture_slice_image_layout", view_layout,
&BindGroupLayoutEntries::sequential( image_layout,
ShaderStages::FRAGMENT, shader: load_embedded_asset!(asset_server.as_ref(), "ui_texture_slice.wgsl"),
( });
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
),
),
);
UiTextureSlicePipeline {
view_layout,
image_layout,
shader: load_embedded_asset!(world, "ui_texture_slice.wgsl"),
}
}
} }
#[derive(Clone, Copy, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Hash, PartialEq, Eq)]