use bytemuck::{Pod, Zeroable};
use vello_common::tile::Tile;
#[repr(C, align(16))]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct GpuConfig {
pub width: u32,
pub height: u32,
pub strip_height: u32,
pub alphas_tex_width_bits: u32,
pub encoded_paints_tex_width_bits: u32,
pub strip_offset_x: i32,
pub strip_offset_y: i32,
pub negate_ndc: u32,
}
const _: () = assert!(
size_of::<GpuConfig>() == 32,
"`GpuConfig` must stay 32 bytes — the shaders' `Config` uniform layout",
);
const _: () = assert!(
align_of::<GpuConfig>() == 16,
"`GpuConfig` must stay 16-byte aligned — uniform-buffer layout rules",
);
impl GpuConfig {
pub const SIZE: u64 = size_of::<Self>() as u64;
#[must_use]
pub fn new(
width: u32,
height: u32,
alphas_tex_width: u32,
encoded_paints_tex_width: u32,
) -> Self {
Self {
width,
height,
strip_height: u32::from(Tile::HEIGHT),
alphas_tex_width_bits: tex_width_bits(alphas_tex_width),
encoded_paints_tex_width_bits: tex_width_bits(encoded_paints_tex_width),
strip_offset_x: 0,
strip_offset_y: 0,
negate_ndc: 0,
}
}
#[must_use]
pub const fn with_strip_offset(mut self, x: i32, y: i32) -> Self {
self.strip_offset_x = x;
self.strip_offset_y = y;
self
}
#[must_use]
pub const fn with_negate_ndc(mut self, negate: bool) -> Self {
self.negate_ndc = negate as u32;
self
}
}
#[must_use]
pub fn resource_widths_agree(dim: u32, alphas: u32, paints: u32, lut: u32) -> bool {
dim.is_power_of_two() && alphas == dim && paints == dim && lut == dim
}
#[must_use]
pub fn tex_width_bits(width: u32) -> u32 {
debug_assert!(
width.is_power_of_two(),
"resource texture width must be a power of two, got {width}",
);
width.trailing_zeros()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_lut_and_paint_textures_share_one_width_shift() {
let config = GpuConfig::new(800, 600, 2048, 2048);
let bits = config.encoded_paints_tex_width_bits;
let flat = 2048 * 3 + 17;
assert_eq!((flat & ((1 << bits) - 1), flat >> bits), (17, 3));
assert!(resource_widths_agree(2048, 2048, 2048, 2048));
}
#[test]
fn a_resource_texture_of_another_width_breaks_the_guard() {
assert!(!resource_widths_agree(2048, 1024, 2048, 2048));
assert!(!resource_widths_agree(2048, 2048, 4096, 2048));
assert!(!resource_widths_agree(2048, 2048, 2048, 1024));
assert!(!resource_widths_agree(1000, 1000, 1000, 1000));
}
#[test]
fn shift_and_mask_addressing_equals_division_for_power_of_two_widths() {
for bits in 0..=13_u32 {
let width = 1_u32 << bits;
for index in (0..width * 5).step_by(((width / 7) as usize).max(1)) {
let mask = (1_u32 << bits) - 1;
assert_eq!(
(index & mask, index >> bits),
(index % width, index / width)
);
}
}
}
}