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