bevy_core_pipeline/oit/resolve/
mod.rs

1use 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
26/// Contains the render node used to run the resolve pass.
27pub mod node;
28
29/// Minimum required value of `wgpu::Limits::max_storage_buffers_per_shader_stage`.
30pub const OIT_REQUIRED_STORAGE_BUFFERS: u32 = 2;
31
32/// Plugin needed to resolve the Order Independent Transparency (OIT) buffer to the screen.
33pub 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/// Bind group for the OIT resolve pass.
93#[derive(Resource, Deref)]
94pub struct OitResolveBindGroup(pub BindGroup);
95
96/// Bind group layouts used for the OIT resolve pass.
97#[derive(Resource)]
98pub struct OitResolvePipeline {
99    /// View bind group layout.
100    pub view_bind_group_layout: BindGroupLayout,
101    /// Depth bind group layout.
102    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                    // layers
116                    storage_buffer_sized(false, None),
117                    // layer ids
118                    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/// This key is used to cache the pipeline id and to specialize the render pipeline descriptor.
138#[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    // Store the key with the id to make the clean up logic easier.
159    // This also means it will always replace the entry if the key changes so nothing to clean up.
160    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    // Clear cache for views that don't exist anymore.
190    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}