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, 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 bevy_utils::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> = Handle::weak_from_u128(7698420424769536);
31
32/// Contains the render node used to run the resolve pass.
33pub mod node;
34
35/// Plugin needed to resolve the Order Independent Transparency (OIT) buffer to the screen.
36pub struct OitResolvePlugin;
37impl Plugin for OitResolvePlugin {
38    fn build(&self, app: &mut bevy_app::App) {
39        load_internal_asset!(
40            app,
41            OIT_RESOLVE_SHADER_HANDLE,
42            "oit_resolve.wgsl",
43            Shader::from_wgsl
44        );
45    }
46
47    fn finish(&self, app: &mut bevy_app::App) {
48        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
49            return;
50        };
51
52        if !render_app
53            .world()
54            .resource::<RenderAdapter>()
55            .get_downlevel_capabilities()
56            .flags
57            .contains(DownlevelFlags::FRAGMENT_WRITABLE_STORAGE)
58        {
59            warn!("OrderIndependentTransparencyPlugin not loaded. GPU lacks support: DownlevelFlags::FRAGMENT_WRITABLE_STORAGE.");
60            return;
61        }
62
63        render_app
64            .add_systems(
65                Render,
66                (
67                    queue_oit_resolve_pipeline.in_set(RenderSet::Queue),
68                    prepare_oit_resolve_bind_group.in_set(RenderSet::PrepareBindGroups),
69                ),
70            )
71            .init_resource::<OitResolvePipeline>();
72    }
73}
74
75/// Bind group for the OIT resolve pass.
76#[derive(Resource, Deref)]
77pub struct OitResolveBindGroup(pub BindGroup);
78
79/// Bind group layouts used for the OIT resolve pass.
80#[derive(Resource)]
81pub struct OitResolvePipeline {
82    /// View bind group layout.
83    pub view_bind_group_layout: BindGroupLayout,
84    /// Depth bind group layout.
85    pub oit_depth_bind_group_layout: BindGroupLayout,
86}
87
88impl FromWorld for OitResolvePipeline {
89    fn from_world(world: &mut World) -> Self {
90        let render_device = world.resource::<RenderDevice>();
91
92        let view_bind_group_layout = render_device.create_bind_group_layout(
93            "oit_resolve_bind_group_layout",
94            &BindGroupLayoutEntries::sequential(
95                ShaderStages::FRAGMENT,
96                (
97                    uniform_buffer::<ViewUniform>(true),
98                    // layers
99                    storage_buffer_sized(false, None),
100                    // layer ids
101                    storage_buffer_sized(false, None),
102                ),
103            ),
104        );
105
106        let oit_depth_bind_group_layout = render_device.create_bind_group_layout(
107            "oit_depth_bind_group_layout",
108            &BindGroupLayoutEntries::single(ShaderStages::FRAGMENT, texture_depth_2d()),
109        );
110        OitResolvePipeline {
111            view_bind_group_layout,
112            oit_depth_bind_group_layout,
113        }
114    }
115}
116
117#[derive(Component, Deref, Clone, Copy)]
118pub struct OitResolvePipelineId(pub CachedRenderPipelineId);
119
120/// This key is used to cache the pipeline id and to specialize the render pipeline descriptor.
121#[derive(Debug, PartialEq, Eq, Clone, Copy)]
122pub struct OitResolvePipelineKey {
123    hdr: bool,
124    layer_count: i32,
125}
126
127#[allow(clippy::too_many_arguments)]
128pub fn queue_oit_resolve_pipeline(
129    mut commands: Commands,
130    pipeline_cache: Res<PipelineCache>,
131    resolve_pipeline: Res<OitResolvePipeline>,
132    views: Query<
133        (
134            Entity,
135            &ExtractedView,
136            &OrderIndependentTransparencySettings,
137        ),
138        With<OrderIndependentTransparencySettings>,
139    >,
140    // Store the key with the id to make the clean up logic easier.
141    // This also means it will always replace the entry if the key changes so nothing to clean up.
142    mut cached_pipeline_id: Local<EntityHashMap<(OitResolvePipelineKey, CachedRenderPipelineId)>>,
143) {
144    let mut current_view_entities = EntityHashSet::default();
145    for (e, view, oit_settings) in &views {
146        current_view_entities.insert(e);
147        let key = OitResolvePipelineKey {
148            hdr: view.hdr,
149            layer_count: oit_settings.layer_count,
150        };
151
152        if let Some((cached_key, id)) = cached_pipeline_id.get(&e) {
153            if *cached_key == key {
154                commands.entity(e).insert(OitResolvePipelineId(*id));
155                continue;
156            }
157        }
158
159        let desc = specialize_oit_resolve_pipeline(key, &resolve_pipeline);
160
161        let pipeline_id = pipeline_cache.queue_render_pipeline(desc);
162        commands.entity(e).insert(OitResolvePipelineId(pipeline_id));
163        cached_pipeline_id.insert(e, (key, pipeline_id));
164    }
165
166    // Clear cache for views that don't exist anymore.
167    for e in cached_pipeline_id.keys().copied().collect::<Vec<_>>() {
168        if !current_view_entities.contains(&e) {
169            cached_pipeline_id.remove(&e);
170        }
171    }
172}
173
174fn specialize_oit_resolve_pipeline(
175    key: OitResolvePipelineKey,
176    resolve_pipeline: &OitResolvePipeline,
177) -> RenderPipelineDescriptor {
178    let format = if key.hdr {
179        ViewTarget::TEXTURE_FORMAT_HDR
180    } else {
181        TextureFormat::bevy_default()
182    };
183
184    RenderPipelineDescriptor {
185        label: Some("oit_resolve_pipeline".into()),
186        layout: vec![
187            resolve_pipeline.view_bind_group_layout.clone(),
188            resolve_pipeline.oit_depth_bind_group_layout.clone(),
189        ],
190        fragment: Some(FragmentState {
191            entry_point: "fragment".into(),
192            shader: OIT_RESOLVE_SHADER_HANDLE,
193            shader_defs: vec![ShaderDefVal::UInt(
194                "LAYER_COUNT".into(),
195                key.layer_count as u32,
196            )],
197            targets: vec![Some(ColorTargetState {
198                format,
199                blend: Some(BlendState {
200                    color: BlendComponent::OVER,
201                    alpha: BlendComponent::OVER,
202                }),
203                write_mask: ColorWrites::ALL,
204            })],
205        }),
206        vertex: fullscreen_shader_vertex_state(),
207        primitive: PrimitiveState::default(),
208        depth_stencil: None,
209        multisample: MultisampleState::default(),
210        push_constant_ranges: vec![],
211        zero_initialize_workgroup_memory: false,
212    }
213}
214
215pub fn prepare_oit_resolve_bind_group(
216    mut commands: Commands,
217    resolve_pipeline: Res<OitResolvePipeline>,
218    render_device: Res<RenderDevice>,
219    view_uniforms: Res<ViewUniforms>,
220    buffers: Res<OitBuffers>,
221) {
222    if let (Some(binding), Some(layers_binding), Some(layer_ids_binding)) = (
223        view_uniforms.uniforms.binding(),
224        buffers.layers.binding(),
225        buffers.layer_ids.binding(),
226    ) {
227        let bind_group = render_device.create_bind_group(
228            "oit_resolve_bind_group",
229            &resolve_pipeline.view_bind_group_layout,
230            &BindGroupEntries::sequential((binding.clone(), layers_binding, layer_ids_binding)),
231        );
232        commands.insert_resource(OitResolveBindGroup(bind_group));
233    }
234}