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}