Skip to main content

bevy_core_pipeline/oit/resolve/
mod.rs

1use 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
31/// Contains the render node used to run the resolve pass.
32pub mod node;
33
34/// Minimum required value of `wgpu::Limits::max_storage_buffers_per_shader_stage`.
35pub const OIT_REQUIRED_STORAGE_BUFFERS: u32 = 3;
36
37/// Plugin needed to resolve the Order Independent Transparency (OIT) buffer to the screen.
38pub 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/// Bind group for the OIT resolve pass.
98#[derive(Resource, Deref)]
99pub struct OitResolveBindGroup(pub BindGroup);
100
101/// Bind group layouts used for the OIT resolve pass.
102#[derive(Resource)]
103pub struct OitResolvePipeline {
104    /// View bind group layout.
105    pub view_bind_group_layout: BindGroupLayoutDescriptor,
106    /// Depth bind group layout.
107    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                    // nodes
119                    storage_buffer_read_only_sized(false, None),
120                    // heads
121                    storage_buffer_sized(false, None),
122                    // atomic_counter
123                    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/// This key is used to cache the pipeline id and to specialize the render pipeline descriptor.
143#[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    // Store the key with the id to make the clean up logic easier.
169    // This also means it will always replace the entry if the key changes so nothing to clean up.
170    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    // Clear cache for views that don't exist anymore.
201    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}