Skip to content

Commit

Permalink
simplify example
Browse files Browse the repository at this point in the history
  • Loading branch information
IceSentry committed Dec 11, 2022
1 parent d891f8a commit 69c2911
Showing 1 changed file with 91 additions and 145 deletions.
236 changes: 91 additions & 145 deletions examples/shader/post_process_pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
},
};

Expand All @@ -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::<PostProcessPipeline>()
.init_resource::<SpecializedRenderPipelines<PostProcessPipeline>>()
.add_system_to_stage(RenderStage::Prepare, prepare_pipeline);
render_app.init_resource::<PostProcessPipeline>();

// Create our node with the render world
let node = PostProcessNode::new(&mut render_app.world);

// Get the render graph for the entire app
Expand Down Expand Up @@ -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<ExtractedView>>,
// TODO avoid using a mutex
cached_texture_bind_group: Mutex<Option<(TextureViewId, BindGroup)>>,
query: QueryState<&'static ViewTarget, With<ExtractedView>>,
}

impl PostProcessNode {
Expand All @@ -106,7 +99,6 @@ impl PostProcessNode {
fn new(world: &mut World) -> Self {
Self {
query: QueryState::new(world),
cached_texture_bind_group: Mutex::new(None),
}
}
}
Expand Down Expand Up @@ -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::<PostProcessPipeline>();
let post_process_pipeline = world.resource::<PostProcessPipeline>();
// 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::<PipelineCache>();

// 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(());
};

Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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<Shader>,
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::<RenderDevice>();

// We need to define the bind group layout used for our pipeline
let layout =
world
.resource::<RenderDevice>()
.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::<AssetServer>()
.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::<PipelineCache>()
.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<PipelineCache>,
post_process_pipeline: Res<PostProcessPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<PostProcessPipeline>>,
views: Query<Entity, With<ExtractedView>>,
) {
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,
Expand Down

0 comments on commit 69c2911

Please sign in to comment.