Skip to main content

frust_engine/gpu/
config.rs

1//! The `Config` uniform every strip shader binds.
2//!
3//! Named [`GpuConfig`] rather than `Config` because the crate root already
4//! owns a `config` module of process-wide kill switches ([`crate::config`]);
5//! this one is the GPU-side uniform buffer's layout and nothing else.
6//!
7//! Field order and size are part of the shader contract — see the module
8//! header of [`super`].
9
10use bytemuck::{Pod, Zeroable};
11use vello_common::tile::Tile;
12
13/// The uniform block the strip shaders read once per draw.
14///
15/// Laid out to match the reference renderer's `Config` byte for byte: eight
16/// 4-byte scalars in a 16-byte-aligned block.
17#[repr(C, align(16))]
18#[derive(Debug, Clone, Copy, Pod, Zeroable)]
19pub struct GpuConfig {
20    /// Width of the render target in pixels.
21    pub width: u32,
22    /// Height of the render target in pixels.
23    pub height: u32,
24    /// Height of one strip in pixels — always `Tile::HEIGHT`.
25    pub strip_height: u32,
26    /// `log2` of the alpha texture's width in texels.
27    ///
28    /// Pre-computed on the CPU because a downlevel (GLES 3.0 / WebGL2)
29    /// target has no `firstTrailingBit`; the shader shifts by this instead of
30    /// dividing.
31    pub alphas_tex_width_bits: u32,
32    /// `log2` of the encoded-paint texture's width in texels, pre-computed
33    /// for the same reason as [`Self::alphas_tex_width_bits`].
34    pub encoded_paints_tex_width_bits: u32,
35    /// Horizontal offset applied to every strip, in pixels.
36    pub strip_offset_x: i32,
37    /// Vertical offset applied to every strip, in pixels.
38    pub strip_offset_y: i32,
39    /// Whether the shader negates the y component of its NDC position
40    /// (non-zero) or leaves it alone (zero).
41    ///
42    /// Kept for a future GLSL/WebGL backend: transpiling WGSL to GLSL applies
43    /// a y-flip to reconcile WebGPU's y-down clip space with a WebGL
44    /// framebuffer's y-up one. Rendering straight into a caller-provided
45    /// framebuffer wants that flip undone rather than a second framebuffer
46    /// allocated and blitted, and the rest of the strip pipeline (slot
47    /// textures included) assumes y-down throughout. Every backend this crate
48    /// drives today leaves it zero.
49    pub negate_ndc: u32,
50}
51
52const _: () = assert!(
53    size_of::<GpuConfig>() == 32,
54    "`GpuConfig` must stay 32 bytes — the shaders' `Config` uniform layout",
55);
56const _: () = assert!(
57    align_of::<GpuConfig>() == 16,
58    "`GpuConfig` must stay 16-byte aligned — uniform-buffer layout rules",
59);
60
61impl GpuConfig {
62    /// The uniform's size in bytes, for a buffer binding's `min_binding_size`.
63    pub const SIZE: u64 = size_of::<Self>() as u64;
64
65    /// A config for a `width` x `height` target sampling resource textures
66    /// `alphas_tex_width` and `encoded_paints_tex_width` texels wide, with no
67    /// strip offset and no NDC negation.
68    ///
69    /// Both texture widths must be powers of two: the shader reconstructs
70    /// them as `1 << bits` (see [`tex_width_bits`]).
71    #[must_use]
72    pub fn new(
73        width: u32,
74        height: u32,
75        alphas_tex_width: u32,
76        encoded_paints_tex_width: u32,
77    ) -> Self {
78        Self {
79            width,
80            height,
81            strip_height: u32::from(Tile::HEIGHT),
82            alphas_tex_width_bits: tex_width_bits(alphas_tex_width),
83            encoded_paints_tex_width_bits: tex_width_bits(encoded_paints_tex_width),
84            strip_offset_x: 0,
85            strip_offset_y: 0,
86            negate_ndc: 0,
87        }
88    }
89
90    /// The same config with strips offset by `(x, y)` pixels.
91    #[must_use]
92    pub const fn with_strip_offset(mut self, x: i32, y: i32) -> Self {
93        self.strip_offset_x = x;
94        self.strip_offset_y = y;
95        self
96    }
97
98    /// The same config with NDC y negation switched on or off
99    /// ([`Self::negate_ndc`]).
100    #[must_use]
101    pub const fn with_negate_ndc(mut self, negate: bool) -> Self {
102        self.negate_ndc = negate as u32;
103        self
104    }
105}
106
107/// `log2` of a resource texture's width in texels.
108///
109/// `width` must be a power of two — every resource texture is sized from
110/// `frust_gpu::TierCaps::resource_texture_dim`, which is. A width that is not
111/// makes the shader's `1 << bits` reconstruction disagree with the real
112/// texture, so the precondition is checked in debug builds; release builds
113/// take the trailing-zero count as-is rather than panicking on a frame path.
114#[must_use]
115pub fn tex_width_bits(width: u32) -> u32 {
116    debug_assert!(
117        width.is_power_of_two(),
118        "resource texture width must be a power of two, got {width}",
119    );
120    width.trailing_zeros()
121}