use bytemuck::{Pod, Zeroable};
use vello_common::encode::{
EncodedBlurredRoundedRectangle, EncodedGradient, EncodedKind, EncodedPaint, RadialKind,
};
use crate::EngineError;
use crate::cache::CachedRamp;
use super::strips::PaintType;
use super::{MIN_RESOURCE_TEXTURE_HEIGHT, TEXEL_BYTES_SHIFT, resource_texture_descriptor};
#[repr(C, align(16))]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct GpuEncodedImage {
pub image_params: u32,
pub image_size: u32,
pub image_offset: u32,
pub transform: [f32; 6],
pub tint: u32,
pub tint_mode: u32,
pub image_padding: u32,
}
#[repr(C, align(16))]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct GpuLinearGradient {
pub texture_width_and_extend_mode: u32,
pub gradient_start: u32,
pub transform: [f32; 6],
}
#[repr(C, align(16))]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct GpuRadialGradient {
pub texture_width_and_extend_mode: u32,
pub gradient_start: u32,
pub transform: [f32; 6],
pub kind_and_f_is_swapped: u32,
pub bias: f32,
pub scale: f32,
pub fp0: f32,
pub fp1: f32,
pub fr1: f32,
pub f_focal_x: f32,
pub scaled_r0_squared: f32,
}
#[repr(C, align(16))]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct GpuSweepGradient {
pub texture_width_and_extend_mode: u32,
pub gradient_start: u32,
pub transform: [f32; 6],
pub start_angle: f32,
pub inv_angle_delta: f32,
pub _padding: [u32; 2],
}
#[repr(C, align(16))]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct GpuBlurredRoundedRect {
pub transform: [f32; 6],
pub color: u32,
pub invert: u32,
pub params0: [f32; 4],
pub params1: [f32; 4],
pub size: [f32; 2],
pub _padding1: [u32; 2],
}
const _: () = assert!(size_of::<GpuEncodedImage>() == 48);
const _: () = assert!(size_of::<GpuLinearGradient>() == 32);
const _: () = assert!(size_of::<GpuRadialGradient>() == 64);
const _: () = assert!(size_of::<GpuSweepGradient>() == 48);
const _: () = assert!(size_of::<GpuBlurredRoundedRect>() == 80);
#[derive(Debug, Clone, Copy)]
pub enum GpuEncodedPaint {
Image(GpuEncodedImage),
LinearGradient(GpuLinearGradient),
RadialGradient(GpuRadialGradient),
SweepGradient(GpuSweepGradient),
BlurredRoundedRect(GpuBlurredRoundedRect),
}
macro_rules! paint_record_bytes {
($($ty:ty),+ $(,)?) => {
$(
impl $ty {
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
bytemuck::bytes_of(self)
}
}
)+
};
}
paint_record_bytes!(
GpuEncodedImage,
GpuLinearGradient,
GpuRadialGradient,
GpuSweepGradient,
GpuBlurredRoundedRect,
);
impl GpuEncodedPaint {
#[must_use]
pub const fn paint_type(&self) -> PaintType {
match self {
Self::Image(_) => PaintType::Image,
Self::LinearGradient(_) => PaintType::LinearGradient,
Self::RadialGradient(_) => PaintType::RadialGradient,
Self::SweepGradient(_) => PaintType::SweepGradient,
Self::BlurredRoundedRect(_) => PaintType::BlurredRoundedRect,
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Image(paint) => paint.as_bytes(),
Self::LinearGradient(paint) => paint.as_bytes(),
Self::RadialGradient(paint) => paint.as_bytes(),
Self::SweepGradient(paint) => paint.as_bytes(),
Self::BlurredRoundedRect(paint) => paint.as_bytes(),
}
}
#[must_use]
pub fn byte_len(&self) -> usize {
self.as_bytes().len()
}
#[must_use]
pub fn texel_len(&self) -> u32 {
(self.byte_len() as u32) >> TEXEL_BYTES_SHIFT
}
#[must_use]
pub fn serialized_len(paints: &[Self]) -> usize {
paints.iter().map(Self::byte_len).sum()
}
pub fn serialize_to_buffer(paints: &[Self], buffer: &mut [u8]) -> Result<usize, EngineError> {
let required = Self::serialized_len(paints);
if buffer.len() < required {
return Err(EngineError::AtlasError);
}
let mut offset = 0;
for paint in paints {
let bytes = paint.as_bytes();
buffer[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
Ok(offset)
}
#[must_use]
pub fn texel_offsets(paints: &[Self]) -> Vec<u32> {
let mut offset = 0;
paints
.iter()
.map(|paint| {
let start = offset;
offset += paint.texel_len();
start
})
.collect()
}
}
#[must_use]
pub fn lower_encoded_paint(
paint: &EncodedPaint,
ramp: Option<CachedRamp>,
) -> Option<GpuEncodedPaint> {
match paint {
EncodedPaint::Gradient(gradient) => Some(lower_gradient(gradient, ramp?)),
EncodedPaint::BlurredRoundedRect(entry) => Some(lower_blurred_rounded_rect(entry)),
EncodedPaint::Image(_) | EncodedPaint::ExternalTexture(_) => None,
}
}
fn lower_blurred_rounded_rect(entry: &EncodedBlurredRoundedRectangle) -> GpuEncodedPaint {
let transform = entry.transform.as_coeffs().map(|coeff| coeff as f32);
GpuEncodedPaint::BlurredRoundedRect(GpuBlurredRoundedRect {
transform,
color: entry.color.as_premul_rgba8().to_u32(),
invert: u32::from(entry.invert),
params0: [
entry.exponent,
entry.recip_exponent,
entry.scale,
entry.std_dev_inv,
],
params1: [entry.min_edge, entry.w, entry.h, entry.r1],
size: [entry.width, entry.height],
_padding1: [0, 0],
})
}
fn lower_gradient(gradient: &EncodedGradient, ramp: CachedRamp) -> GpuEncodedPaint {
let transform = gradient.transform.as_coeffs().map(|coeff| coeff as f32);
let texture_width_and_extend_mode =
pack_texture_width_and_extend_mode(ramp.width, extend_mode(gradient.extend));
let gradient_start = ramp.lut_start;
match &gradient.kind {
EncodedKind::Linear(_) => GpuEncodedPaint::LinearGradient(GpuLinearGradient {
texture_width_and_extend_mode,
gradient_start,
transform,
}),
EncodedKind::Radial(radial) => {
let shape = RadialShape::of(radial);
GpuEncodedPaint::RadialGradient(GpuRadialGradient {
texture_width_and_extend_mode,
gradient_start,
transform,
kind_and_f_is_swapped: pack_kind_and_f_is_swapped(shape.kind, shape.f_is_swapped),
bias: shape.bias,
scale: shape.scale,
fp0: shape.fp0,
fp1: shape.fp1,
fr1: shape.fr1,
f_focal_x: shape.f_focal_x,
scaled_r0_squared: shape.scaled_r0_squared,
})
}
EncodedKind::Sweep(sweep) => GpuEncodedPaint::SweepGradient(GpuSweepGradient {
texture_width_and_extend_mode,
gradient_start,
transform,
start_angle: sweep.start_angle,
inv_angle_delta: sweep.inv_angle_delta,
_padding: [0, 0],
}),
}
}
const fn extend_mode(extend: peniko::Extend) -> u32 {
match extend {
peniko::Extend::Pad => 0,
peniko::Extend::Repeat => 1,
peniko::Extend::Reflect => 2,
}
}
struct RadialShape {
kind: u32,
bias: f32,
scale: f32,
fp0: f32,
fp1: f32,
fr1: f32,
f_focal_x: f32,
f_is_swapped: bool,
scaled_r0_squared: f32,
}
impl RadialShape {
const ZERO: Self = Self {
kind: 0,
bias: 0.0,
scale: 0.0,
fp0: 0.0,
fp1: 0.0,
fr1: 0.0,
f_focal_x: 0.0,
f_is_swapped: false,
scaled_r0_squared: 0.0,
};
fn of(radial: &RadialKind) -> Self {
match radial {
RadialKind::Radial { bias, scale } => Self {
kind: 0,
bias: *bias,
scale: *scale,
..Self::ZERO
},
RadialKind::Strip { scaled_r0_squared } => Self {
kind: 1,
scaled_r0_squared: *scaled_r0_squared,
..Self::ZERO
},
RadialKind::Focal {
focal_data,
fp0,
fp1,
} => Self {
kind: 2,
bias: *fp0,
scale: *fp1,
fp0: *fp0,
fp1: *fp1,
fr1: focal_data.fr1,
f_focal_x: focal_data.f_focal_x,
f_is_swapped: focal_data.f_is_swapped,
scaled_r0_squared: 0.0,
},
}
}
}
#[must_use]
pub fn encoded_paints_texture_descriptor(
width: u32,
height: u32,
) -> wgpu::TextureDescriptor<'static> {
resource_texture_descriptor("frust-engine encoded paints texture", width, height)
}
pub fn encoded_paints_texture_height(
texels: u32,
resource_texture_dim: u32,
) -> Result<u32, EngineError> {
let required = texels.div_ceil(resource_texture_dim);
if required > resource_texture_dim {
return Err(EngineError::PaintCapacity);
}
Ok(required.max(MIN_RESOURCE_TEXTURE_HEIGHT))
}
pub fn grow_encoded_paints_texture_height(
current_height: u32,
texels: u32,
resource_texture_dim: u32,
) -> Result<Option<u32>, EngineError> {
let required = encoded_paints_texture_height(texels, resource_texture_dim)?;
Ok((required > current_height).then_some(required))
}
const EXTEND_MODE_SHIFT: u32 = 30;
const TEXTURE_WIDTH_MASK: u32 = (1 << EXTEND_MODE_SHIFT) - 1;
#[must_use]
pub fn pack_texture_width_and_extend_mode(texture_width: u32, extend_mode: u32) -> u32 {
debug_assert!(extend_mode <= 2, "extend mode must be 0, 1 or 2");
debug_assert!(
texture_width <= TEXTURE_WIDTH_MASK,
"gradient texture width {texture_width} exceeds {TEXTURE_WIDTH_MASK}",
);
(extend_mode << EXTEND_MODE_SHIFT) | (texture_width & TEXTURE_WIDTH_MASK)
}
#[must_use]
pub const fn unpack_texture_width_and_extend_mode(packed: u32) -> (u32, u32) {
(packed & TEXTURE_WIDTH_MASK, packed >> EXTEND_MODE_SHIFT)
}
#[must_use]
pub fn pack_kind_and_f_is_swapped(kind: u32, f_is_swapped: bool) -> u32 {
debug_assert!(kind <= 2, "radial gradient kind must be 0, 1 or 2");
(kind & 0b11) | (u32::from(f_is_swapped) << 2)
}