use crate::{
light::{LightTextures, VARIANCE_TEXTURE_FORMAT},
prepass::{DeferredBindGroup, PrepassBindGroup, PrepassPipeline, PrepassTextures},
view::{FrameCounter, FrameUniform, PreviousViewUniformOffset},
HikariSettings, Taa, Upscale, DENOISE_SHADER_HANDLE, FSR1_EASU_SHADER_HANDLE,
FSR1_RCAS_SHADER_HANDLE, SMAA_SHADER_HANDLE, TAA_SHADER_HANDLE, TONE_MAPPING_SHADER_HANDLE,
WORKGROUP_SIZE,
};
use bevy::{
ecs::query::QueryItem,
pbr::ViewLightsUniformOffset,
prelude::*,
render::{
camera::ExtractedCamera,
extract_component::{
ComponentUniforms, DynamicUniformIndex, ExtractComponent, ExtractComponentPlugin,
UniformComponentPlugin,
},
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_resource::*,
renderer::{RenderContext, RenderDevice},
texture::TextureCache,
view::ViewUniformOffset,
RenderApp, RenderStage,
},
};
use serde::Serialize;
pub const HDR_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rgba16Float;
pub struct PostProcessPlugin;
impl Plugin for PostProcessPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(ExtractComponentPlugin::<FsrConstantsUniform>::default())
.add_plugin(UniformComponentPlugin::<FsrConstantsUniform>::default());
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<PostProcessPipeline>()
.init_resource::<SpecializedComputePipelines<PostProcessPipeline>>()
.add_system_to_stage(RenderStage::Prepare, prepare_post_process_textures)
.add_system_to_stage(RenderStage::Queue, queue_post_process_pipelines)
.add_system_to_stage(RenderStage::Queue, queue_post_process_bind_groups);
}
}
}
#[derive(Resource)]
pub struct PostProcessPipeline {
pub view_layout: BindGroupLayout,
pub deferred_layout: BindGroupLayout,
pub sampler_layout: BindGroupLayout,
pub denoise_internal_layout: BindGroupLayout,
pub denoise_render_layout: BindGroupLayout,
pub tone_mapping_layout: BindGroupLayout,
pub smaa_layout: BindGroupLayout,
pub taa_layout: BindGroupLayout,
pub upscale_layout: BindGroupLayout,
pub output_layout: BindGroupLayout,
}
impl FromWorld for PostProcessPipeline {
fn from_world(world: &mut World) -> Self {
let view_layout = world.resource::<PrepassPipeline>().view_layout.clone();
let render_device = world.resource::<RenderDevice>();
let deferred_layout = PrepassTextures::bind_group_layout(render_device);
let sampler_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: None,
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::all(),
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::all(),
ty: BindingType::Sampler(SamplerBindingType::Filtering),
count: None,
},
],
});
let denoise_internal_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: None,
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::COMPUTE,
ty: BindingType::StorageTexture {
access: StorageTextureAccess::ReadWrite,
format: HDR_TEXTURE_FORMAT,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::COMPUTE,
ty: BindingType::StorageTexture {
access: StorageTextureAccess::ReadWrite,
format: HDR_TEXTURE_FORMAT,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::COMPUTE,
ty: BindingType::StorageTexture {
access: StorageTextureAccess::ReadWrite,
format: HDR_TEXTURE_FORMAT,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
BindGroupLayoutEntry {
binding: 3,
visibility: ShaderStages::COMPUTE,
ty: BindingType::StorageTexture {
access: StorageTextureAccess::ReadWrite,
format: HDR_TEXTURE_FORMAT,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
BindGroupLayoutEntry {
binding: 4,
visibility: ShaderStages::COMPUTE,
ty: BindingType::StorageTexture {
access: StorageTextureAccess::ReadWrite,
format: VARIANCE_TEXTURE_FORMAT,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
],
});
let denoise_render_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: None,
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
BindGroupLayoutEntry {
binding: 3,
visibility: ShaderStages::COMPUTE,
ty: BindingType::StorageTexture {
access: StorageTextureAccess::ReadWrite,
format: HDR_TEXTURE_FORMAT,
view_dimension: TextureViewDimension::D2,
},
count: None,
},
],
});
let tone_mapping_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: None,
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
BindGroupLayoutEntry {
binding: 2,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
});
let smaa_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: None,
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
});
let taa_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: None,
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
});
let upscale_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: None,
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: Some(FsrConstantsUniform::min_size()),
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::COMPUTE,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: true },
view_dimension: TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
});
let output_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: None,
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::all(),
ty: BindingType::StorageTexture {
access: StorageTextureAccess::ReadWrite,
format: HDR_TEXTURE_FORMAT,
view_dimension: TextureViewDimension::D2,
},
count: None,
}],
});
Self {
view_layout,
deferred_layout,
sampler_layout,
denoise_internal_layout,
denoise_render_layout,
tone_mapping_layout,
smaa_layout,
taa_layout,
upscale_layout,
output_layout,
}
}
}
#[repr(C)]
#[derive(Default, Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, FromPrimitive)]
#[serde(rename_all = "snake_case")]
pub enum PostProcessEntryPoint {
#[default]
Demodulation = 0,
Denoise = 1,
ToneMapping = 2,
TaaJasmine = 3,
SmaaTu4x = 4,
SmaaTu4xExtrapolate = 5,
Upscale = 6,
UpscaleSharpen = 7,
}
bitflags::bitflags! {
#[repr(transparent)]
pub struct PostProcessPipelineKey: u32 {
const ENTRY_POINT_BITS = PostProcessPipelineKey::ENTRY_POINT_MASK_BITS;
const FIREFLY_FILTERING_BITS = 1 << PostProcessPipelineKey::FIREFLY_FILTERING_SHIFT_BITS;
const DENOISE_LEVEL_BITS = PostProcessPipelineKey::DENOISE_LEVEL_MASK_BITS << PostProcessPipelineKey::DENOISE_LEVEL_SHIFT_BITS;
}
}
impl PostProcessPipelineKey {
const ENTRY_POINT_MASK_BITS: u32 = 0xF;
const DENOISE_LEVEL_MASK_BITS: u32 = 0b11;
const DENOISE_LEVEL_SHIFT_BITS: u32 = 32 - 2;
const FIREFLY_FILTERING_SHIFT_BITS: u32 = 8;
pub fn from_entry_point(entry_point: PostProcessEntryPoint) -> Self {
let entry_point_bits = (entry_point as u32) & Self::ENTRY_POINT_MASK_BITS;
Self::from_bits(entry_point_bits).unwrap()
}
pub fn entry_point(&self) -> PostProcessEntryPoint {
let entry_point_bits = self.bits & Self::ENTRY_POINT_MASK_BITS;
num_traits::FromPrimitive::from_u32(entry_point_bits).unwrap()
}
pub fn from_denoise_level(level: u32) -> Self {
let denoise_level_bits =
(level & Self::DENOISE_LEVEL_MASK_BITS) << Self::DENOISE_LEVEL_SHIFT_BITS;
Self::from_bits(denoise_level_bits).unwrap()
}
pub fn denoise_level(&self) -> u32 {
(self.bits >> Self::DENOISE_LEVEL_SHIFT_BITS) & Self::DENOISE_LEVEL_MASK_BITS
}
}
impl SpecializedComputePipeline for PostProcessPipeline {
type Key = PostProcessPipelineKey;
fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
let mut entry_point = serde_variant::to_variant_name(&key.entry_point())
.unwrap()
.into();
let mut shader_defs: Vec<_> = vec![];
if key.contains(PostProcessPipelineKey::FIREFLY_FILTERING_BITS) {
shader_defs.push("FIREFLY_FILTERING".into());
}
let (layout, shader) = match key.entry_point() {
PostProcessEntryPoint::Demodulation | PostProcessEntryPoint::Denoise => {
let layout = vec![
self.view_layout.clone(),
self.deferred_layout.clone(),
self.sampler_layout.clone(),
self.denoise_internal_layout.clone(),
self.denoise_render_layout.clone(),
];
shader_defs.push(format!("DENOISE_LEVEL_{}", key.denoise_level()));
let shader = DENOISE_SHADER_HANDLE.typed();
(layout, shader)
}
PostProcessEntryPoint::ToneMapping => {
let layout = vec![
self.view_layout.clone(),
self.deferred_layout.clone(),
self.sampler_layout.clone(),
self.tone_mapping_layout.clone(),
self.output_layout.clone(),
];
let shader = TONE_MAPPING_SHADER_HANDLE.typed();
(layout, shader)
}
PostProcessEntryPoint::TaaJasmine => {
let layout = vec![
self.view_layout.clone(),
self.deferred_layout.clone(),
self.sampler_layout.clone(),
self.taa_layout.clone(),
self.output_layout.clone(),
];
let shader = TAA_SHADER_HANDLE.typed();
(layout, shader)
}
PostProcessEntryPoint::SmaaTu4x => {
let layout = vec![
self.view_layout.clone(),
self.deferred_layout.clone(),
self.sampler_layout.clone(),
self.smaa_layout.clone(),
self.output_layout.clone(),
];
let shader = SMAA_SHADER_HANDLE.typed();
(layout, shader)
}
PostProcessEntryPoint::SmaaTu4xExtrapolate => {
let layout = vec![
self.view_layout.clone(),
self.deferred_layout.clone(),
self.sampler_layout.clone(),
self.smaa_layout.clone(),
self.output_layout.clone(),
];
let shader = SMAA_SHADER_HANDLE.typed();
(layout, shader)
}
PostProcessEntryPoint::Upscale => {
let layout = vec![
self.sampler_layout.clone(),
self.upscale_layout.clone(),
self.output_layout.clone(),
];
entry_point = "main".into();
let shader = FSR1_EASU_SHADER_HANDLE.typed();
(layout, shader)
}
PostProcessEntryPoint::UpscaleSharpen => {
let layout = vec![
self.sampler_layout.clone(),
self.upscale_layout.clone(),
self.output_layout.clone(),
];
entry_point = "main".into();
let shader = FSR1_RCAS_SHADER_HANDLE.typed();
(layout, shader)
}
};
ComputePipelineDescriptor {
label: None,
layout: Some(layout),
shader,
shader_defs,
entry_point,
}
}
}
#[derive(Debug, Default, Clone, Copy, Component, ShaderType)]
pub struct FsrConstantsUniform {
pub input_viewport_in_pixels: Vec2,
pub input_size_in_pixels: Vec2,
pub output_size_in_pixels: Vec2,
pub sharpness: f32,
pub hdr: u32,
}
impl ExtractComponent for FsrConstantsUniform {
type Query = (&'static Camera, &'static HikariSettings);
type Filter = ();
fn extract_component((camera, settings): QueryItem<Self::Query>) -> Self {
let size = camera.physical_target_size().unwrap_or_default();
let scale = settings.upscale.ratio().recip();
let scaled_size = (scale * size.as_vec2()).ceil();
Self {
input_viewport_in_pixels: scaled_size,
input_size_in_pixels: scaled_size,
output_size_in_pixels: size.as_vec2(),
sharpness: settings.upscale.sharpness(),
hdr: 0,
}
}
}
#[derive(Component)]
pub struct PostProcessTextures {
pub head: usize,
pub nearest_sampler: Sampler,
pub linear_sampler: Sampler,
pub fallback: TextureView,
pub denoise_internal: [TextureView; 4],
pub denoise_internal_variance: TextureView,
pub denoise_render: [TextureView; 3],
pub tone_mapping_output: [TextureView; 2],
pub taa_output: [TextureView; 2],
pub upscale_output: [TextureView; 2],
}
fn prepare_post_process_textures(
mut commands: Commands,
render_device: Res<RenderDevice>,
mut texture_cache: ResMut<TextureCache>,
cameras: Query<(Entity, &ExtractedCamera, &FrameCounter, &HikariSettings)>,
) {
let texture_usage = TextureUsages::TEXTURE_BINDING | TextureUsages::STORAGE_BINDING;
let fallback = texture_cache
.get(
&render_device,
TextureDescriptor {
label: None,
size: Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: HDR_TEXTURE_FORMAT,
usage: texture_usage,
},
)
.default_view;
for (entity, camera, counter, settings) in &cameras {
if let Some(size) = camera.physical_target_size {
let mut create_texture = |texture_format, scale: f32| {
let extent = Extent3d {
width: (size.x as f32 * scale).ceil() as u32,
height: (size.y as f32 * scale).ceil() as u32,
depth_or_array_layers: 1,
};
texture_cache
.get(
&render_device,
TextureDescriptor {
label: None,
size: extent,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: texture_format,
usage: texture_usage,
},
)
.default_view
};
macro_rules! create_texture_array {
[$texture_format:ident, $scale:ident; $count:literal] => {
[(); $count].map(|_| create_texture($texture_format, $scale))
};
[$texture_format:ident, $scale:literal; $count:literal] => {
[(); $count].map(|_| create_texture($texture_format, $scale))
};
[$texture:ident; $count:literal] => {
[(); $count].map(|_| $texture.clone())
};
}
let nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
mag_filter: FilterMode::Nearest,
min_filter: FilterMode::Nearest,
mipmap_filter: FilterMode::Nearest,
..Default::default()
});
let linear_sampler = render_device.create_sampler(&SamplerDescriptor {
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
mipmap_filter: FilterMode::Linear,
..Default::default()
});
let mut scale = settings.upscale.ratio().recip();
let denoise_internal_variance = create_texture(VARIANCE_TEXTURE_FORMAT, scale);
let denoise_internal = create_texture_array![HDR_TEXTURE_FORMAT, scale; 4];
let denoise_render = create_texture_array![HDR_TEXTURE_FORMAT, scale; 3];
let tone_mapping_output = create_texture_array![HDR_TEXTURE_FORMAT, scale; 2];
let upscale_output = match settings.upscale {
Upscale::SmaaTu4x { .. } => {
scale *= 2.0;
create_texture_array![HDR_TEXTURE_FORMAT, scale; 2]
}
Upscale::Fsr1 { .. } => create_texture_array![HDR_TEXTURE_FORMAT, 1.0; 2],
Upscale::None => create_texture_array![fallback; 2],
};
let taa_output = match settings.taa {
Taa::Jasmine => {
create_texture_array![HDR_TEXTURE_FORMAT, scale; 2]
}
Taa::None => create_texture_array![fallback; 2],
};
commands.entity(entity).insert(PostProcessTextures {
head: counter.0 % 2,
nearest_sampler,
linear_sampler,
fallback: fallback.clone(),
denoise_internal,
denoise_internal_variance,
denoise_render,
tone_mapping_output,
taa_output,
upscale_output,
});
}
}
}
#[allow(dead_code)]
#[derive(Resource)]
pub struct CachedPostProcessPipelines {
demodulation: CachedComputePipelineId,
denoise_direct: [CachedComputePipelineId; 4],
denoise: [CachedComputePipelineId; 4],
tone_mapping: CachedComputePipelineId,
taa_jasmine: CachedComputePipelineId,
smaa_tu4x: CachedComputePipelineId,
smaa_tu4x_extrapolate: CachedComputePipelineId,
upscale: CachedComputePipelineId,
upscale_sharpen: CachedComputePipelineId,
}
fn queue_post_process_pipelines(
mut commands: Commands,
pipeline: Res<PostProcessPipeline>,
mut pipelines: ResMut<SpecializedComputePipelines<PostProcessPipeline>>,
mut pipeline_cache: ResMut<PipelineCache>,
) {
let demodulation = {
let key = PostProcessPipelineKey::from_entry_point(PostProcessEntryPoint::Demodulation);
pipelines.specialize(&mut pipeline_cache, &pipeline, key)
};
let denoise_direct = [0, 1, 2, 3].map(|level| {
let mut key = PostProcessPipelineKey::from_entry_point(PostProcessEntryPoint::Denoise);
key |= PostProcessPipelineKey::from_denoise_level(level);
pipelines.specialize(&mut pipeline_cache, &pipeline, key)
});
let denoise = [0, 1, 2, 3].map(|level| {
let mut key = PostProcessPipelineKey::from_entry_point(PostProcessEntryPoint::Denoise);
key |= PostProcessPipelineKey::from_denoise_level(level);
key |= PostProcessPipelineKey::FIREFLY_FILTERING_BITS;
pipelines.specialize(&mut pipeline_cache, &pipeline, key)
});
let tone_mapping = {
let key = PostProcessPipelineKey::from_entry_point(PostProcessEntryPoint::ToneMapping);
pipelines.specialize(&mut pipeline_cache, &pipeline, key)
};
let taa_jasmine = {
let key = PostProcessPipelineKey::from_entry_point(PostProcessEntryPoint::TaaJasmine);
pipelines.specialize(&mut pipeline_cache, &pipeline, key)
};
let smaa_tu4x = {
let key = PostProcessPipelineKey::from_entry_point(PostProcessEntryPoint::SmaaTu4x);
pipelines.specialize(&mut pipeline_cache, &pipeline, key)
};
let smaa_tu4x_extrapolate = {
let key =
PostProcessPipelineKey::from_entry_point(PostProcessEntryPoint::SmaaTu4xExtrapolate);
pipelines.specialize(&mut pipeline_cache, &pipeline, key)
};
let upscale = {
let key = PostProcessPipelineKey::from_entry_point(PostProcessEntryPoint::Upscale);
pipelines.specialize(&mut pipeline_cache, &pipeline, key)
};
let upscale_sharpen = {
let key = PostProcessPipelineKey::from_entry_point(PostProcessEntryPoint::UpscaleSharpen);
pipelines.specialize(&mut pipeline_cache, &pipeline, key)
};
commands.insert_resource(CachedPostProcessPipelines {
demodulation,
denoise_direct,
denoise,
tone_mapping,
taa_jasmine,
smaa_tu4x,
smaa_tu4x_extrapolate,
upscale,
upscale_sharpen,
})
}
#[derive(Component, Clone)]
pub struct PostProcessBindGroup {
pub sampler: BindGroup,
pub denoise_internal: BindGroup,
pub denoise_render: Vec<BindGroup>,
pub tone_mapping: BindGroup,
pub tone_mapping_output: BindGroup,
pub smaa: BindGroup,
pub smaa_output: BindGroup,
pub taa: BindGroup,
pub taa_output: BindGroup,
pub upscale: BindGroup,
pub upscale_output: BindGroup,
pub upscale_sharpen: BindGroup,
pub upscale_sharpen_output: BindGroup,
}
#[allow(clippy::type_complexity)]
fn queue_post_process_bind_groups(
mut commands: Commands,
render_device: Res<RenderDevice>,
pipeline: Res<PostProcessPipeline>,
fsr_constants_uniforms: Res<ComponentUniforms<FsrConstantsUniform>>,
query: Query<
(
Entity,
&LightTextures,
&PostProcessTextures,
&HikariSettings,
),
With<ExtractedCamera>,
>,
) {
let fsr_constants_binding = match fsr_constants_uniforms.binding() {
Some(binding) => binding,
None => return,
};
for (entity, light, post_process, settings) in &query {
let current = post_process.head;
let previous = 1 - current;
let sampler = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.sampler_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::Sampler(&post_process.nearest_sampler),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&post_process.linear_sampler),
},
],
});
let denoise_internal = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.denoise_internal_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&post_process.denoise_internal[0]),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(&post_process.denoise_internal[1]),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(&post_process.denoise_internal[2]),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::TextureView(&post_process.denoise_internal[3]),
},
BindGroupEntry {
binding: 4,
resource: BindingResource::TextureView(&post_process.denoise_internal_variance),
},
],
});
let mut denoise_render = [0, 1, 2]
.map(|id| {
render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.denoise_render_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&light.albedo),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(&light.variance[id]),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(&light.render[id]),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::TextureView(
&post_process.denoise_render[id],
),
},
],
})
})
.to_vec();
let (direct_render, emissive_render, mut indirect_render) = match settings.denoise {
false => (&light.render[0], &light.render[1], &light.render[2]),
true => (
&post_process.denoise_render[0],
&post_process.denoise_render[1],
&post_process.denoise_render[2],
),
};
if settings.indirect_bounces == 0 {
denoise_render.pop();
indirect_render = &post_process.fallback;
}
let tone_mapping = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.tone_mapping_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(direct_render),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(emissive_render),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(indirect_render),
},
],
});
let tone_mapping_output = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.output_layout,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&post_process.tone_mapping_output[current]),
}],
});
let smaa = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.smaa_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(
&post_process.tone_mapping_output[previous],
),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(
&post_process.tone_mapping_output[current],
),
},
],
});
let smaa_output = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.output_layout,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&post_process.upscale_output[0]),
}],
});
let taa_input_texture = match settings.upscale {
Upscale::SmaaTu4x { .. } => &post_process.upscale_output[0],
_ => &post_process.tone_mapping_output[current],
};
let taa = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.taa_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&post_process.taa_output[previous]),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(taa_input_texture),
},
],
});
let taa_output = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.output_layout,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&post_process.taa_output[current]),
}],
});
let upscale_input_texture = match settings.taa {
Taa::Jasmine => &post_process.taa_output[current],
Taa::None => &post_process.tone_mapping_output[current],
};
let upscale = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.upscale_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: fsr_constants_binding.clone(),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(upscale_input_texture),
},
],
});
let upscale_output = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.output_layout,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&post_process.upscale_output[0]),
}],
});
let upscale_sharpen = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.upscale_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: fsr_constants_binding.clone(),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(&post_process.upscale_output[0]),
},
],
});
let upscale_sharpen_output = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.output_layout,
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&post_process.upscale_output[1]),
}],
});
commands.entity(entity).insert(PostProcessBindGroup {
sampler,
denoise_internal,
denoise_render,
tone_mapping,
tone_mapping_output,
smaa,
smaa_output,
taa,
taa_output,
upscale,
upscale_output,
upscale_sharpen,
upscale_sharpen_output,
});
}
}
#[allow(clippy::type_complexity)]
pub struct PostProcessNode {
query: QueryState<(
&'static ExtractedCamera,
&'static DynamicUniformIndex<FrameUniform>,
&'static ViewUniformOffset,
&'static PreviousViewUniformOffset,
&'static ViewLightsUniformOffset,
&'static DeferredBindGroup,
&'static PostProcessBindGroup,
&'static DynamicUniformIndex<FsrConstantsUniform>,
&'static HikariSettings,
)>,
}
impl PostProcessNode {
pub const IN_VIEW: &'static str = "view";
pub fn new(world: &mut World) -> Self {
Self {
query: world.query_filtered(),
}
}
}
impl Node for PostProcessNode {
fn input(&self) -> Vec<SlotInfo> {
vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
}
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let entity = graph.get_input_entity(Self::IN_VIEW)?;
let (
camera,
frame_uniform,
view_uniform,
previous_view_uniform,
view_lights,
deferred_bind_group,
post_process_bind_group,
fsr_constants_uniform,
settings,
) = match self.query.get_manual(world, entity) {
Ok(query) => query,
Err(_) => return Ok(()),
};
let view_bind_group = match world.get_resource::<PrepassBindGroup>() {
Some(bind_group) => &bind_group.view,
None => return Ok(()),
};
let pipelines = world.resource::<CachedPostProcessPipelines>();
let pipeline_cache = world.resource::<PipelineCache>();
let size = camera.physical_target_size.unwrap();
let scale = settings.upscale.ratio().recip();
let mut scaled_size = (scale * size.as_vec2()).ceil().as_uvec2();
let mut pass = render_context
.command_encoder
.begin_compute_pass(&ComputePassDescriptor::default());
pass.set_bind_group(
0,
view_bind_group,
&[
frame_uniform.index(),
view_uniform.offset,
previous_view_uniform.offset,
view_lights.offset,
],
);
pass.set_bind_group(1, &deferred_bind_group.0, &[]);
pass.set_bind_group(2, &post_process_bind_group.sampler, &[]);
if settings.denoise {
pass.set_bind_group(3, &post_process_bind_group.denoise_internal, &[]);
let denoise_pipelines = [
pipelines.denoise_direct,
pipelines.denoise,
pipelines.denoise,
];
for (render_bind_group, denoise) in post_process_bind_group
.denoise_render
.iter()
.zip(denoise_pipelines.iter())
{
pass.set_bind_group(4, render_bind_group, &[]);
if let Some(pipeline) = pipeline_cache.get_compute_pipeline(pipelines.demodulation)
{
pass.set_pipeline(pipeline);
let count = (scaled_size + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
pass.dispatch_workgroups(count.x, count.y, 1);
}
for pipeline in denoise
.iter()
.filter_map(|pipeline| pipeline_cache.get_compute_pipeline(*pipeline))
{
pass.set_pipeline(pipeline);
let count = (scaled_size + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
pass.dispatch_workgroups(count.x, count.y, 1);
}
}
}
pass.set_bind_group(3, &post_process_bind_group.tone_mapping, &[]);
pass.set_bind_group(4, &post_process_bind_group.tone_mapping_output, &[]);
if let Some(pipeline) = pipeline_cache.get_compute_pipeline(pipelines.tone_mapping) {
pass.set_pipeline(pipeline);
let count = (scaled_size + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
pass.dispatch_workgroups(count.x, count.y, 1);
}
if matches!(settings.upscale, Upscale::SmaaTu4x { .. }) {
pass.set_bind_group(3, &post_process_bind_group.smaa, &[]);
pass.set_bind_group(4, &post_process_bind_group.smaa_output, &[]);
if let Some(pipeline) = pipeline_cache.get_compute_pipeline(pipelines.smaa_tu4x) {
pass.set_pipeline(pipeline);
let count = (scaled_size + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
pass.dispatch_workgroups(count.x, count.y, 1);
}
if let Some(pipeline) =
pipeline_cache.get_compute_pipeline(pipelines.smaa_tu4x_extrapolate)
{
pass.set_pipeline(pipeline);
let count = (scaled_size + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
pass.dispatch_workgroups(count.x, count.y, 1);
}
scaled_size *= 2;
}
if matches!(settings.taa, Taa::Jasmine) {
pass.set_bind_group(3, &post_process_bind_group.taa, &[]);
pass.set_bind_group(4, &post_process_bind_group.taa_output, &[]);
if let Some(pipeline) = pipeline_cache.get_compute_pipeline(pipelines.taa_jasmine) {
pass.set_pipeline(pipeline);
let count = (scaled_size + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
pass.dispatch_workgroups(count.x, count.y, 1);
}
}
if matches!(settings.upscale, Upscale::Fsr1 { .. }) {
pass.set_bind_group(0, &post_process_bind_group.sampler, &[]);
pass.set_bind_group(
1,
&post_process_bind_group.upscale,
&[fsr_constants_uniform.index()],
);
pass.set_bind_group(2, &post_process_bind_group.upscale_output, &[]);
if let Some(pipeline) = pipeline_cache.get_compute_pipeline(pipelines.upscale) {
pass.set_pipeline(pipeline);
let count = (size * 2 + 15) / 16;
pass.dispatch_workgroups(count.x, count.y, 1);
}
pass.set_bind_group(
1,
&post_process_bind_group.upscale_sharpen,
&[fsr_constants_uniform.index()],
);
pass.set_bind_group(2, &post_process_bind_group.upscale_sharpen_output, &[]);
if let Some(pipeline) = pipeline_cache.get_compute_pipeline(pipelines.upscale_sharpen) {
pass.set_pipeline(pipeline);
let count = (size * 2 + 15) / 16;
pass.dispatch_workgroups(count.x, count.y, 1);
}
}
Ok(())
}
}