use bytemuck::{Pod, Zeroable};
use crate::shader::{ShaderHandle, StockShader, UniformBlock, UniformValue};
use crate::tree::{Color, Rect};
use crate::vector::IconMaterial;
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable, Debug)]
pub struct QuadInstance {
pub rect: [f32; 4],
pub slot_a: [f32; 4],
pub slot_b: [f32; 4],
pub slot_c: [f32; 4],
pub inner_rect: [f32; 4],
pub slot_d: [f32; 4],
pub slot_e: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable, Debug)]
pub struct IconInstance {
pub rect: [f32; 4],
pub line: [f32; 4],
pub color: [f32; 4],
pub params: [f32; 4],
}
#[derive(Clone, Copy)]
pub struct InstanceRun {
pub handle: ShaderHandle,
pub scissor: Option<PhysicalScissor>,
pub first: u32,
pub count: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IconRunKind {
Tess,
Msdf,
}
#[derive(Clone, Copy)]
pub struct IconRun {
pub kind: IconRunKind,
pub scissor: Option<PhysicalScissor>,
pub first: u32,
pub count: u32,
pub page: u32,
pub material: IconMaterial,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PhysicalScissor {
pub x: u32,
pub y: u32,
pub w: u32,
pub h: u32,
}
#[derive(Clone, Copy)]
pub enum PaintItem {
QuadRun(usize),
IconRun(usize),
Text(usize),
Image(usize),
AppTexture(usize),
Vector(usize),
BackdropSnapshot,
}
pub fn close_run(
runs: &mut Vec<InstanceRun>,
paint_items: &mut Vec<PaintItem>,
run_key: Option<(ShaderHandle, Option<PhysicalScissor>)>,
first: u32,
end: u32,
) {
if let Some((handle, scissor)) = run_key {
let count = end - first;
if count > 0 {
let index = runs.len();
runs.push(InstanceRun {
handle,
scissor,
first,
count,
});
paint_items.push(PaintItem::QuadRun(index));
}
}
}
pub fn physical_scissor(
scissor: Option<Rect>,
scale: f32,
viewport_px: (u32, u32),
) -> Option<PhysicalScissor> {
let r = scissor?;
let x1 = (r.x * scale).floor().clamp(0.0, viewport_px.0 as f32) as u32;
let y1 = (r.y * scale).floor().clamp(0.0, viewport_px.1 as f32) as u32;
let x2 = (r.right() * scale).ceil().clamp(0.0, viewport_px.0 as f32) as u32;
let y2 = (r.bottom() * scale).ceil().clamp(0.0, viewport_px.1 as f32) as u32;
Some(PhysicalScissor {
x: x1,
y: y1,
w: x2.saturating_sub(x1),
h: y2.saturating_sub(y1),
})
}
pub fn pack_instance(rect: Rect, shader: ShaderHandle, uniforms: &UniformBlock) -> QuadInstance {
let rect_arr = [rect.x, rect.y, rect.w, rect.h];
let inner_rect = uniforms
.get("inner_rect")
.map(value_to_vec4)
.unwrap_or(rect_arr);
match shader {
ShaderHandle::Stock(StockShader::RoundedRect) => {
let radii = uniforms.get("radii").map(value_to_vec4);
let scalar_radius = uniforms.get("radius").and_then(as_f32).unwrap_or(0.0);
let radii = radii.unwrap_or([scalar_radius; 4]);
let max_radius = radii[0].max(radii[1]).max(radii[2]).max(radii[3]);
QuadInstance {
rect: rect_arr,
inner_rect,
slot_a: uniforms
.get("fill")
.and_then(as_color)
.map(rgba_f32)
.unwrap_or([0.0; 4]),
slot_b: uniforms
.get("stroke")
.and_then(as_color)
.map(rgba_f32)
.unwrap_or([0.0; 4]),
slot_c: [
uniforms.get("stroke_width").and_then(as_f32).unwrap_or(0.0),
max_radius,
uniforms.get("shadow").and_then(as_f32).unwrap_or(0.0),
uniforms.get("focus_width").and_then(as_f32).unwrap_or(0.0),
],
slot_d: uniforms
.get("focus_color")
.and_then(as_color)
.map(rgba_f32)
.unwrap_or([0.0; 4]),
slot_e: radii,
}
}
_ => QuadInstance {
rect: rect_arr,
inner_rect,
slot_a: uniforms.get("vec_a").map(value_to_vec4).unwrap_or([0.0; 4]),
slot_b: uniforms.get("vec_b").map(value_to_vec4).unwrap_or([0.0; 4]),
slot_c: uniforms.get("vec_c").map(value_to_vec4).unwrap_or([0.0; 4]),
slot_d: uniforms.get("vec_d").map(value_to_vec4).unwrap_or([0.0; 4]),
slot_e: uniforms.get("vec_e").map(value_to_vec4).unwrap_or([0.0; 4]),
},
}
}
fn as_color(v: &UniformValue) -> Option<Color> {
match v {
UniformValue::Color(c) => Some(*c),
_ => None,
}
}
fn as_f32(v: &UniformValue) -> Option<f32> {
match v {
UniformValue::F32(f) => Some(*f),
_ => None,
}
}
fn value_to_vec4(v: &UniformValue) -> [f32; 4] {
match v {
UniformValue::Color(c) => rgba_f32(*c),
UniformValue::Vec4(a) => *a,
UniformValue::Vec2([x, y]) => [*x, *y, 0.0, 0.0],
UniformValue::F32(f) => [*f, 0.0, 0.0, 0.0],
UniformValue::Bool(b) => [if *b { 1.0 } else { 0.0 }, 0.0, 0.0, 0.0],
}
}
pub fn rgba_f32(c: Color) -> [f32; 4] {
[
srgb_to_linear(c.r as f32 / 255.0),
srgb_to_linear(c.g as f32 / 255.0),
srgb_to_linear(c.b as f32 / 255.0),
c.a as f32 / 255.0,
]
}
fn srgb_to_linear(c: f32) -> f32 {
if c <= 0.04045 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shader::UniformBlock;
use crate::tokens;
#[test]
fn focus_uniforms_pack_into_rounded_rect_slots() {
let mut uniforms = UniformBlock::new();
uniforms.insert("fill", UniformValue::Color(Color::rgba(40, 40, 40, 255)));
uniforms.insert("radius", UniformValue::F32(8.0));
uniforms.insert("focus_color", UniformValue::Color(tokens::RING));
uniforms.insert("focus_width", UniformValue::F32(tokens::RING_WIDTH));
let inst = pack_instance(
Rect::new(1.0, 2.0, 30.0, 40.0),
ShaderHandle::Stock(StockShader::RoundedRect),
&uniforms,
);
assert_eq!(inst.rect, [1.0, 2.0, 30.0, 40.0]);
assert_eq!(
inst.inner_rect, inst.rect,
"no inner_rect uniform → fall back to painted rect"
);
assert_eq!(
inst.slot_c[1], 8.0,
"max corner radius in slot_c.y (uniform corners derived from scalar `radius` uniform)"
);
assert_eq!(
inst.slot_e,
[8.0, 8.0, 8.0, 8.0],
"scalar `radius` uniform fills all four corners on slot_e"
);
assert_eq!(
inst.slot_c[3],
tokens::RING_WIDTH,
"focus_width in slot_c.w"
);
assert!(inst.slot_d[3] > 0.0, "focus_color alpha should be visible");
}
#[test]
fn per_corner_radii_uniform_routes_to_slot_e() {
let mut uniforms = UniformBlock::new();
uniforms.insert("fill", UniformValue::Color(Color::rgba(40, 40, 40, 255)));
uniforms.insert("radii", UniformValue::Vec4([12.0, 12.0, 0.0, 0.0]));
uniforms.insert("radius", UniformValue::F32(12.0));
let inst = pack_instance(
Rect::new(0.0, 0.0, 100.0, 40.0),
ShaderHandle::Stock(StockShader::RoundedRect),
&uniforms,
);
assert_eq!(inst.slot_e, [12.0, 12.0, 0.0, 0.0]);
assert_eq!(inst.slot_c[1], 12.0, "max corner radius -> slot_c.y");
}
#[test]
fn physical_scissor_converts_logical_to_physical_pixels() {
let scissor = physical_scissor(Some(Rect::new(10.2, 20.2, 30.2, 40.2)), 2.0, (200, 200))
.expect("scissor");
assert_eq!(
scissor,
PhysicalScissor {
x: 20,
y: 40,
w: 61,
h: 81
}
);
}
}