use bevy::platform::collections::HashMap;
use bevy::prelude::*;
use bevy::render::render_phase::ViewSortedRenderPhases;
use bevy::render::render_resource::{
BindGroup, Extent3d, PipelineCache, TextureDescriptor, TextureDimension, TextureFormat,
TextureUsages, TextureViewDescriptor,
};
use bevy::render::renderer::RenderDevice;
use bevy::render::sync_world::MainEntity;
use bevy::render::texture::CachedTexture;
use bevy::ui_render::TransparentUi;
use super::{ExtractedUiLayers, mips};
#[derive(Resource, Default)]
pub struct LayerAtlases {
pub textures: Vec<CachedTexture>,
}
pub struct LayerSlot {
pub texture: CachedTexture,
pub size: UVec2,
pub format: TextureFormat,
pub bind_group: Option<BindGroup>,
pub mips: Option<mips::MipChain>,
pub mips_valid: bool,
pub bind_group_mips: Option<BindGroup>,
pub content_valid: bool,
pub filter: Option<FilterSlot>,
pub backdrop: Option<super::backdrop::BackdropSlot>,
pub last_seen: u64,
}
pub struct FilterSlot {
pub textures: [CachedTexture; 2],
pub params_version: u32,
pub output_valid: bool,
pub gated_frames: u32,
pub gate_warned: bool,
pub output_index: usize,
pub composite_bind_group: Option<(usize, BindGroup)>,
pub mips: [Option<mips::MipChain>; 2],
pub mips_valid: bool,
pub composite_bind_group_mips: Option<(usize, BindGroup)>,
}
#[derive(Resource, Default)]
pub struct LayerTextureStore {
pub slots: HashMap<MainEntity, LayerSlot>,
pub frame: u64,
}
pub fn prepare_layer_textures(
extracted: Res<ExtractedUiLayers>,
render_device: Res<RenderDevice>,
pipeline_cache: Res<PipelineCache>,
phases: Res<ViewSortedRenderPhases<TransparentUi>>,
mut store: ResMut<LayerTextureStore>,
mut atlases: ResMut<LayerAtlases>,
) {
atlases.textures.clear();
let store = &mut *store;
store.frame += 1;
let frame = store.frame;
for layer in &extracted.layers {
let wanted = layer.size.max(UVec2::ONE);
let slot = store.slots.entry(layer.main_entity).or_insert_with(|| {
alloc_layer_slot(
&render_device,
wanted,
layer.target_format,
layer.wants_mips,
)
});
if slot.size != wanted
|| slot.format != layer.target_format
|| slot.mips.is_some() != layer.wants_mips
{
*slot = alloc_layer_slot(
&render_device,
wanted,
layer.target_format,
layer.wants_mips,
);
}
if layer.chain.is_some() {
if slot.filter.is_none() {
slot.filter = Some(alloc_filter_slot(
&render_device,
wanted,
layer.target_format,
layer.wants_mips,
));
}
} else {
slot.filter = None;
}
if layer.backdrop_chain.is_some() {
if slot.backdrop.is_none() {
slot.backdrop = Some(super::backdrop::alloc_backdrop_slot(
&render_device,
wanted,
layer.target_format,
));
}
} else {
slot.backdrop = None;
}
if layer.needs_capture {
slot.content_valid = phases.get(&layer.retained).is_some_and(|phase| {
!phase.items.is_empty()
&& phase
.items
.values()
.all(|i| pipeline_cache.get_render_pipeline(i.pipeline).is_some())
});
slot.mips_valid = false;
}
slot.last_seen = frame;
atlases.textures.push(slot.texture.clone());
}
store.slots.retain(|_, slot| slot.last_seen + 3 >= frame);
}
pub(super) fn alloc_capture_texture(
render_device: &RenderDevice,
label: &'static str,
size: UVec2,
format: TextureFormat,
mipped: bool,
) -> (CachedTexture, Option<mips::MipChain>) {
let levels = if mipped {
mips::mip_level_count(size)
} else {
1
};
let texture = render_device.create_texture(&TextureDescriptor {
label: Some(label),
size: Extent3d {
width: size.x,
height: size.y,
depth_or_array_layers: 1,
},
mip_level_count: levels,
sample_count: 1,
dimension: TextureDimension::D2,
format,
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let default_view = texture.create_view(&TextureViewDescriptor {
mip_level_count: Some(1),
..Default::default()
});
let chain = mipped.then(|| mips::build_mip_chain(&texture, levels));
(
CachedTexture {
texture,
default_view,
},
chain,
)
}
fn alloc_layer_slot(
render_device: &RenderDevice,
size: UVec2,
format: TextureFormat,
mipped: bool,
) -> LayerSlot {
let (texture, mips) =
alloc_capture_texture(render_device, "ui_layer_capture", size, format, mipped);
LayerSlot {
texture,
size,
format,
bind_group: None,
mips,
mips_valid: false,
bind_group_mips: None,
content_valid: false,
filter: None,
backdrop: None,
last_seen: 0,
}
}
fn alloc_filter_slot(
render_device: &RenderDevice,
size: UVec2,
format: TextureFormat,
mipped: bool,
) -> FilterSlot {
let alloc =
|label: &'static str| alloc_capture_texture(render_device, label, size, format, mipped);
let (ping, ping_mips) = alloc("ui_layer_filter_ping");
let (pong, pong_mips) = alloc("ui_layer_filter_pong");
FilterSlot {
textures: [ping, pong],
params_version: 0,
output_valid: false,
gated_frames: 0,
gate_warned: false,
output_index: 0,
composite_bind_group: None,
mips: [ping_mips, pong_mips],
mips_valid: false,
composite_bind_group_mips: None,
}
}