Phosphor
Qt6 / Wayland library suite for window-management tools
 
Loading...
Searching...
No Matches
BaseUniforms.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 fuddlesworth
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#pragma once
5
6#include <cstddef>
7
8namespace PhosphorShaders {
9
11constexpr double kShaderTimeWrap = 1024.0;
12
36struct alignas(16) BaseUniforms
37{
38 // Transform and opacity from Qt scene graph (offset 0)
39 float qt_Matrix[16]; // mat4: 64 bytes at offset 0
40 float qt_Opacity; // float: 4 bytes at offset 64
41
42 // Shader timing (Shadertoy-compatible)
43 float iTime; // float: 4 bytes at offset 68 (wrapped)
44 float iTimeDelta; // float: 4 bytes at offset 72
45 int iFrame; // int: 4 bytes at offset 76
46
47 // Resolution
48 float iResolution[2]; // vec2: 8 bytes at offset 80
49
50 // Consumer-defined int slots — see class doc above for the escape-hatch
51 // design rationale. Written via ShaderNodeRhi::setAppField0/1.
52 int appField0; // offset 88
53 int appField1; // offset 92
54
55 // Mouse position
56 float iMouse[4]; // vec4: 16 bytes at offset 96
57
58 // Date/time: year, month (1-12), day (1-31), seconds since midnight
59 float iDate[4]; // vec4: 16 bytes at offset 112
60
61 // Custom shader parameters (32 float slots in 8 vec4s)
62 float customParams[8][4]; // vec4[8]: 128 bytes at offset 128
63
64 // Custom colors (16 color slots)
65 float customColors[16][4]; // vec4[16]: 256 bytes at offset 256
66
67 // Multi-pass: iChannelResolution[i] = buffer texture size
68 float iChannelResolution[4][4]; // vec4[4]: 64 bytes at offset 512
69
70 // Audio spectrum
71 int iAudioSpectrumSize; // offset 576
72 int iFlipBufferY; // offset 580 — always 1 for Y-flip
73 // The two pad ints at offsets 584 and 588 are explicitly written as zero
74 // by the C-side upload path (see BaseUniformProfile::fill in
75 // baseuniformprofile.cpp) — readers should not assume the bytes are skipped
76 // on the wire. On the
77 // GLSL side they are absorbed by std140's vec4 alignment of the following
78 // `iTextureResolution` (vec4[4]) member, which forces the next field onto
79 // a 16-byte boundary at offset 592. Removing the pad would shift the
80 // GLSL view of `iTextureResolution` and break the
81 // `data/animations/shared/animation_uniforms.glsl` byte-for-byte
82 // contract pinned by the static_asserts below.
83 int _pad_after_audioSpectrum[2]; // offset 584
84
85 // User texture resolutions (bindings 7-10)
86 float iTextureResolution[4][4]; // vec4[4]: 64 bytes at offset 592
87
88 // Wrap-offset counterpart of iTime
89 float iTimeHi; // offset 656
90
91 // Direction signal for asymmetric leg rendering. 1 when the runtime
92 // is driving this leg in the "reverse" direction (window.close /
93 // going-to-minimized / unmaximize on the kwin path; hide leg on
94 // the daemon path), 0 otherwise. Symmetric shaders ignore this and
95 // rely on the runtime's iTime flip to auto-mirror; asymmetric
96 // shaders (matrix's directional rain, rain windowAlpha trajectory,
97 // anything where open and close differ in more than time direction)
98 // branch on it. Carved out of the trailing std140 pad so total
99 // struct size stays 672 bytes.
100 int iIsReversed; // offset 660
101 // Ditto: trailing pad zeroed by value-init (BaseUniformProfile's `m_u = {}`)
102 // and covered by K_SCENE_HEADER. See the expanded `_pad_after_audioSpectrum`
103 // comment above for the full std140 / upload-region rationale.
104 int _pad_after_iIsReversed[2]; // std140 struct alignment, total 672 bytes
105};
106
107static_assert(sizeof(BaseUniforms) == 672, "BaseUniforms must be exactly 672 bytes");
108
109// Per-field std140 offset asserts. These pin the layout the
110// `data/animations/shared/animation_uniforms.glsl` canonical UBO branch
111// depends on — that GLSL declaration is a byte-for-byte std140 prefix of
112// `BaseUniforms`, which is what lets a single animation `effect.frag`
113// source produce identical visuals on both runtimes (compositor classic
114// GL via the canonical header's `#ifdef PLASMAZONES_KWIN` default-block
115// branch, daemon Qt RHI via the `binding=0` UBO upload). If anyone
116// reorders or inserts a field above any of these, the corresponding
117// assert fails at compile time and the canonical GLSL header MUST be
118// updated to match (and all in-tree `effect.frag` files re-baked, since
119// their `customParams[N]` `#define` macros encode the slot positions).
120//
121// Every field declared in `animation_uniforms.glsl` is pinned here. A
122// previous revision asserted only iTime / iResolution / customParams /
123// customColors, which left the door open to a reorder that swapped
124// {iTimeDelta, iFrame, appField0, appField1, iMouse, iDate} amongst
125// themselves while preserving the four asserted offsets — silent
126// miscompile for any future animation shader that reads the in-between
127// fields. The full coverage below catches that.
128static_assert(offsetof(BaseUniforms, qt_Matrix) == 0,
129 "BaseUniforms::qt_Matrix must remain at std140 offset 0 (animation UBO contract)");
130static_assert(offsetof(BaseUniforms, qt_Opacity) == 64,
131 "BaseUniforms::qt_Opacity must remain at std140 offset 64 (animation UBO contract)");
132static_assert(offsetof(BaseUniforms, iTime) == 68,
133 "BaseUniforms::iTime must remain at std140 offset 68 (animation UBO contract)");
134static_assert(offsetof(BaseUniforms, iTimeDelta) == 72,
135 "BaseUniforms::iTimeDelta must remain at std140 offset 72 (animation UBO contract)");
136static_assert(offsetof(BaseUniforms, iFrame) == 76,
137 "BaseUniforms::iFrame must remain at std140 offset 76 (animation UBO contract)");
138static_assert(offsetof(BaseUniforms, iResolution) == 80,
139 "BaseUniforms::iResolution must remain at std140 offset 80 (animation UBO contract)");
140static_assert(offsetof(BaseUniforms, appField0) == 88,
141 "BaseUniforms::appField0 must remain at std140 offset 88 (animation UBO contract)");
142static_assert(offsetof(BaseUniforms, appField1) == 92,
143 "BaseUniforms::appField1 must remain at std140 offset 92 (animation UBO contract)");
144static_assert(offsetof(BaseUniforms, iMouse) == 96,
145 "BaseUniforms::iMouse must remain at std140 offset 96 (animation UBO contract)");
146static_assert(offsetof(BaseUniforms, iDate) == 112,
147 "BaseUniforms::iDate must remain at std140 offset 112 (animation UBO contract)");
148static_assert(offsetof(BaseUniforms, customParams) == 128,
149 "BaseUniforms::customParams must remain at std140 offset 128 (animation UBO contract)");
150static_assert(offsetof(BaseUniforms, customColors) == 256,
151 "BaseUniforms::customColors must remain at std140 offset 256 (animation UBO contract)");
152static_assert(offsetof(BaseUniforms, iChannelResolution) == 512,
153 "BaseUniforms::iChannelResolution must remain at std140 offset 512 (animation UBO contract)");
154static_assert(offsetof(BaseUniforms, iAudioSpectrumSize) == 576,
155 "BaseUniforms::iAudioSpectrumSize must remain at std140 offset 576 (animation UBO contract)");
156static_assert(offsetof(BaseUniforms, iFlipBufferY) == 580,
157 "BaseUniforms::iFlipBufferY must remain at std140 offset 580 (animation UBO contract)");
158static_assert(offsetof(BaseUniforms, iTextureResolution) == 592,
159 "BaseUniforms::iTextureResolution must remain at std140 offset 592 (animation UBO contract)");
160static_assert(offsetof(BaseUniforms, iTimeHi) == 656,
161 "BaseUniforms::iTimeHi must remain at std140 offset 656 (animation UBO contract)");
162static_assert(offsetof(BaseUniforms, iIsReversed) == 660,
163 "BaseUniforms::iIsReversed must remain at std140 offset 660 (animation UBO contract)");
164
166namespace UboRegions {
167
168// Transform and opacity from Qt scene graph (mat4 + float)
169constexpr size_t K_MATRIX_OPACITY_OFFSET = 0;
170constexpr size_t K_MATRIX_OPACITY_SIZE = offsetof(BaseUniforms, iTime); // 68 bytes
171
172// Animation time block (iTime, iTimeDelta, iFrame)
173constexpr size_t K_TIME_BLOCK_OFFSET = offsetof(BaseUniforms, iTime);
174constexpr size_t K_TIME_BLOCK_SIZE = sizeof(float) + sizeof(float) + sizeof(int); // 12 bytes
175
176// App-fields block (appField0, appField1) — 8 bytes at offset 88.
177// Uploaded as a tiny standalone region when ONLY the consumer's escape-hatch
178// fields changed. Without this granular region, every appField update would
179// trigger a full scene-header re-upload (~592 bytes).
180constexpr size_t K_APP_FIELDS_OFFSET = offsetof(BaseUniforms, appField0);
181constexpr size_t K_APP_FIELDS_SIZE = sizeof(int) * 2;
182
183// Scene header: iResolution through end of struct.
184// Covers iResolution, appFields, iMouse, iDate, customParams, customColors,
185// iChannelResolution, iAudioSpectrumSize, iFlipBufferY, iTextureResolution,
186// iTimeHi, iIsReversed, and the trailing std140 pad. Sized to the
187// remainder of BaseUniforms so any field added at or after offset 80
188// (iResolution) is automatically covered by m_sceneDataDirty's upload
189// path without needing a per-field dirty flag.
190//
191// History: an earlier revision capped K_SCENE_HEADER_SIZE at offsetof(iTimeHi),
192// leaving the gap from offsetof(iTimeHi) + sizeof(iTimeHi) (660) to
193// sizeof(BaseUniforms) (672) un-uploaded by partial-update paths. The
194// new iIsReversed field at offset 660 landed exactly in that gap and
195// silently failed to propagate to the GPU after the first full upload —
196// asymmetric direction-aware shaders read stale values forever.
197//
198// WARNING: a new field added BEFORE iResolution (offset < 80) must
199// extend K_MATRIX_OPACITY or land in its own region; this scene-header
200// range only covers offsets [80, sizeof(BaseUniforms)).
201constexpr size_t K_SCENE_HEADER_OFFSET = offsetof(BaseUniforms, iResolution);
203
204// iTimeHi block: a granular 4-byte upload region for the time-wrap-only
205// path (m_timeHiDirty fires every ~30 s when iTime crosses the wrap
206// boundary). Subsumed by K_SCENE_HEADER, so when m_sceneDataDirty also
207// fires for the same frame the upload site below skips this granular
208// write to avoid the duplicate 4-byte transfer.
209constexpr size_t K_TIME_HI_OFFSET = offsetof(BaseUniforms, iTimeHi);
210constexpr size_t K_TIME_HI_SIZE = sizeof(float);
211
212// Verify K_SCENE_HEADER reaches end of BaseUniforms — a new field added
213// at or after offset 80 must land inside this range. Without this assert,
214// adding a field beyond the previous K_SCENE_HEADER end (the iTimeHi-
215// based cap) would silently regress the gap-bug fixed by extending the
216// region. Pin via the actual LAST field's offset+size so a developer
217// who manually narrows K_SCENE_HEADER_SIZE (e.g. `= offsetof(iTimeHi) -
218// K_SCENE_HEADER_OFFSET`, which is exactly the original regression)
219// fails to build. The earlier formulation
220// `K_SCENE_HEADER_OFFSET + K_SCENE_HEADER_SIZE == sizeof(BaseUniforms)`
221// was tautological because K_SCENE_HEADER_SIZE is itself defined as
222// `sizeof(BaseUniforms) - K_SCENE_HEADER_OFFSET` — the assert reduced
223// to `sizeof == sizeof` and could not catch the regression it claimed
224// to defend.
225static_assert(offsetof(BaseUniforms, _pad_after_iIsReversed) + sizeof(BaseUniforms::_pad_after_iIsReversed)
227 "K_SCENE_HEADER must cover the trailing _pad_after_iIsReversed bytes — "
228 "narrowing K_SCENE_HEADER_SIZE leaves the iIsReversed gap unmapped");
230 "K_SCENE_HEADER must reach end-of-BaseUniforms — defensive companion to "
231 "the trailing-field assert above");
232// Verify K_TIME_HI is fully nested inside K_SCENE_HEADER so the upload
233// site can skip the granular write when both dirty flags fire.
236 "K_TIME_HI must be subsumed by K_SCENE_HEADER so a scene-data upload "
237 "covers iTimeHi too without needing the m_timeHiDirty granular path");
238// Verify K_APP_FIELDS is fully nested inside K_SCENE_HEADER for the
239// same reason: the upload site uses an `else if` to skip the granular
240// app-fields write when scene-data is also dirty (the broader upload
241// already covers it). Pinning the nesting at compile time makes a
242// future field-shuffle that breaks containment a build failure rather
243// than a silent missed-upload regression.
246 "K_APP_FIELDS must be subsumed by K_SCENE_HEADER so the scene-data upload "
247 "covers appField0/appField1 too without needing the m_appFieldsDirty granular path");
248
249// Total base size (for extension offset calculation)
250constexpr size_t K_BASE_SIZE = sizeof(BaseUniforms);
251
252} // namespace UboRegions
253
254} // namespace PhosphorShaders
constexpr size_t K_TIME_HI_OFFSET
Definition BaseUniforms.h:209
constexpr size_t K_TIME_HI_SIZE
Definition BaseUniforms.h:210
constexpr size_t K_TIME_BLOCK_SIZE
Definition BaseUniforms.h:174
constexpr size_t K_MATRIX_OPACITY_SIZE
Definition BaseUniforms.h:170
constexpr size_t K_TIME_BLOCK_OFFSET
Definition BaseUniforms.h:173
constexpr size_t K_APP_FIELDS_OFFSET
Definition BaseUniforms.h:180
constexpr size_t K_MATRIX_OPACITY_OFFSET
Definition BaseUniforms.h:169
constexpr size_t K_SCENE_HEADER_SIZE
Definition BaseUniforms.h:202
constexpr size_t K_APP_FIELDS_SIZE
Definition BaseUniforms.h:181
constexpr size_t K_SCENE_HEADER_OFFSET
Definition BaseUniforms.h:201
constexpr size_t K_BASE_SIZE
Definition BaseUniforms.h:250
Definition ShaderEffect.h:33
constexpr double kShaderTimeWrap
Time wrap period for float32 precision preservation.
Definition BaseUniforms.h:11
GPU uniform buffer layout following std140 rules (base region).
Definition BaseUniforms.h:37
int iAudioSpectrumSize
Definition BaseUniforms.h:71
float iChannelResolution[4][4]
Definition BaseUniforms.h:68
float iTime
Definition BaseUniforms.h:43
int _pad_after_iIsReversed[2]
Definition BaseUniforms.h:104
float iResolution[2]
Definition BaseUniforms.h:48
float iTimeDelta
Definition BaseUniforms.h:44
int _pad_after_audioSpectrum[2]
Definition BaseUniforms.h:83
float iTextureResolution[4][4]
Definition BaseUniforms.h:86
int appField1
Definition BaseUniforms.h:53
int iFrame
Definition BaseUniforms.h:45
int iIsReversed
Definition BaseUniforms.h:100
float iTimeHi
Definition BaseUniforms.h:89
float qt_Opacity
Definition BaseUniforms.h:40
float customColors[16][4]
Definition BaseUniforms.h:65
int appField0
Definition BaseUniforms.h:52
float iDate[4]
Definition BaseUniforms.h:59
int iFlipBufferY
Definition BaseUniforms.h:72
float qt_Matrix[16]
Definition BaseUniforms.h:39
float customParams[8][4]
Definition BaseUniforms.h:62
float iMouse[4]
Definition BaseUniforms.h:56