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