use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Handle};
use bevy_ecs::{component::*, prelude::*};
use bevy_math::UVec2;
use bevy_reflect::Reflect;
use bevy_render::{
camera::{Camera, ExtractedCamera},
extract_component::{ExtractComponent, ExtractComponentPlugin},
render_graph::{RenderGraphApp, ViewNodeRunner},
render_resource::{
BufferUsages, BufferVec, DynamicUniformBuffer, Shader, ShaderType, TextureUsages,
},
renderer::{RenderDevice, RenderQueue},
view::Msaa,
Render, RenderApp, RenderSet,
};
use bevy_utils::{
tracing::{trace, warn},
HashSet, Instant,
};
use bevy_window::PrimaryWindow;
use resolve::{
node::{OitResolveNode, OitResolvePass},
OitResolvePlugin,
};
use crate::core_3d::{
graph::{Core3d, Node3d},
Camera3d,
};
pub mod resolve;
pub const OIT_DRAW_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(4042527984320512);
#[derive(Clone, Copy, ExtractComponent, Reflect, ShaderType)]
pub struct OrderIndependentTransparencySettings {
pub layer_count: i32,
pub alpha_threshold: f32,
}
impl Default for OrderIndependentTransparencySettings {
fn default() -> Self {
Self {
layer_count: 8,
alpha_threshold: 0.0,
}
}
}
impl Component for OrderIndependentTransparencySettings {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_add(|world, entity, _| {
if let Some(value) = world.get::<OrderIndependentTransparencySettings>(entity) {
if value.layer_count > 32 {
warn!("OrderIndependentTransparencySettings layer_count set to {} might be too high.", value.layer_count);
}
}
});
}
}
pub struct OrderIndependentTransparencyPlugin;
impl Plugin for OrderIndependentTransparencyPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
OIT_DRAW_SHADER_HANDLE,
"oit_draw.wgsl",
Shader::from_wgsl
);
app.add_plugins((
ExtractComponentPlugin::<OrderIndependentTransparencySettings>::default(),
OitResolvePlugin,
))
.add_systems(Update, check_msaa)
.add_systems(Last, configure_depth_texture_usages)
.register_type::<OrderIndependentTransparencySettings>();
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.add_systems(
Render,
prepare_oit_buffers.in_set(RenderSet::PrepareResources),
);
render_app
.add_render_graph_node::<ViewNodeRunner<OitResolveNode>>(Core3d, OitResolvePass)
.add_render_graph_edges(
Core3d,
(
Node3d::MainTransparentPass,
OitResolvePass,
Node3d::EndMainPass,
),
);
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<OitBuffers>();
}
}
fn configure_depth_texture_usages(
p: Query<Entity, With<PrimaryWindow>>,
cameras: Query<(&Camera, Has<OrderIndependentTransparencySettings>)>,
mut new_cameras: Query<(&mut Camera3d, &Camera), Added<Camera3d>>,
) {
if new_cameras.is_empty() {
return;
}
let primary_window = p.get_single().ok();
let mut render_target_has_oit = HashSet::new();
for (camera, has_oit) in &cameras {
if has_oit {
render_target_has_oit.insert(camera.target.normalize(primary_window));
}
}
for (mut camera_3d, camera) in &mut new_cameras {
if render_target_has_oit.contains(&camera.target.normalize(primary_window)) {
let mut usages = TextureUsages::from(camera_3d.depth_texture_usages);
usages |= TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING;
camera_3d.depth_texture_usages = usages.into();
}
}
}
fn check_msaa(cameras: Query<&Msaa, With<OrderIndependentTransparencySettings>>) {
for msaa in &cameras {
if msaa.samples() > 1 {
panic!("MSAA is not supported when using OrderIndependentTransparency");
}
}
}
#[derive(Resource)]
pub struct OitBuffers {
pub layers: BufferVec<UVec2>,
pub layer_ids: BufferVec<i32>,
pub settings: DynamicUniformBuffer<OrderIndependentTransparencySettings>,
}
impl FromWorld for OitBuffers {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let render_queue = world.resource::<RenderQueue>();
let mut layers = BufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
layers.set_label(Some("oit_layers"));
layers.reserve(1, render_device);
layers.write_buffer(render_device, render_queue);
let mut layer_ids = BufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
layer_ids.set_label(Some("oit_layer_ids"));
layer_ids.reserve(1, render_device);
layer_ids.write_buffer(render_device, render_queue);
let mut settings = DynamicUniformBuffer::default();
settings.set_label(Some("oit_settings"));
Self {
layers,
layer_ids,
settings,
}
}
}
#[derive(Component)]
pub struct OrderIndependentTransparencySettingsOffset {
pub offset: u32,
}
#[allow(clippy::type_complexity)]
pub fn prepare_oit_buffers(
mut commands: Commands,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
cameras: Query<
(&ExtractedCamera, &OrderIndependentTransparencySettings),
(
Changed<ExtractedCamera>,
Changed<OrderIndependentTransparencySettings>,
),
>,
camera_oit_uniforms: Query<(Entity, &OrderIndependentTransparencySettings)>,
mut buffers: ResMut<OitBuffers>,
) {
let mut max_layer_ids_size = usize::MIN;
let mut max_layers_size = usize::MIN;
for (camera, settings) in &cameras {
let Some(size) = camera.physical_target_size else {
continue;
};
let layer_count = settings.layer_count as usize;
let size = (size.x * size.y) as usize;
max_layer_ids_size = max_layer_ids_size.max(size);
max_layers_size = max_layers_size.max(size * layer_count);
}
if buffers.layers.capacity() < max_layers_size {
let start = Instant::now();
buffers.layers.reserve(max_layers_size, &render_device);
let remaining = max_layers_size - buffers.layers.capacity();
for _ in 0..remaining {
buffers.layers.push(UVec2::ZERO);
}
buffers.layers.write_buffer(&render_device, &render_queue);
trace!(
"OIT layers buffer updated in {:.01}ms with total size {} MiB",
start.elapsed().as_millis(),
buffers.layers.capacity() * size_of::<UVec2>() / 1024 / 1024,
);
}
if buffers.layer_ids.capacity() < max_layer_ids_size {
let start = Instant::now();
buffers
.layer_ids
.reserve(max_layer_ids_size, &render_device);
let remaining = max_layer_ids_size - buffers.layer_ids.capacity();
for _ in 0..remaining {
buffers.layer_ids.push(0);
}
buffers
.layer_ids
.write_buffer(&render_device, &render_queue);
trace!(
"OIT layer ids buffer updated in {:.01}ms with total size {} MiB",
start.elapsed().as_millis(),
buffers.layer_ids.capacity() * size_of::<UVec2>() / 1024 / 1024,
);
}
if let Some(mut writer) = buffers.settings.get_writer(
camera_oit_uniforms.iter().len(),
&render_device,
&render_queue,
) {
for (entity, settings) in &camera_oit_uniforms {
let offset = writer.write(settings);
commands
.entity(entity)
.insert(OrderIndependentTransparencySettingsOffset { offset });
}
}
}