use bevy::ecs::system::SystemParamItem;
use bevy::ecs::system::lifetimeless::SRes;
use bevy::math::{Mat4, Vec2, Vec4};
use bevy::prelude::*;
use bevy::render::render_phase::{
PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass,
};
use bevy::render::render_resource::{BindGroup, DynamicUniformBuffer, ShaderType};
#[derive(Clone, Copy, ShaderType)]
pub struct CompositeUniforms {
pub model: Mat4,
pub clip_min: Vec2,
pub clip_max: Vec2,
pub edge_feather: f32,
pub pad_a: f32,
pub pad_b: Vec2,
pub radius: Vec4,
pub box_center: Vec2,
pub box_size: Vec2,
}
pub fn open_clip() -> (Vec2, Vec2) {
(Vec2::splat(-f32::MAX), Vec2::splat(f32::MAX))
}
#[derive(Resource)]
pub struct CompositeUniformsMeta {
pub uniforms: DynamicUniformBuffer<CompositeUniforms>,
pub bind_group: Option<BindGroup>,
}
impl Default for CompositeUniformsMeta {
fn default() -> Self {
let mut uniforms = DynamicUniformBuffer::default();
uniforms.set_label(Some("ui_layer_composite_uniforms"));
Self {
uniforms,
bind_group: None,
}
}
}
pub struct SetCompositeUniforms<const I: usize>;
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetCompositeUniforms<I> {
type Param = SRes<CompositeUniformsMeta>;
type ViewQuery = ();
type ItemQuery = bevy::ecs::system::lifetimeless::Read<super::LayerCompositeBatch>;
#[inline]
fn render<'w>(
_item: &P,
_view: (),
batch: Option<&'w super::LayerCompositeBatch>,
meta: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Some(batch) = batch else {
return RenderCommandResult::Skip;
};
let Some(bind_group) = &meta.into_inner().bind_group else {
return RenderCommandResult::Failure("composite uniforms bind group missing");
};
pass.set_bind_group(I, bind_group, &[batch.uniform_offset]);
RenderCommandResult::Success
}
}
#[cfg(test)]
mod tests {
use super::*;
use bevy::render::render_resource::encase::UniformBuffer;
fn f32_at(bytes: &[u8], offset: usize) -> f32 {
f32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap())
}
#[test]
fn composite_uniforms_match_the_documented_wgsl_layout() {
assert_eq!(CompositeUniforms::min_size().get(), 128);
let value = CompositeUniforms {
model: Mat4::from_translation(bevy::math::Vec3::new(9.0, 0.0, 0.0)),
clip_min: Vec2::new(1.0, 2.0),
clip_max: Vec2::new(3.0, 4.0),
edge_feather: 1.5,
pad_a: 0.0,
pad_b: Vec2::ZERO,
radius: Vec4::new(5.0, 6.0, 7.0, 8.0),
box_center: Vec2::new(10.0, 11.0),
box_size: Vec2::new(12.0, 13.0),
};
let mut buffer = UniformBuffer::new(Vec::<u8>::new());
buffer.write(&value).expect("uniform write");
let bytes = buffer.into_inner();
assert_eq!(bytes.len(), 128);
assert_eq!(f32_at(&bytes, 48), 9.0); assert_eq!(f32_at(&bytes, 64), 1.0); assert_eq!(f32_at(&bytes, 68), 2.0); assert_eq!(f32_at(&bytes, 72), 3.0); assert_eq!(f32_at(&bytes, 76), 4.0); assert_eq!(f32_at(&bytes, 80), 1.5); assert_eq!(f32_at(&bytes, 96), 5.0); assert_eq!(f32_at(&bytes, 100), 6.0); assert_eq!(f32_at(&bytes, 104), 7.0); assert_eq!(f32_at(&bytes, 108), 8.0); assert_eq!(f32_at(&bytes, 112), 10.0); assert_eq!(f32_at(&bytes, 116), 11.0); assert_eq!(f32_at(&bytes, 120), 12.0); assert_eq!(f32_at(&bytes, 124), 13.0); }
}