bevy_core_pipeline/oit/resolve/
mod.rs1use super::OitBuffers;
2use crate::{oit::OrderIndependentTransparencySettings, FullscreenShader};
3use bevy_app::Plugin;
4use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer};
5use bevy_derive::Deref;
6use bevy_ecs::{
7 entity::{EntityHashMap, EntityHashSet},
8 prelude::*,
9};
10use bevy_image::BevyDefault as _;
11use bevy_render::{
12 render_resource::{
13 binding_types::{storage_buffer_sized, texture_depth_2d, uniform_buffer},
14 BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, BlendComponent,
15 BlendState, CachedRenderPipelineId, ColorTargetState, ColorWrites, DownlevelFlags,
16 FragmentState, PipelineCache, RenderPipelineDescriptor, ShaderStages, TextureFormat,
17 },
18 renderer::{RenderAdapter, RenderDevice},
19 view::{ExtractedView, ViewTarget, ViewUniform, ViewUniforms},
20 Render, RenderApp, RenderSystems,
21};
22use bevy_shader::ShaderDefVal;
23use bevy_utils::default;
24use tracing::warn;
25
26pub mod node;
28
29pub const OIT_REQUIRED_STORAGE_BUFFERS: u32 = 2;
31
32pub struct OitResolvePlugin;
34impl Plugin for OitResolvePlugin {
35 fn build(&self, app: &mut bevy_app::App) {
36 embedded_asset!(app, "oit_resolve.wgsl");
37 }
38
39 fn finish(&self, app: &mut bevy_app::App) {
40 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
41 return;
42 };
43
44 if !is_oit_supported(
45 render_app.world().resource::<RenderAdapter>(),
46 render_app.world().resource::<RenderDevice>(),
47 true,
48 ) {
49 return;
50 }
51
52 render_app
53 .add_systems(
54 Render,
55 (
56 queue_oit_resolve_pipeline.in_set(RenderSystems::Queue),
57 prepare_oit_resolve_bind_group.in_set(RenderSystems::PrepareBindGroups),
58 ),
59 )
60 .init_resource::<OitResolvePipeline>();
61 }
62}
63
64pub fn is_oit_supported(adapter: &RenderAdapter, device: &RenderDevice, warn: bool) -> bool {
65 if !adapter
66 .get_downlevel_capabilities()
67 .flags
68 .contains(DownlevelFlags::FRAGMENT_WRITABLE_STORAGE)
69 {
70 if warn {
71 warn!("OrderIndependentTransparencyPlugin not loaded. GPU lacks support: DownlevelFlags::FRAGMENT_WRITABLE_STORAGE.");
72 }
73 return false;
74 }
75
76 let max_storage_buffers_per_shader_stage = device.limits().max_storage_buffers_per_shader_stage;
77
78 if max_storage_buffers_per_shader_stage < OIT_REQUIRED_STORAGE_BUFFERS {
79 if warn {
80 warn!(
81 max_storage_buffers_per_shader_stage,
82 OIT_REQUIRED_STORAGE_BUFFERS,
83 "OrderIndependentTransparencyPlugin not loaded. RenderDevice lacks support: max_storage_buffers_per_shader_stage < OIT_REQUIRED_STORAGE_BUFFERS."
84 );
85 }
86 return false;
87 }
88
89 true
90}
91
92#[derive(Resource, Deref)]
94pub struct OitResolveBindGroup(pub BindGroup);
95
96#[derive(Resource)]
98pub struct OitResolvePipeline {
99 pub view_bind_group_layout: BindGroupLayout,
101 pub oit_depth_bind_group_layout: BindGroupLayout,
103}
104
105impl FromWorld for OitResolvePipeline {
106 fn from_world(world: &mut World) -> Self {
107 let render_device = world.resource::<RenderDevice>();
108
109 let view_bind_group_layout = render_device.create_bind_group_layout(
110 "oit_resolve_bind_group_layout",
111 &BindGroupLayoutEntries::sequential(
112 ShaderStages::FRAGMENT,
113 (
114 uniform_buffer::<ViewUniform>(true),
115 storage_buffer_sized(false, None),
117 storage_buffer_sized(false, None),
119 ),
120 ),
121 );
122
123 let oit_depth_bind_group_layout = render_device.create_bind_group_layout(
124 "oit_depth_bind_group_layout",
125 &BindGroupLayoutEntries::single(ShaderStages::FRAGMENT, texture_depth_2d()),
126 );
127 OitResolvePipeline {
128 view_bind_group_layout,
129 oit_depth_bind_group_layout,
130 }
131 }
132}
133
134#[derive(Component, Deref, Clone, Copy)]
135pub struct OitResolvePipelineId(pub CachedRenderPipelineId);
136
137#[derive(Debug, PartialEq, Eq, Clone, Copy)]
139pub struct OitResolvePipelineKey {
140 hdr: bool,
141 layer_count: i32,
142}
143
144pub fn queue_oit_resolve_pipeline(
145 mut commands: Commands,
146 pipeline_cache: Res<PipelineCache>,
147 resolve_pipeline: Res<OitResolvePipeline>,
148 views: Query<
149 (
150 Entity,
151 &ExtractedView,
152 &OrderIndependentTransparencySettings,
153 ),
154 With<OrderIndependentTransparencySettings>,
155 >,
156 fullscreen_shader: Res<FullscreenShader>,
157 asset_server: Res<AssetServer>,
158 mut cached_pipeline_id: Local<EntityHashMap<(OitResolvePipelineKey, CachedRenderPipelineId)>>,
161) {
162 let mut current_view_entities = EntityHashSet::default();
163 for (e, view, oit_settings) in &views {
164 current_view_entities.insert(e);
165 let key = OitResolvePipelineKey {
166 hdr: view.hdr,
167 layer_count: oit_settings.layer_count,
168 };
169
170 if let Some((cached_key, id)) = cached_pipeline_id.get(&e)
171 && *cached_key == key
172 {
173 commands.entity(e).insert(OitResolvePipelineId(*id));
174 continue;
175 }
176
177 let desc = specialize_oit_resolve_pipeline(
178 key,
179 &resolve_pipeline,
180 &fullscreen_shader,
181 &asset_server,
182 );
183
184 let pipeline_id = pipeline_cache.queue_render_pipeline(desc);
185 commands.entity(e).insert(OitResolvePipelineId(pipeline_id));
186 cached_pipeline_id.insert(e, (key, pipeline_id));
187 }
188
189 for e in cached_pipeline_id.keys().copied().collect::<Vec<_>>() {
191 if !current_view_entities.contains(&e) {
192 cached_pipeline_id.remove(&e);
193 }
194 }
195}
196
197fn specialize_oit_resolve_pipeline(
198 key: OitResolvePipelineKey,
199 resolve_pipeline: &OitResolvePipeline,
200 fullscreen_shader: &FullscreenShader,
201 asset_server: &AssetServer,
202) -> RenderPipelineDescriptor {
203 let format = if key.hdr {
204 ViewTarget::TEXTURE_FORMAT_HDR
205 } else {
206 TextureFormat::bevy_default()
207 };
208
209 RenderPipelineDescriptor {
210 label: Some("oit_resolve_pipeline".into()),
211 layout: vec![
212 resolve_pipeline.view_bind_group_layout.clone(),
213 resolve_pipeline.oit_depth_bind_group_layout.clone(),
214 ],
215 fragment: Some(FragmentState {
216 shader: load_embedded_asset!(asset_server, "oit_resolve.wgsl"),
217 shader_defs: vec![ShaderDefVal::UInt(
218 "LAYER_COUNT".into(),
219 key.layer_count as u32,
220 )],
221 targets: vec![Some(ColorTargetState {
222 format,
223 blend: Some(BlendState {
224 color: BlendComponent::OVER,
225 alpha: BlendComponent::OVER,
226 }),
227 write_mask: ColorWrites::ALL,
228 })],
229 ..default()
230 }),
231 vertex: fullscreen_shader.to_vertex_state(),
232 ..default()
233 }
234}
235
236pub fn prepare_oit_resolve_bind_group(
237 mut commands: Commands,
238 resolve_pipeline: Res<OitResolvePipeline>,
239 render_device: Res<RenderDevice>,
240 view_uniforms: Res<ViewUniforms>,
241 buffers: Res<OitBuffers>,
242) {
243 if let (Some(binding), Some(layers_binding), Some(layer_ids_binding)) = (
244 view_uniforms.uniforms.binding(),
245 buffers.layers.binding(),
246 buffers.layer_ids.binding(),
247 ) {
248 let bind_group = render_device.create_bind_group(
249 "oit_resolve_bind_group",
250 &resolve_pipeline.view_bind_group_layout,
251 &BindGroupEntries::sequential((binding.clone(), layers_binding, layer_ids_binding)),
252 );
253 commands.insert_resource(OitResolveBindGroup(bind_group));
254 }
255}