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