bevy_core_pipeline/oit/resolve/
mod.rs

1use 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
29/// Shader handle for the shader that sorts the OIT layers, blends the colors based on depth and renders them to the screen.
30pub const OIT_RESOLVE_SHADER_HANDLE: Handle<Shader> =
31    weak_handle!("562d2917-eb06-444d-9ade-41de76b0f5ae");
32
33/// Contains the render node used to run the resolve pass.
34pub mod node;
35
36/// Minimum required value of `wgpu::Limits::max_storage_buffers_per_shader_stage`.
37pub const OIT_REQUIRED_STORAGE_BUFFERS: u32 = 2;
38
39/// Plugin needed to resolve the Order Independent Transparency (OIT) buffer to the screen.
40pub 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/// Bind group for the OIT resolve pass.
105#[derive(Resource, Deref)]
106pub struct OitResolveBindGroup(pub BindGroup);
107
108/// Bind group layouts used for the OIT resolve pass.
109#[derive(Resource)]
110pub struct OitResolvePipeline {
111    /// View bind group layout.
112    pub view_bind_group_layout: BindGroupLayout,
113    /// Depth bind group layout.
114    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                    // layers
128                    storage_buffer_sized(false, None),
129                    // layer ids
130                    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/// This key is used to cache the pipeline id and to specialize the render pipeline descriptor.
150#[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    // 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) 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    // Clear cache for views that don't exist anymore.
195    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}