Files
marathon/crates/libmarathon/src/render/oit/resolve/mod.rs
Sienna Meridian Satterwhite b03b71c1d9 feat(rendering): Vendor Bevy rendering crates (Phase 1 complete)
Closes #6, #7, #8, #9, #10
Refs #2, #122

Vendored bevy_render, bevy_core_pipeline, and bevy_pbr from Bevy v0.17.2
(commit 566358363126dd69f6e457e47f306c68f8041d2a) into libmarathon.

- ~51K LOC vendored to crates/libmarathon/src/render/
- Merged bevy_render_macros into crates/macros/
- Fixed 773→0 compilation errors
- Updated dependencies (encase 0.10→0.11, added 4 new deps)
- Removed bevy_render/pbr/core_pipeline from app Cargo features

All builds passing, macOS smoke test successful.

Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
2026-02-07 18:22:13 +00:00

256 lines
8.4 KiB
Rust

use super::OitBuffers;
use crate::render::{oit::OrderIndependentTransparencySettings, FullscreenShader};
use bevy_app::Plugin;
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer};
use bevy_derive::Deref;
use bevy_ecs::{
entity::{EntityHashMap, EntityHashSet},
prelude::*,
};
use bevy_image::BevyDefault as _;
use crate::render::{
render_resource::{
binding_types::{storage_buffer_sized, texture_depth_2d, uniform_buffer},
BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, BlendComponent,
BlendState, CachedRenderPipelineId, ColorTargetState, ColorWrites, DownlevelFlags,
FragmentState, PipelineCache, RenderPipelineDescriptor, ShaderStages, TextureFormat,
},
renderer::{RenderAdapter, RenderDevice},
view::{ExtractedView, ViewTarget, ViewUniform, ViewUniforms},
Render, RenderApp, RenderSystems,
};
use bevy_shader::ShaderDefVal;
use bevy_utils::default;
use tracing::warn;
/// Contains the render node used to run the resolve pass.
pub mod node;
/// Minimum required value of `wgpu::Limits::max_storage_buffers_per_shader_stage`.
pub const OIT_REQUIRED_STORAGE_BUFFERS: u32 = 2;
/// Plugin needed to resolve the Order Independent Transparency (OIT) buffer to the screen.
pub struct OitResolvePlugin;
impl Plugin for OitResolvePlugin {
fn build(&self, app: &mut bevy_app::App) {
embedded_asset!(app, "oit_resolve.wgsl");
}
fn finish(&self, app: &mut bevy_app::App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
if !is_oit_supported(
render_app.world().resource::<RenderAdapter>(),
render_app.world().resource::<RenderDevice>(),
true,
) {
return;
}
render_app
.add_systems(
Render,
(
queue_oit_resolve_pipeline.in_set(RenderSystems::Queue),
prepare_oit_resolve_bind_group.in_set(RenderSystems::PrepareBindGroups),
),
)
.init_resource::<OitResolvePipeline>();
}
}
pub fn is_oit_supported(adapter: &RenderAdapter, device: &RenderDevice, warn: bool) -> bool {
if !adapter
.get_downlevel_capabilities()
.flags
.contains(DownlevelFlags::FRAGMENT_WRITABLE_STORAGE)
{
if warn {
warn!("OrderIndependentTransparencyPlugin not loaded. GPU lacks support: DownlevelFlags::FRAGMENT_WRITABLE_STORAGE.");
}
return false;
}
let max_storage_buffers_per_shader_stage = device.limits().max_storage_buffers_per_shader_stage;
if max_storage_buffers_per_shader_stage < OIT_REQUIRED_STORAGE_BUFFERS {
if warn {
warn!(
max_storage_buffers_per_shader_stage,
OIT_REQUIRED_STORAGE_BUFFERS,
"OrderIndependentTransparencyPlugin not loaded. RenderDevice lacks support: max_storage_buffers_per_shader_stage < OIT_REQUIRED_STORAGE_BUFFERS."
);
}
return false;
}
true
}
/// Bind group for the OIT resolve pass.
#[derive(Resource, Deref)]
pub struct OitResolveBindGroup(pub BindGroup);
/// Bind group layouts used for the OIT resolve pass.
#[derive(Resource)]
pub struct OitResolvePipeline {
/// View bind group layout.
pub view_bind_group_layout: BindGroupLayout,
/// Depth bind group layout.
pub oit_depth_bind_group_layout: BindGroupLayout,
}
impl FromWorld for OitResolvePipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
let view_bind_group_layout = render_device.create_bind_group_layout(
"oit_resolve_bind_group_layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
uniform_buffer::<ViewUniform>(true),
// layers
storage_buffer_sized(false, None),
// layer ids
storage_buffer_sized(false, None),
),
),
);
let oit_depth_bind_group_layout = render_device.create_bind_group_layout(
"oit_depth_bind_group_layout",
&BindGroupLayoutEntries::single(ShaderStages::FRAGMENT, texture_depth_2d()),
);
OitResolvePipeline {
view_bind_group_layout,
oit_depth_bind_group_layout,
}
}
}
#[derive(Component, Deref, Clone, Copy)]
pub struct OitResolvePipelineId(pub CachedRenderPipelineId);
/// This key is used to cache the pipeline id and to specialize the render pipeline descriptor.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct OitResolvePipelineKey {
hdr: bool,
layer_count: i32,
}
pub fn queue_oit_resolve_pipeline(
mut commands: Commands,
pipeline_cache: Res<PipelineCache>,
resolve_pipeline: Res<OitResolvePipeline>,
views: Query<
(
Entity,
&ExtractedView,
&OrderIndependentTransparencySettings,
),
With<OrderIndependentTransparencySettings>,
>,
fullscreen_shader: Res<FullscreenShader>,
asset_server: Res<AssetServer>,
// Store the key with the id to make the clean up logic easier.
// This also means it will always replace the entry if the key changes so nothing to clean up.
mut cached_pipeline_id: Local<EntityHashMap<(OitResolvePipelineKey, CachedRenderPipelineId)>>,
) {
let mut current_view_entities = EntityHashSet::default();
for (e, view, oit_settings) in &views {
current_view_entities.insert(e);
let key = OitResolvePipelineKey {
hdr: view.hdr,
layer_count: oit_settings.layer_count,
};
if let Some((cached_key, id)) = cached_pipeline_id.get(&e)
&& *cached_key == key
{
commands.entity(e).insert(OitResolvePipelineId(*id));
continue;
}
let desc = specialize_oit_resolve_pipeline(
key,
&resolve_pipeline,
&fullscreen_shader,
&asset_server,
);
let pipeline_id = pipeline_cache.queue_render_pipeline(desc);
commands.entity(e).insert(OitResolvePipelineId(pipeline_id));
cached_pipeline_id.insert(e, (key, pipeline_id));
}
// Clear cache for views that don't exist anymore.
for e in cached_pipeline_id.keys().copied().collect::<Vec<_>>() {
if !current_view_entities.contains(&e) {
cached_pipeline_id.remove(&e);
}
}
}
fn specialize_oit_resolve_pipeline(
key: OitResolvePipelineKey,
resolve_pipeline: &OitResolvePipeline,
fullscreen_shader: &FullscreenShader,
asset_server: &AssetServer,
) -> RenderPipelineDescriptor {
let format = if key.hdr {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
};
RenderPipelineDescriptor {
label: Some("oit_resolve_pipeline".into()),
layout: vec![
resolve_pipeline.view_bind_group_layout.clone(),
resolve_pipeline.oit_depth_bind_group_layout.clone(),
],
fragment: Some(FragmentState {
shader: load_embedded_asset!(asset_server, "oit_resolve.wgsl"),
shader_defs: vec![ShaderDefVal::UInt(
"LAYER_COUNT".into(),
key.layer_count as u32,
)],
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState {
color: BlendComponent::OVER,
alpha: BlendComponent::OVER,
}),
write_mask: ColorWrites::ALL,
})],
..default()
}),
vertex: fullscreen_shader.to_vertex_state(),
..default()
}
}
pub fn prepare_oit_resolve_bind_group(
mut commands: Commands,
resolve_pipeline: Res<OitResolvePipeline>,
render_device: Res<RenderDevice>,
view_uniforms: Res<ViewUniforms>,
buffers: Res<OitBuffers>,
) {
if let (Some(binding), Some(layers_binding), Some(layer_ids_binding)) = (
view_uniforms.uniforms.binding(),
buffers.layers.binding(),
buffers.layer_ids.binding(),
) {
let bind_group = render_device.create_bind_group(
"oit_resolve_bind_group",
&resolve_pipeline.view_bind_group_layout,
&BindGroupEntries::sequential((binding.clone(), layers_binding, layer_ids_binding)),
);
commands.insert_resource(OitResolveBindGroup(bind_group));
}
}