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