use bevy::asset::{AssetServer, Handle};
use bevy::prelude::*;
use bevy::render::render_resource::binding_types::{sampler, texture_2d};
use bevy::render::render_resource::{
BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries,
CachedRenderPipelineId, ColorTargetState, ColorWrites, FilterMode, FragmentState,
PipelineCache, RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor,
ShaderStages, SpecializedRenderPipeline, SpecializedRenderPipelines, Texture, TextureFormat,
TextureSampleType, TextureView, TextureViewDescriptor, VertexState,
};
use bevy::render::renderer::RenderDevice;
use bevy::shader::Shader;
use super::{ExtractedUiLayers, FilterSlot, LayerTextureStore};
pub struct MipChain {
pub full_view: TextureView,
pub level_views: Vec<TextureView>,
pub bind_groups: Vec<BindGroup>,
}
pub fn mip_level_count(size: UVec2) -> u32 {
size.max(UVec2::ONE).max_element().ilog2() + 1
}
pub fn build_mip_chain(texture: &Texture, levels: u32) -> MipChain {
let full_view = texture.create_view(&TextureViewDescriptor {
label: Some("ui_layer_mips_full"),
..Default::default()
});
let level_views = (0..levels)
.map(|level| {
texture.create_view(&TextureViewDescriptor {
label: Some("ui_layer_mip_level"),
base_mip_level: level,
mip_level_count: Some(1),
..Default::default()
})
})
.collect();
MipChain {
full_view,
level_views,
bind_groups: Vec::new(),
}
}
#[derive(Resource)]
pub struct LayerBlitPipeline {
pub layout: BindGroupLayoutDescriptor,
pub sampler: Sampler,
pub shader: Handle<Shader>,
}
pub fn init_layer_blit_pipeline(
mut commands: Commands,
render_device: Res<RenderDevice>,
asset_server: Res<AssetServer>,
) {
let layout = BindGroupLayoutDescriptor::new(
"ui_layer_blit_layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
),
),
);
commands.insert_resource(LayerBlitPipeline {
layout,
sampler: render_device.create_sampler(&SamplerDescriptor {
label: Some("ui_layer_blit_sampler"),
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..Default::default()
}),
shader: bevy::asset::load_embedded_asset!(asset_server.as_ref(), "mip_blit.wgsl"),
});
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct LayerBlitPipelineKey {
pub target_format: TextureFormat,
}
impl SpecializedRenderPipeline for LayerBlitPipeline {
type Key = LayerBlitPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
RenderPipelineDescriptor {
vertex: VertexState {
shader: self.shader.clone(),
entry_point: Some("vertex".into()),
..Default::default()
},
fragment: Some(FragmentState {
shader: self.shader.clone(),
entry_point: Some("fragment".into()),
targets: vec![Some(ColorTargetState {
format: key.target_format,
blend: None,
write_mask: ColorWrites::ALL,
})],
..Default::default()
}),
layout: vec![self.layout.clone()],
label: Some("ui_layer_blit_pipeline".into()),
..Default::default()
}
}
}
pub struct MipLevel {
pub bind_group: BindGroup,
pub target: TextureView,
}
pub struct MipRun {
pub pipeline: CachedRenderPipelineId,
pub levels: Vec<MipLevel>,
}
#[derive(Resource, Default)]
pub struct LayerMipMeta {
pub runs: Vec<Option<MipRun>>,
}
pub fn prepare_layer_mips(
extracted: Res<ExtractedUiLayers>,
mut store: ResMut<LayerTextureStore>,
pipeline: Option<Res<LayerBlitPipeline>>,
mut specialized: ResMut<SpecializedRenderPipelines<LayerBlitPipeline>>,
pipeline_cache: Res<PipelineCache>,
render_device: Res<RenderDevice>,
mut meta: ResMut<LayerMipMeta>,
) {
meta.runs.clear();
meta.runs.resize_with(extracted.layers.len(), || None);
let Some(pipeline) = pipeline else {
return;
};
for (idx, layer) in extracted.layers.iter().enumerate() {
if !layer.wants_mips {
continue;
}
let Some(slot) = store.slots.get_mut(&layer.main_entity) else {
continue;
};
let pipeline_id = specialized.specialize(
&pipeline_cache,
&pipeline,
LayerBlitPipelineKey {
target_format: layer.target_format,
},
);
let (chain, mips_valid, source_valid) = if layer.chain.is_some() {
let Some(filter) = slot.filter.as_mut() else {
continue;
};
let FilterSlot {
mips,
mips_valid,
output_index,
output_valid,
..
} = filter;
let Some(chain) = mips[*output_index].as_mut() else {
continue;
};
(chain, mips_valid, *output_valid)
} else {
let Some(chain) = slot.mips.as_mut() else {
continue;
};
(chain, &mut slot.mips_valid, slot.content_valid)
};
if *mips_valid {
continue; }
if !source_valid || pipeline_cache.get_render_pipeline(pipeline_id).is_none() {
continue;
}
if chain.bind_groups.is_empty() {
let layout = pipeline_cache.get_bind_group_layout(&pipeline.layout);
chain.bind_groups = chain
.level_views
.iter()
.map(|view| {
render_device.create_bind_group(
"ui_layer_mip_source",
&layout,
&BindGroupEntries::sequential((view, &pipeline.sampler)),
)
})
.collect();
}
let levels = (0..chain.level_views.len().saturating_sub(1))
.map(|level| MipLevel {
bind_group: chain.bind_groups[level].clone(),
target: chain.level_views[level + 1].clone(),
})
.collect();
meta.runs[idx] = Some(MipRun {
pipeline: pipeline_id,
levels,
});
*mips_valid = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mip_level_count_covers_full_chain() {
assert_eq!(mip_level_count(UVec2::new(1, 1)), 1);
assert_eq!(mip_level_count(UVec2::new(2, 2)), 2);
assert_eq!(mip_level_count(UVec2::new(256, 64)), 9);
assert_eq!(mip_level_count(UVec2::new(300, 20)), 9);
assert_eq!(mip_level_count(UVec2::ZERO), 1);
}
}