Convert basic example cases to use RenderStartup.

This commit is contained in:
andriyDev 2025-07-11 23:08:46 -07:00
parent 33bed5dd70
commit 5c7dd2eec3
5 changed files with 87 additions and 109 deletions

View File

@ -27,7 +27,7 @@ use bevy::{
sync_component::SyncComponentPlugin, sync_component::SyncComponentPlugin,
sync_world::{MainEntityHashMap, RenderEntity}, sync_world::{MainEntityHashMap, RenderEntity},
view::{ExtractedView, RenderVisibleEntities, ViewTarget}, view::{ExtractedView, RenderVisibleEntities, ViewTarget},
Extract, Render, RenderApp, RenderSystems, Extract, Render, RenderApp, RenderStartup, RenderSystems,
}, },
sprite::{ sprite::{
extract_mesh2d, DrawMesh2d, Material2dBindGroupId, Mesh2dPipeline, Mesh2dPipelineKey, extract_mesh2d, DrawMesh2d, Material2dBindGroupId, Mesh2dPipeline, Mesh2dPipelineKey,
@ -132,14 +132,16 @@ pub struct ColoredMesh2dPipeline {
shader: Handle<Shader>, shader: Handle<Shader>,
} }
impl FromWorld for ColoredMesh2dPipeline { fn init_colored_mesh_2d_pipeline(
fn from_world(world: &mut World) -> Self { mut commands: Commands,
Self { mesh2d_pipeline: Res<Mesh2dPipeline>,
mesh2d_pipeline: Mesh2dPipeline::from_world(world), colored_mesh2d_shader: Res<ColoredMesh2dShader>,
// Get the shader from the shader resource we inserted in the plugin. ) {
shader: world.resource::<ColoredMesh2dShader>().0.clone(), commands.insert_resource(ColoredMesh2dPipeline {
} mesh2d_pipeline: mesh2d_pipeline.clone(),
} // Clone the shader from the shader resource we inserted in the plugin.
shader: colored_mesh2d_shader.0.clone(),
});
} }
// We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline` // We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline`
@ -307,6 +309,7 @@ impl Plugin for ColoredMesh2dPlugin {
.add_render_command::<Transparent2d, DrawColoredMesh2d>() .add_render_command::<Transparent2d, DrawColoredMesh2d>()
.init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>() .init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
.init_resource::<RenderColoredMesh2dInstances>() .init_resource::<RenderColoredMesh2dInstances>()
.add_systems(RenderStartup, init_colored_mesh_2d_pipeline)
.add_systems( .add_systems(
ExtractSchedule, ExtractSchedule,
extract_colored_mesh2d.after(extract_mesh2d), extract_colored_mesh2d.after(extract_mesh2d),
@ -316,13 +319,6 @@ impl Plugin for ColoredMesh2dPlugin {
queue_colored_mesh2d.in_set(RenderSystems::QueueMeshes), queue_colored_mesh2d.in_set(RenderSystems::QueueMeshes),
); );
} }
fn finish(&self, app: &mut App) {
// Register our custom pipeline
app.get_sub_app_mut(RenderApp)
.unwrap()
.init_resource::<ColoredMesh2dPipeline>();
}
} }
/// Extract the [`ColoredMesh2d`] marker component into the render app /// Extract the [`ColoredMesh2d`] marker component into the render app

View File

@ -12,7 +12,7 @@ use bevy::{
render_resource::{binding_types::texture_storage_2d, *}, render_resource::{binding_types::texture_storage_2d, *},
renderer::{RenderContext, RenderDevice}, renderer::{RenderContext, RenderDevice},
texture::GpuImage, texture::GpuImage,
Render, RenderApp, RenderSystems, Render, RenderApp, RenderStartup, RenderSystems,
}, },
}; };
use std::borrow::Cow; use std::borrow::Cow;
@ -103,20 +103,17 @@ impl Plugin for GameOfLifeComputePlugin {
// for operation on by the compute shader and display on the sprite. // for operation on by the compute shader and display on the sprite.
app.add_plugins(ExtractResourcePlugin::<GameOfLifeImages>::default()); app.add_plugins(ExtractResourcePlugin::<GameOfLifeImages>::default());
let render_app = app.sub_app_mut(RenderApp); let render_app = app.sub_app_mut(RenderApp);
render_app.add_systems( render_app
Render, .add_systems(RenderStartup, init_game_of_life_pipeline)
prepare_bind_group.in_set(RenderSystems::PrepareBindGroups), .add_systems(
); Render,
prepare_bind_group.in_set(RenderSystems::PrepareBindGroups),
);
let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>(); let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>();
render_graph.add_node(GameOfLifeLabel, GameOfLifeNode::default()); render_graph.add_node(GameOfLifeLabel, GameOfLifeNode::default());
render_graph.add_node_edge(GameOfLifeLabel, bevy::render::graph::CameraDriverLabel); render_graph.add_node_edge(GameOfLifeLabel, bevy::render::graph::CameraDriverLabel);
} }
fn finish(&self, app: &mut App) {
let render_app = app.sub_app_mut(RenderApp);
render_app.init_resource::<GameOfLifePipeline>();
}
} }
#[derive(Resource, Clone, ExtractResource)] #[derive(Resource, Clone, ExtractResource)]
@ -157,40 +154,41 @@ struct GameOfLifePipeline {
update_pipeline: CachedComputePipelineId, update_pipeline: CachedComputePipelineId,
} }
impl FromWorld for GameOfLifePipeline { fn init_game_of_life_pipeline(
fn from_world(world: &mut World) -> Self { mut commands: Commands,
let render_device = world.resource::<RenderDevice>(); render_device: Res<RenderDevice>,
let texture_bind_group_layout = render_device.create_bind_group_layout( asset_server: Res<AssetServer>,
"GameOfLifeImages", pipeline_cache: Res<PipelineCache>,
&BindGroupLayoutEntries::sequential( ) {
ShaderStages::COMPUTE, let texture_bind_group_layout = render_device.create_bind_group_layout(
( "GameOfLifeImages",
texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::ReadOnly), &BindGroupLayoutEntries::sequential(
texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly), ShaderStages::COMPUTE,
), (
texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::ReadOnly),
texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
), ),
); ),
let shader = world.load_asset(SHADER_ASSET_PATH); );
let pipeline_cache = world.resource::<PipelineCache>(); let shader = asset_server.load(SHADER_ASSET_PATH);
let init_pipeline = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { let init_pipeline = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
layout: vec![texture_bind_group_layout.clone()], layout: vec![texture_bind_group_layout.clone()],
shader: shader.clone(), shader: shader.clone(),
entry_point: Some(Cow::from("init")), entry_point: Some(Cow::from("init")),
..default() ..default()
}); });
let update_pipeline = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { let update_pipeline = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
layout: vec![texture_bind_group_layout.clone()], layout: vec![texture_bind_group_layout.clone()],
shader, shader,
entry_point: Some(Cow::from("update")), entry_point: Some(Cow::from("update")),
..default() ..default()
}); });
GameOfLifePipeline { commands.insert_resource(GameOfLifePipeline {
texture_bind_group_layout, texture_bind_group_layout,
init_pipeline, init_pipeline,
update_pipeline, update_pipeline,
} });
}
} }
enum GameOfLifeState { enum GameOfLifeState {

View File

@ -55,7 +55,7 @@ use bevy::{
renderer::RenderContext, renderer::RenderContext,
sync_world::MainEntity, sync_world::MainEntity,
view::{ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewTarget}, view::{ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewTarget},
Extract, Render, RenderApp, RenderDebugFlags, RenderSystems, Extract, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems,
}, },
}; };
use nonmax::NonMaxU32; use nonmax::NonMaxU32;
@ -127,6 +127,7 @@ impl Plugin for MeshStencilPhasePlugin {
.init_resource::<DrawFunctions<Stencil3d>>() .init_resource::<DrawFunctions<Stencil3d>>()
.add_render_command::<Stencil3d, DrawMesh3dStencil>() .add_render_command::<Stencil3d, DrawMesh3dStencil>()
.init_resource::<ViewSortedRenderPhases<Stencil3d>>() .init_resource::<ViewSortedRenderPhases<Stencil3d>>()
.add_systems(RenderStartup, init_stencil_pipeline)
.add_systems(ExtractSchedule, extract_camera_phases) .add_systems(ExtractSchedule, extract_camera_phases)
.add_systems( .add_systems(
Render, Render,
@ -143,16 +144,6 @@ impl Plugin for MeshStencilPhasePlugin {
// Tell the node to run after the main pass // Tell the node to run after the main pass
.add_render_graph_edges(Core3d, (Node3d::MainOpaquePass, CustomDrawPassLabel)); .add_render_graph_edges(Core3d, (Node3d::MainOpaquePass, CustomDrawPassLabel));
} }
fn finish(&self, app: &mut App) {
// We need to get the render app from the main app
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
// The pipeline needs the RenderDevice to be created and it's only available once plugins
// are initialized
render_app.init_resource::<StencilPipeline>();
}
} }
#[derive(Resource)] #[derive(Resource)]
@ -167,13 +158,15 @@ struct StencilPipeline {
shader_handle: Handle<Shader>, shader_handle: Handle<Shader>,
} }
impl FromWorld for StencilPipeline { fn init_stencil_pipeline(
fn from_world(world: &mut World) -> Self { mut commands: Commands,
Self { mesh_pipeline: Res<MeshPipeline>,
mesh_pipeline: MeshPipeline::from_world(world), asset_server: Res<AssetServer>,
shader_handle: world.resource::<AssetServer>().load(SHADER_ASSET_PATH), ) {
} commands.insert_resource(StencilPipeline {
} mesh_pipeline: mesh_pipeline.clone(),
shader_handle: asset_server.load(SHADER_ASSET_PATH),
});
} }
// For more information on how SpecializedMeshPipeline work, please look at the // For more information on how SpecializedMeshPipeline work, please look at the

View File

@ -32,7 +32,7 @@ use bevy::{
renderer::RenderDevice, renderer::RenderDevice,
sync_world::MainEntity, sync_world::MainEntity,
view::{ExtractedView, NoFrustumCulling, NoIndirectDrawing}, view::{ExtractedView, NoFrustumCulling, NoIndirectDrawing},
Render, RenderApp, RenderSystems, Render, RenderApp, RenderStartup, RenderSystems,
}, },
}; };
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
@ -102,6 +102,7 @@ impl Plugin for CustomMaterialPlugin {
app.sub_app_mut(RenderApp) app.sub_app_mut(RenderApp)
.add_render_command::<Transparent3d, DrawCustom>() .add_render_command::<Transparent3d, DrawCustom>()
.init_resource::<SpecializedMeshPipelines<CustomPipeline>>() .init_resource::<SpecializedMeshPipelines<CustomPipeline>>()
.add_systems(RenderStartup, init_custom_pipeline)
.add_systems( .add_systems(
Render, Render,
( (
@ -110,10 +111,6 @@ impl Plugin for CustomMaterialPlugin {
), ),
); );
} }
fn finish(&self, app: &mut App) {
app.sub_app_mut(RenderApp).init_resource::<CustomPipeline>();
}
} }
#[derive(Clone, Copy, Pod, Zeroable)] #[derive(Clone, Copy, Pod, Zeroable)]
@ -203,15 +200,15 @@ struct CustomPipeline {
mesh_pipeline: MeshPipeline, mesh_pipeline: MeshPipeline,
} }
impl FromWorld for CustomPipeline { fn init_custom_pipeline(
fn from_world(world: &mut World) -> Self { mut commands: Commands,
let mesh_pipeline = world.resource::<MeshPipeline>(); asset_server: Res<AssetServer>,
mesh_pipeline: Res<MeshPipeline>,
CustomPipeline { ) {
shader: world.load_asset(SHADER_ASSET_PATH), commands.insert_resource(CustomPipeline {
mesh_pipeline: mesh_pipeline.clone(), shader: asset_server.load(SHADER_ASSET_PATH),
} mesh_pipeline: mesh_pipeline.clone(),
} });
} }
impl SpecializedMeshPipeline for CustomPipeline { impl SpecializedMeshPipeline for CustomPipeline {

View File

@ -39,7 +39,7 @@ use bevy::{
}, },
view::NoIndirectDrawing, view::NoIndirectDrawing,
view::{self, ExtractedView, RenderVisibleEntities, ViewTarget, VisibilityClass}, view::{self, ExtractedView, RenderVisibleEntities, ViewTarget, VisibilityClass},
Render, RenderApp, RenderSystems, Render, RenderApp, RenderStartup, RenderSystems,
}, },
}; };
@ -118,20 +118,12 @@ impl Plugin for CustomRenderedMeshPipelinePlugin {
.init_resource::<SpecializedMeshPipelines<CustomMeshPipeline>>() .init_resource::<SpecializedMeshPipelines<CustomMeshPipeline>>()
// We need to use a custom draw command so we need to register it // We need to use a custom draw command so we need to register it
.add_render_command::<Opaque3d, DrawSpecializedPipelineCommands>() .add_render_command::<Opaque3d, DrawSpecializedPipelineCommands>()
.add_systems(RenderStartup, init_custom_mesh_pipeline)
.add_systems( .add_systems(
Render, Render,
queue_custom_mesh_pipeline.in_set(RenderSystems::Queue), queue_custom_mesh_pipeline.in_set(RenderSystems::Queue),
); );
} }
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
// Creating this pipeline needs the RenderDevice and RenderQueue
// which are only available once rendering plugins are initialized.
render_app.init_resource::<CustomMeshPipeline>();
}
} }
/// A marker component that represents an entity that is to be rendered using /// A marker component that represents an entity that is to be rendered using
@ -174,15 +166,17 @@ struct CustomMeshPipeline {
shader_handle: Handle<Shader>, shader_handle: Handle<Shader>,
} }
impl FromWorld for CustomMeshPipeline { fn init_custom_mesh_pipeline(
fn from_world(world: &mut World) -> Self { mut commands: Commands,
// Load the shader asset_server: Res<AssetServer>,
let shader_handle: Handle<Shader> = world.resource::<AssetServer>().load(SHADER_ASSET_PATH); mesh_pipeline: Res<MeshPipeline>,
Self { ) {
mesh_pipeline: MeshPipeline::from_world(world), // Load the shader
shader_handle, let shader_handle: Handle<Shader> = asset_server.load(SHADER_ASSET_PATH);
} commands.insert_resource(CustomMeshPipeline {
} mesh_pipeline: mesh_pipeline.clone(),
shader_handle,
});
} }
impl SpecializedMeshPipeline for CustomMeshPipeline { impl SpecializedMeshPipeline for CustomMeshPipeline {