diff --git a/examples/shader/post_process_pass.rs b/examples/shader/post_process_pass.rs index 8fb52380ae583f..a3462ef90084c1 100644 --- a/examples/shader/post_process_pass.rs +++ b/examples/shader/post_process_pass.rs @@ -5,8 +5,6 @@ //! //! This is a fairly low level example and assumes some familiarity with rendering concepts and wgpu. -use std::sync::Mutex; - use bevy::{ core_pipeline::{ clear_color::ClearColorConfig, core_3d, @@ -16,18 +14,17 @@ use bevy::{ render::{ render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, render_resource::{ - BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, - BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, - CachedRenderPipelineId, ColorTargetState, ColorWrites, FilterMode, FragmentState, - MultisampleState, Operations, PipelineCache, PrimitiveState, RenderPassColorAttachment, - RenderPassDescriptor, RenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, - ShaderStages, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat, - TextureSampleType, TextureViewDimension, TextureViewId, + BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingResource, BindingType, CachedRenderPipelineId, + ColorTargetState, ColorWrites, FragmentState, MultisampleState, Operations, + PipelineCache, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor, + RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages, + TextureFormat, TextureSampleType, TextureViewDimension, }, renderer::{RenderContext, RenderDevice}, texture::BevyDefault, view::{ExtractedView, ViewTarget}, - RenderApp, RenderStage, + RenderApp, }, }; @@ -54,11 +51,9 @@ impl Plugin for PostProcessPlugin { // The renderer has multiple stages. For more details see the docs for RenderStage // Extract -> Prepare -> Queue -> PhaseSort -> Render -> CleanUp - render_app - .init_resource::() - .init_resource::>() - .add_system_to_stage(RenderStage::Prepare, prepare_pipeline); + render_app.init_resource::(); + // Create our node with the render world let node = PostProcessNode::new(&mut render_app.world); // Get the render graph for the entire app @@ -94,9 +89,7 @@ impl Plugin for PostProcessPlugin { struct PostProcessNode { // The node needs a query to know how to render, // but it's not a normal system so we need to define it here. - query: QueryState<(&'static ViewTarget, &'static ViewPostProcessPipeline), With>, - // TODO avoid using a mutex - cached_texture_bind_group: Mutex>, + query: QueryState<&'static ViewTarget, With>, } impl PostProcessNode { @@ -106,7 +99,6 @@ impl PostProcessNode { fn new(world: &mut World) -> Self { Self { query: QueryState::new(world), - cached_texture_bind_group: Mutex::new(None), } } } @@ -136,18 +128,18 @@ impl Node for PostProcessNode { let view_entity = graph_context.get_input_entity(PostProcessNode::IN_VIEW)?; // We get the data we need from the world based on the view entity. - let Ok((view_target, view_pipeline)) = self.query.get_manual(world, view_entity) else { + let Ok(view_target) = self.query.get_manual(world, view_entity) else { return Ok(()); }; // Get the pipeline resource that contains the global data we need to create the render pipeline - let pipeline_res = world.resource::(); + let post_process_pipeline = world.resource::(); // The pipeline cache is a cache of all previously created pipelines. // It's required to avoid creating a new pipeline each frame. let pipeline_cache = world.resource::(); // Get the pipeline data for the current view - let Some(pipeline) = pipeline_cache.get_render_pipeline(view_pipeline.pipeline_id) else { + let Some(pipeline) = pipeline_cache.get_render_pipeline(post_process_pipeline.pipeline_id) else { return Ok(()); }; @@ -156,46 +148,30 @@ impl Node for PostProcessNode { let source = post_process.source; let destination = post_process.destination; - // TODO find a simpler way to do this. Could this be done in a queue_ system? - let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap(); - let bind_group = match &mut *cached_bind_group { - Some((id, bind_group)) if source.id() == *id => bind_group, - cached_bind_group => { - let sampler = render_context - .render_device - .create_sampler(&SamplerDescriptor { - mipmap_filter: FilterMode::Linear, - mag_filter: FilterMode::Linear, - min_filter: FilterMode::Linear, - ..default() - }); - - let bind_group = - render_context - .render_device - .create_bind_group(&BindGroupDescriptor { - label: None, - layout: &pipeline_res.layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(source), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&sampler), - }, - ], - }); - - let (_, bind_group) = cached_bind_group.insert((source.id(), bind_group)); - bind_group - } + let bind_group_descriptor = BindGroupDescriptor { + label: Some("post_process_bind_group"), + layout: &post_process_pipeline.layout, + entries: &[ + BindGroupEntry { + binding: 0, + // Make sure to use the source view + resource: BindingResource::TextureView(source), + }, + BindGroupEntry { + binding: 1, + // Use the sampler created for the pipeline + resource: BindingResource::Sampler(&post_process_pipeline.sampler), + }, + ], }; + // The bind_group gets created each frame. This isn't the most efficient, implementing a cache on top is recommended. + let bind_group = render_context + .render_device + .create_bind_group(&bind_group_descriptor); + let descriptor = RenderPassDescriptor { label: Some("post_process_pass"), - // TODO could bevy provide an already configured destination color attachment? color_attachments: &[Some(RenderPassColorAttachment { // We need to specify the post process destination view here to make sure we write to the appropriate texture. view: destination, @@ -214,7 +190,7 @@ impl Node for PostProcessNode { render_pass.set_pipeline(pipeline); // Set the bind group - render_pass.set_bind_group(0, bind_group, &[]); + render_pass.set_bind_group(0, &bind_group, &[]); // In this case we want to draw a fullscreen triangle, so we just need to send a single draw call. render_pass.draw(0..3, 0..1); @@ -223,112 +199,82 @@ impl Node for PostProcessNode { } } -// This contains global data used by our pipeline. This will be created once on startup. +// This contains global data used by the render pipeline. This will be created once on startup. #[derive(Resource)] struct PostProcessPipeline { layout: BindGroupLayout, - shader: Handle, + sampler: Sampler, + pipeline_id: CachedRenderPipelineId, } -// TODO figure out how to use AsBindGroup here 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 = - world - .resource::() - .create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("post_process_bind_group_layout"), - entries: &[ - // This will be the screen texture - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Texture { - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - // The sampler that will be used to sample the screen texture - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - ], - }); + let layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("post_process_bind_group_layout"), + entries: &[ + // This will be the screen texture + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + // The sampler that will be used to sample the screen texture + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + // 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 .resource::() .load("shaders/post_process_pass.wgsl"); - Self { layout, shader } - } -} - -// This contains data needed for the pipeline used by each view -#[derive(Component)] -struct ViewPostProcessPipeline { - pipeline_id: CachedRenderPipelineId, -} + let pipeline_id = + world + .resource_mut::() + .queue_render_pipeline(RenderPipelineDescriptor { + label: Some("post_process_pipeline".into()), + layout: Some(vec![layout.clone()]), + // This will setup a fullscreen triangle for the vertex state + vertex: fullscreen_shader_vertex_state(), + fragment: Some(FragmentState { + shader, + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::bevy_default(), + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + }); -// This is required for the SpecializedRenderPipeline. For this example we don't need to specialize the key. -#[derive(PartialEq, Eq, Hash, Clone, Copy)] -pub struct PostProcessPipelineKey {} - -impl SpecializedRenderPipeline for PostProcessPipeline { - type Key = PostProcessPipelineKey; - - fn specialize(&self, _key: Self::Key) -> RenderPipelineDescriptor { - RenderPipelineDescriptor { - label: Some("post_process".into()), - layout: Some(vec![self.layout.clone()]), - // This will setup a fullscreen triangle for the vertex state - vertex: fullscreen_shader_vertex_state(), - fragment: Some(FragmentState { - shader: self.shader.clone(), - shader_defs: vec![], - entry_point: "fragment".into(), - targets: vec![Some(ColorTargetState { - format: TextureFormat::bevy_default(), - blend: None, - write_mask: ColorWrites::ALL, - })], - }), - primitive: PrimitiveState::default(), - depth_stencil: None, - multisample: MultisampleState::default(), + Self { + layout, + sampler, + pipeline_id, } } } -// This will specialize the pipeline for each view. For this example we don't do anything special, -// but you could for example specialize based on the texture format of the view. -// -// Runs every frame -fn prepare_pipeline( - mut commands: Commands, - mut pipeline_cache: ResMut, - post_process_pipeline: Res, - mut pipelines: ResMut>, - views: Query>, -) { - for entity in &views { - let pipeline_id = pipelines.specialize( - &mut pipeline_cache, - &post_process_pipeline, - PostProcessPipelineKey {}, - ); - commands - .entity(entity) - .insert(ViewPostProcessPipeline { pipeline_id }); - } -} - /// set up a simple 3D scene fn setup( mut commands: Commands,