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}