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_math::{vec2, Affine2, FloatOrd, Rect, Vec2};
use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity};
use bevy_render::RenderApp;
use bevy_render::{
render_phase::*,
render_resource::{binding_types::uniform_buffer, *},
@ -24,6 +23,7 @@ use bevy_render::{
view::*,
Extract, ExtractSchedule, Render, RenderSystems,
};
use bevy_render::{RenderApp, RenderStartup};
use bevy_ui::{
BoxShadow, CalculatedClip, ComputedNode, ComputedNodeTarget, ResolvedBorderRadius,
UiGlobalTransform, Val,
@ -48,6 +48,7 @@ impl Plugin for BoxShadowPlugin {
.init_resource::<ExtractedBoxShadows>()
.init_resource::<BoxShadowMeta>()
.init_resource::<SpecializedRenderPipelines<BoxShadowPipeline>>()
.add_systems(RenderStartup, init_box_shadow_pipeline)
.add_systems(
ExtractSchedule,
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)]
@ -111,23 +106,23 @@ pub struct BoxShadowPipeline {
pub shader: Handle<Shader>,
}
impl FromWorld for BoxShadowPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
pub fn init_box_shadow_pipeline(
mut commands: Commands,
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(
"box_shadow_view_layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX_FRAGMENT,
uniform_buffer::<ViewUniform>(true),
),
);
BoxShadowPipeline {
view_layout,
shader: load_embedded_asset!(world, "box_shadow.wgsl"),
}
}
commands.insert_resource(BoxShadowPipeline {
view_layout,
shader: load_embedded_asset!(asset_server.as_ref(), "box_shadow.wgsl"),
});
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]

View File

@ -21,7 +21,6 @@ use bevy_math::{
FloatOrd, Rect, Vec2,
};
use bevy_math::{Affine2, Vec2Swizzles};
use bevy_render::sync_world::MainEntity;
use bevy_render::{
render_phase::*,
render_resource::{binding_types::uniform_buffer, *},
@ -30,6 +29,7 @@ use bevy_render::{
view::*,
Extract, ExtractSchedule, Render, RenderSystems,
};
use bevy_render::{sync_world::MainEntity, RenderStartup};
use bevy_sprite::BorderRect;
use bevy_ui::{
BackgroundGradient, BorderGradient, ColorStop, ConicGradient, Gradient,
@ -51,6 +51,7 @@ impl Plugin for GradientPlugin {
.init_resource::<ExtractedColorStops>()
.init_resource::<GradientMeta>()
.init_resource::<SpecializedRenderPipelines<GradientPipeline>>()
.add_systems(RenderStartup, init_gradient_pipeline)
.add_systems(
ExtractSchedule,
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)]
@ -102,23 +97,23 @@ pub struct GradientPipeline {
pub shader: Handle<Shader>,
}
impl FromWorld for GradientPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
pub fn init_gradient_pipeline(
mut commands: Commands,
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(
"ui_gradient_view_layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX_FRAGMENT,
uniform_buffer::<ViewUniform>(true),
),
);
GradientPipeline {
view_layout,
shader: load_embedded_asset!(world, "gradient.wgsl"),
}
}
commands.insert_resource(GradientPipeline {
view_layout,
shader: load_embedded_asset!(asset_server.as_ref(), "gradient.wgsl"),
});
}
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_image::prelude::*;
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_phase::ViewSortedRenderPhases;
use bevy_render::renderer::RenderContext;
@ -53,6 +52,7 @@ use bevy_render::{
view::{ExtractedView, ViewUniforms},
Extract, RenderApp, RenderSystems,
};
use bevy_render::{load_shader_library, RenderStartup};
use bevy_render::{
render_phase::{PhaseItem, PhaseItemExtraIndex},
sync_world::{RenderEntity, TemporaryRenderEntity},
@ -243,6 +243,7 @@ impl Plugin for UiRenderPlugin {
)
.chain(),
)
.add_systems(RenderStartup, init_ui_pipeline)
.add_systems(
ExtractSchedule,
(
@ -292,14 +293,6 @@ impl Plugin for UiRenderPlugin {
app.add_plugins(GradientPlugin);
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 {

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

View File

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

View File

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