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