Skip to main content

unlit_wgpu/
globals.rs

1//! Uniforms the built-in pipelines bind.
2//!
3//! Three records, each advanced by whoever owns the value it carries:
4//! `Camera` by the camera a pass draws through, `Globals` by the renderer once a
5//! frame, and `View` by the pass that states the viewport. They are mirrored by
6//! the `camera.wesl`, `globals.wesl` and `view.wesl` modules of the built-in WESL
7//! package, and all three are bound as `var<uniform>`.
8
9/// Camera state: the transforms a pass draws through, and the eye they imply.
10///
11/// Mirrors `camera.wesl::Camera`; the layout is checked at compile time against
12/// the WGSL uniform address-space rules.
13///
14/// A pass binds this for the camera it draws through, so two passes through two
15/// cameras bind two records. What the pass frames — its viewport, and the pixel
16/// scale that follows from it — is [`View`] rather than part of this record.
17#[repr(C)]
18#[derive(
19    Clone,
20    Copy,
21    Debug,
22    PartialEq,
23    zerocopy_derive::FromBytes,
24    zerocopy_derive::Immutable,
25    zerocopy_derive::IntoBytes,
26    zerocopy_derive::KnownLayout,
27    const_shader_layout::ShaderLayoutCompat,
28)]
29pub struct Camera {
30    /// World -> clip matrix (`projection * view`).
31    ///
32    /// The one matrix a vertex stage needs; it is stored rather than left for
33    /// the shader to multiply so the hottest path pays a single matrix
34    /// multiply per vertex.
35    pub clip_from_world: glam::Mat4,
36    /// World -> view matrix.
37    ///
38    /// A shader that lights, fogs or projects in view space reads this; the
39    /// camera's right, up and backward axes are its first three rows.
40    pub view_from_world: glam::Mat4,
41    /// World-space eye position; `w` is padding by convention (write `1.0`).
42    pub camera_position: glam::Vec4,
43}
44
45impl Camera {
46    /// Build a camera from its view and projection transforms and its
47    /// world-space eye position.
48    ///
49    /// The combined world-to-clip matrix is derived here, so a caller only
50    /// hands over the two transforms it holds.
51    pub fn new(
52        view_from_world: glam::Mat4,
53        clip_from_view: glam::Mat4,
54        camera_position: glam::Vec3,
55    ) -> Self {
56        Self {
57            clip_from_world: clip_from_view * view_from_world,
58            view_from_world,
59            camera_position: camera_position.extend(1.0),
60        }
61    }
62
63    /// Build a camera from a world-to-clip matrix alone.
64    ///
65    /// For a caller whose matrix is synthesised and has no separate view and
66    /// projection — the UI's screen-space mapping is one — the view matrix is
67    /// left at the identity, so `view_from_world` says nothing and only
68    /// `clip_from_world` should be read.
69    pub fn from_clip_from_world(clip_from_world: glam::Mat4, camera_position: glam::Vec3) -> Self {
70        Self {
71            clip_from_world,
72            view_from_world: glam::Mat4::IDENTITY,
73            camera_position: camera_position.extend(1.0),
74        }
75    }
76}
77
78/// What one pass frames: the region of the target its clip space covers.
79///
80/// Mirrors `view.wesl::View`.
81///
82/// This is the pass's value rather than the camera's: the same camera drawn into
83/// two differently shaped rectangles is two views, and the passes of one frame
84/// may frame differently. A shader that measures in pixels — a constant-width
85/// line, a pixel-sized billboard, a screen-space projection — needs it to turn
86/// a pixel count into clip space.
87#[repr(C)]
88#[derive(
89    Clone,
90    Copy,
91    Debug,
92    PartialEq,
93    zerocopy_derive::FromBytes,
94    zerocopy_derive::Immutable,
95    zerocopy_derive::IntoBytes,
96    zerocopy_derive::KnownLayout,
97    const_shader_layout::ShaderLayoutCompat,
98)]
99pub struct View {
100    /// The region of the target this view's clip space is stretched across, in
101    /// physical pixels.
102    ///
103    /// One pixel is `2 / viewport_size` on each axis. It is the viewport rather
104    /// than the attachment's size because a letterboxed view stretches its clip
105    /// space across the smaller rectangle, so a width measured against the
106    /// attachment would shrink along with the picture.
107    ///
108    /// Zero for a caller that never set it, which no pixel measurement can
109    /// divide by; such a caller does not read it.
110    pub viewport_size: glam::Vec2,
111    /// Explicit tail padding: the WGSL struct size rounds up to 16 bytes, and a
112    /// record with implicit padding could not be written out as bytes.
113    pub pad0: glam::Vec2,
114}
115
116impl View {
117    /// The view of a pass drawing through a `width` x `height` viewport, in
118    /// physical pixels.
119    pub fn new(width: f32, height: f32) -> Self {
120        Self {
121            viewport_size: glam::Vec2::new(width, height),
122            pad0: glam::Vec2::ZERO,
123        }
124    }
125}
126
127/// Values the renderer advances once per frame.
128///
129/// Mirrors `globals.wesl::Globals`.
130#[repr(C)]
131#[derive(
132    Clone,
133    Copy,
134    Debug,
135    PartialEq,
136    zerocopy_derive::FromBytes,
137    zerocopy_derive::Immutable,
138    zerocopy_derive::IntoBytes,
139    zerocopy_derive::KnownLayout,
140    const_shader_layout::ShaderLayoutCompat,
141)]
142pub struct Globals {
143    /// Seconds elapsed since the renderer started.
144    pub time: f32,
145    /// Seconds elapsed since the previous frame.
146    pub delta_time: f32,
147    /// Monotonic frame counter.
148    pub frame_count: u32,
149    /// Explicit tail padding: the WGSL struct size rounds up to 16 bytes.
150    pub pad0: u32,
151}
152
153impl Default for Globals {
154    fn default() -> Self {
155        Self {
156            time: 0.0,
157            delta_time: 0.0,
158            frame_count: 0,
159            pad0: 0,
160        }
161    }
162}