From 735eb88db98d5cce244e45a2e415fda4da6458b0 Mon Sep 17 00:00:00 2001 From: IceSentry Date: Mon, 30 Jun 2025 19:54:05 -0400 Subject: [PATCH] Use RenderStartup in custom_post_processing example (#19886) # Objective - This example uses a FromWorld impl to initialize a resource on startup - #19887 ## Solution - Use RenderStartup instead ## Testing - The example still works as expected --- examples/shader/custom_post_processing.rs | 137 ++++++++++------------ 1 file changed, 64 insertions(+), 73 deletions(-) diff --git a/examples/shader/custom_post_processing.rs b/examples/shader/custom_post_processing.rs index 64bf312c03..a075dfbdc1 100644 --- a/examples/shader/custom_post_processing.rs +++ b/examples/shader/custom_post_processing.rs @@ -27,7 +27,7 @@ use bevy::{ }, renderer::{RenderContext, RenderDevice}, view::ViewTarget, - RenderApp, + RenderApp, RenderStartup, }, }; @@ -66,6 +66,10 @@ impl Plugin for PostProcessPlugin { return; }; + // RenderStartup runs once on startup after all plugins are built + // It is useful to initialize data that will only live in the RenderApp + render_app.add_systems(RenderStartup, setup_pipeline); + render_app // Bevy's renderer uses a render graph which is a collection of nodes in a directed acyclic graph. // It currently runs on each view/camera and executes each node in the specified order. @@ -97,17 +101,6 @@ impl Plugin for PostProcessPlugin { ), ); } - - 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; - }; - - render_app - // Initialize the pipeline - .init_resource::(); - } } #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] @@ -233,69 +226,67 @@ struct PostProcessPipeline { pipeline_id: CachedRenderPipelineId, } -impl FromWorld for PostProcessPipeline { - fn from_world(world: &mut World) -> Self { - let render_device = world.resource::(); - - // We need to define the bind group layout used for our pipeline - let layout = render_device.create_bind_group_layout( - "post_process_bind_group_layout", - &BindGroupLayoutEntries::sequential( - // The layout entries will only be visible in the fragment stage - ShaderStages::FRAGMENT, - ( - // The screen texture - texture_2d(TextureSampleType::Float { filterable: true }), - // The sampler that will be used to sample the screen texture - sampler(SamplerBindingType::Filtering), - // The settings uniform that will control the effect - uniform_buffer::(true), - ), +fn setup_pipeline( + mut commands: Commands, + render_device: Res, + asset_server: Res, + fullscreen_shader: Res, + pipeline_cache: Res, +) { + // We need to define the bind group layout used for our pipeline + let layout = render_device.create_bind_group_layout( + "post_process_bind_group_layout", + &BindGroupLayoutEntries::sequential( + // The layout entries will only be visible in the fragment stage + ShaderStages::FRAGMENT, + ( + // The screen texture + texture_2d(TextureSampleType::Float { filterable: true }), + // The sampler that will be used to sample the screen texture + sampler(SamplerBindingType::Filtering), + // The settings uniform that will control the effect + uniform_buffer::(true), ), - ); + ), + ); + // We can create the sampler here since it won't change at runtime and doesn't depend on the view + let sampler = render_device.create_sampler(&SamplerDescriptor::default()); - // We can create the sampler here since it won't change at runtime and doesn't depend on the view - let sampler = render_device.create_sampler(&SamplerDescriptor::default()); - - // Get the shader handle - let shader = world.load_asset(SHADER_ASSET_PATH); - // This will setup a fullscreen triangle for the vertex state. - let vertex_state = world.resource::().to_vertex_state(); - - let pipeline_id = world - .resource_mut::() - // This will add the pipeline to the cache and queue its creation - .queue_render_pipeline(RenderPipelineDescriptor { - label: Some("post_process_pipeline".into()), - layout: vec![layout.clone()], - vertex: vertex_state, - fragment: Some(FragmentState { - shader, - shader_defs: vec![], - // Make sure this matches the entry point of your shader. - // It can be anything as long as it matches here and in the shader. - entry_point: "fragment".into(), - targets: vec![Some(ColorTargetState { - format: TextureFormat::bevy_default(), - blend: None, - write_mask: ColorWrites::ALL, - })], - }), - // All of the following properties are not important for this effect so just use the default values. - // This struct doesn't have the Default trait implemented because not all fields can have a default value. - primitive: PrimitiveState::default(), - depth_stencil: None, - multisample: MultisampleState::default(), - push_constant_ranges: vec![], - zero_initialize_workgroup_memory: false, - }); - - Self { - layout, - sampler, - pipeline_id, - } - } + // Get the shader handle + let shader = asset_server.load(SHADER_ASSET_PATH); + // This will setup a fullscreen triangle for the vertex state. + let vertex_state = fullscreen_shader.to_vertex_state(); + let pipeline_id = pipeline_cache + // This will add the pipeline to the cache and queue its creation + .queue_render_pipeline(RenderPipelineDescriptor { + label: Some("post_process_pipeline".into()), + layout: vec![layout.clone()], + vertex: vertex_state, + fragment: Some(FragmentState { + shader, + shader_defs: vec![], + // Make sure this matches the entry point of your shader. + // It can be anything as long as it matches here and in the shader. + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::bevy_default(), + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + // All of the following properties are not important for this effect so just use the default values. + // This struct doesn't have the Default trait implemented because not all fields can have a default value. + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + push_constant_ranges: vec![], + zero_initialize_workgroup_memory: false, + }); + commands.insert_resource(PostProcessPipeline { + layout, + sampler, + pipeline_id, + }); } // This is the component that will get passed to the shader