use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, Assets, Handle};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
component::Component,
entity::Entity,
query::{QueryItem, With},
reflect::ReflectComponent,
schedule::IntoSystemConfigs as _,
system::{lifetimeless::Read, Commands, Query, Res, ResMut, Resource},
world::{FromWorld, World},
};
use bevy_image::{BevyDefault, Image};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
camera::Camera,
extract_component::{ExtractComponent, ExtractComponentPlugin},
render_asset::{RenderAssetUsages, RenderAssets},
render_graph::{
NodeRunError, RenderGraphApp as _, RenderGraphContext, ViewNode, ViewNodeRunner,
},
render_resource::{
binding_types::{sampler, texture_2d, uniform_buffer},
BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId,
ColorTargetState, ColorWrites, DynamicUniformBuffer, Extent3d, FilterMode, FragmentState,
Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, Shader,
ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines,
TextureDimension, TextureFormat, TextureSampleType,
},
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::GpuImage,
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
};
use bevy_utils::prelude::default;
use crate::{
core_2d::graph::{Core2d, Node2d},
core_3d::graph::{Core3d, Node3d},
fullscreen_vertex_shader,
};
const POST_PROCESSING_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(14675654334038973533);
const CHROMATIC_ABERRATION_SHADER_HANDLE: Handle<Shader> =
Handle::weak_from_u128(10969893303667163833);
const DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE: Handle<Image> =
Handle::weak_from_u128(2199972955136579180);
const DEFAULT_CHROMATIC_ABERRATION_INTENSITY: f32 = 0.02;
const DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES: u32 = 8;
static DEFAULT_CHROMATIC_ABERRATION_LUT_DATA: [u8; 12] =
[255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255];
pub struct PostProcessingPlugin;
#[derive(Reflect, Component, Clone)]
#[reflect(Component, Default)]
pub struct ChromaticAberration {
pub color_lut: Handle<Image>,
pub intensity: f32,
pub max_samples: u32,
}
#[derive(Resource)]
pub struct PostProcessingPipeline {
bind_group_layout: BindGroupLayout,
source_sampler: Sampler,
chromatic_aberration_lut_sampler: Sampler,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct PostProcessingPipelineKey {
texture_format: TextureFormat,
}
#[derive(Component, Deref, DerefMut)]
pub struct PostProcessingPipelineId(CachedRenderPipelineId);
#[derive(ShaderType)]
pub struct ChromaticAberrationUniform {
intensity: f32,
max_samples: u32,
unused_1: u32,
unused_2: u32,
}
#[derive(Resource, Deref, DerefMut, Default)]
pub struct PostProcessingUniformBuffers {
chromatic_aberration: DynamicUniformBuffer<ChromaticAberrationUniform>,
}
#[derive(Component, Deref, DerefMut)]
pub struct PostProcessingUniformBufferOffsets {
chromatic_aberration: u32,
}
#[derive(Default)]
pub struct PostProcessingNode;
impl Plugin for PostProcessingPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
POST_PROCESSING_SHADER_HANDLE,
"post_process.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
CHROMATIC_ABERRATION_SHADER_HANDLE,
"chromatic_aberration.wgsl",
Shader::from_wgsl
);
let mut assets = app.world_mut().resource_mut::<Assets<_>>();
assets.insert(
DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE.id(),
Image::new(
Extent3d {
width: 3,
height: 1,
depth_or_array_layers: 1,
},
TextureDimension::D2,
DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(),
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::RENDER_WORLD,
),
);
app.register_type::<ChromaticAberration>();
app.add_plugins(ExtractComponentPlugin::<ChromaticAberration>::default());
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.init_resource::<SpecializedRenderPipelines<PostProcessingPipeline>>()
.init_resource::<PostProcessingUniformBuffers>()
.add_systems(
Render,
(
prepare_post_processing_pipelines,
prepare_post_processing_uniforms,
)
.in_set(RenderSet::Prepare),
)
.add_render_graph_node::<ViewNodeRunner<PostProcessingNode>>(
Core3d,
Node3d::PostProcessing,
)
.add_render_graph_edges(
Core3d,
(
Node3d::DepthOfField,
Node3d::PostProcessing,
Node3d::Tonemapping,
),
)
.add_render_graph_node::<ViewNodeRunner<PostProcessingNode>>(
Core2d,
Node2d::PostProcessing,
)
.add_render_graph_edges(
Core2d,
(Node2d::Bloom, Node2d::PostProcessing, Node2d::Tonemapping),
);
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<PostProcessingPipeline>();
}
}
impl Default for ChromaticAberration {
fn default() -> Self {
Self {
color_lut: DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE,
intensity: DEFAULT_CHROMATIC_ABERRATION_INTENSITY,
max_samples: DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES,
}
}
}
impl FromWorld for PostProcessingPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let bind_group_layout = render_device.create_bind_group_layout(
Some("postprocessing bind group layout"),
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
uniform_buffer::<ChromaticAberrationUniform>(true),
),
),
);
let source_sampler = render_device.create_sampler(&SamplerDescriptor {
mipmap_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
mag_filter: FilterMode::Linear,
..default()
});
let chromatic_aberration_lut_sampler = render_device.create_sampler(&SamplerDescriptor {
mipmap_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
mag_filter: FilterMode::Linear,
..default()
});
PostProcessingPipeline {
bind_group_layout,
source_sampler,
chromatic_aberration_lut_sampler,
}
}
}
impl SpecializedRenderPipeline for PostProcessingPipeline {
type Key = PostProcessingPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
RenderPipelineDescriptor {
label: Some("postprocessing".into()),
layout: vec![self.bind_group_layout.clone()],
vertex: fullscreen_vertex_shader::fullscreen_shader_vertex_state(),
fragment: Some(FragmentState {
shader: POST_PROCESSING_SHADER_HANDLE,
shader_defs: vec![],
entry_point: "fragment_main".into(),
targets: vec![Some(ColorTargetState {
format: key.texture_format,
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
primitive: default(),
depth_stencil: None,
multisample: default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
impl ViewNode for PostProcessingNode {
type ViewQuery = (
Read<ViewTarget>,
Read<PostProcessingPipelineId>,
Read<ChromaticAberration>,
Read<PostProcessingUniformBufferOffsets>,
);
fn run<'w>(
&self,
_: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
(view_target, pipeline_id, chromatic_aberration, post_processing_uniform_buffer_offsets): QueryItem<'w, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let pipeline_cache = world.resource::<PipelineCache>();
let post_processing_pipeline = world.resource::<PostProcessingPipeline>();
let post_processing_uniform_buffers = world.resource::<PostProcessingUniformBuffers>();
let gpu_image_assets = world.resource::<RenderAssets<GpuImage>>();
let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else {
return Ok(());
};
let Some(chromatic_aberration_lut) = gpu_image_assets.get(&chromatic_aberration.color_lut)
else {
return Ok(());
};
let Some(chromatic_aberration_uniform_buffer_binding) = post_processing_uniform_buffers
.chromatic_aberration
.binding()
else {
return Ok(());
};
let post_process = view_target.post_process_write();
let pass_descriptor = RenderPassDescriptor {
label: Some("postprocessing pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view: post_process.destination,
resolve_target: None,
ops: Operations::default(),
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
};
let bind_group = render_context.render_device().create_bind_group(
Some("postprocessing bind group"),
&post_processing_pipeline.bind_group_layout,
&BindGroupEntries::sequential((
post_process.source,
&post_processing_pipeline.source_sampler,
&chromatic_aberration_lut.texture_view,
&post_processing_pipeline.chromatic_aberration_lut_sampler,
chromatic_aberration_uniform_buffer_binding,
)),
);
let mut render_pass = render_context
.command_encoder()
.begin_render_pass(&pass_descriptor);
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, &bind_group, &[**post_processing_uniform_buffer_offsets]);
render_pass.draw(0..3, 0..1);
Ok(())
}
}
pub fn prepare_post_processing_pipelines(
mut commands: Commands,
pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<PostProcessingPipeline>>,
post_processing_pipeline: Res<PostProcessingPipeline>,
views: Query<(Entity, &ExtractedView), With<ChromaticAberration>>,
) {
for (entity, view) in views.iter() {
let pipeline_id = pipelines.specialize(
&pipeline_cache,
&post_processing_pipeline,
PostProcessingPipelineKey {
texture_format: if view.hdr {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
},
},
);
commands
.entity(entity)
.insert(PostProcessingPipelineId(pipeline_id));
}
}
pub fn prepare_post_processing_uniforms(
mut commands: Commands,
mut post_processing_uniform_buffers: ResMut<PostProcessingUniformBuffers>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut views: Query<(Entity, &ChromaticAberration)>,
) {
post_processing_uniform_buffers.clear();
for (view_entity, chromatic_aberration) in views.iter_mut() {
let chromatic_aberration_uniform_buffer_offset =
post_processing_uniform_buffers.push(&ChromaticAberrationUniform {
intensity: chromatic_aberration.intensity,
max_samples: chromatic_aberration.max_samples,
unused_1: 0,
unused_2: 0,
});
commands
.entity(view_entity)
.insert(PostProcessingUniformBufferOffsets {
chromatic_aberration: chromatic_aberration_uniform_buffer_offset,
});
}
post_processing_uniform_buffers.write_buffer(&render_device, &render_queue);
}
impl ExtractComponent for ChromaticAberration {
type QueryData = Read<ChromaticAberration>;
type QueryFilter = With<Camera>;
type Out = ChromaticAberration;
fn extract_component(
chromatic_aberration: QueryItem<'_, Self::QueryData>,
) -> Option<Self::Out> {
if chromatic_aberration.intensity > 0.0 {
Some(chromatic_aberration.clone())
} else {
None
}
}
}