Loading...
Searching...
No Matches
SSBO.hpp
1#pragma once
2
3#include <limits>
4
5#include <isf.hpp>
6
7namespace score::gfx
8{
10{
11 int baseSize{}; // Size of the type itself (e.g., 12 for vec3)
12 int baseAlignment{}; // Alignment requirement (e.g., 16 for vec3)
13
14 bool isValid() const { return baseSize > 0 && baseAlignment > 0; }
15};
16
18{
19 int size{}; // The total size (stride) of the struct/type including padding
20 int alignment{}; // The alignment requirement of the struct/type
21
22 bool isValid() const { return size > 0 && alignment > 0; }
23};
24
26{
27 QString baseType;
28 int arrayCount{}; // 0 = not an array, -1 = flexible array [], >0 = fixed array [N]
29};
30
31static constexpr inline int64_t alignUp(int64_t value, int64_t alignment)
32{
33 if(alignment <= 0)
34 return value;
35 return (value + alignment - 1) & ~(alignment - 1);
36}
37
38static inline ArrayParseResult parseArrayType(const QString& typeStr)
39{
40 ArrayParseResult result;
41 result.baseType = typeStr;
42 result.arrayCount = 0; // Not an array
43
44 int bracketStart = typeStr.lastIndexOf('[');
45 int bracketEnd = typeStr.lastIndexOf(']');
46
47 if(bracketStart != -1 && bracketEnd > bracketStart)
48 {
49 QString content
50 = typeStr.mid(bracketStart + 1, bracketEnd - bracketStart - 1).trimmed();
51 result.baseType = typeStr.left(bracketStart).trimmed();
52
53 if(content.isEmpty())
54 {
55 // Flexible array: "type[]"
56 result.arrayCount = -1;
57 }
58 else
59 {
60 // Fixed array: "type[N]"
61 bool ok = false;
62 int count = content.toInt(&ok);
63 result.arrayCount = ok ? count : 1;
64 }
65 }
66
67 return result;
68}
69
70static inline Std430TypeInfo getStd430BaseTypeInfo(const QString& typeStr)
71{
72 if(typeStr == "float" || typeStr == "int" || typeStr == "uint" || typeStr == "bool")
73 return {4, 4};
74 if(typeStr == "double")
75 return {8, 8};
76
77 if(typeStr == "vec2" || typeStr == "ivec2" || typeStr == "uvec2" || typeStr == "bvec2")
78 return {8, 8};
79 if(typeStr == "vec3" || typeStr == "ivec3" || typeStr == "uvec3" || typeStr == "bvec3")
80 return {12, 16};
81 if(typeStr == "vec4" || typeStr == "ivec4" || typeStr == "uvec4" || typeStr == "bvec4")
82 return {16, 16};
83
84 if(typeStr == "dvec2")
85 return {16, 16};
86 if(typeStr == "dvec3")
87 return {24, 32};
88 if(typeStr == "dvec4")
89 return {32, 32};
90
91 // mat2: 2 columns of vec2, stride=8, total=16
92 if(typeStr == "mat2" || typeStr == "mat2x2")
93 return {16, 8};
94 // mat3: 3 columns of vec3, stride=16 (vec3 aligns to 16), total=48
95 if(typeStr == "mat3" || typeStr == "mat3x3")
96 return {48, 16};
97 // mat4: 4 columns of vec4, stride=16, total=64
98 if(typeStr == "mat4" || typeStr == "mat4x4")
99 return {64, 16};
100
101 // mat2x3: 2 columns of vec3, stride=16, total=32
102 if(typeStr == "mat2x3")
103 return {32, 16};
104 // mat2x4: 2 columns of vec4, stride=16, total=32
105 if(typeStr == "mat2x4")
106 return {32, 16};
107 // mat3x2: 3 columns of vec2, stride=8, total=24
108 if(typeStr == "mat3x2")
109 return {24, 8};
110 // mat3x4: 3 columns of vec4, stride=16, total=48
111 if(typeStr == "mat3x4")
112 return {48, 16};
113 // mat4x2: 4 columns of vec2, stride=8, total=32
114 if(typeStr == "mat4x2")
115 return {32, 8};
116 // mat4x3: 4 columns of vec3, stride=16, total=64
117 if(typeStr == "mat4x3")
118 return {64, 16};
119
120 // dmat2: 2 columns of dvec2, stride=16, total=32
121 if(typeStr == "dmat2" || typeStr == "dmat2x2")
122 return {32, 16};
123 // dmat3: 3 columns of dvec3, stride=32 (dvec3 aligns to 32), total=96
124 if(typeStr == "dmat3" || typeStr == "dmat3x3")
125 return {96, 32};
126 // dmat4: 4 columns of dvec4, stride=32, total=128
127 if(typeStr == "dmat4" || typeStr == "dmat4x4")
128 return {128, 32};
129
130 if(typeStr == "dmat2x3")
131 return {64, 32}; // 2 columns of dvec3
132 if(typeStr == "dmat2x4")
133 return {64, 32}; // 2 columns of dvec4
134 if(typeStr == "dmat3x2")
135 return {48, 16}; // 3 columns of dvec2
136 if(typeStr == "dmat3x4")
137 return {96, 32}; // 3 columns of dvec4
138 if(typeStr == "dmat4x2")
139 return {64, 16}; // 4 columns of dvec2
140 if(typeStr == "dmat4x3")
141 return {128, 32}; // 4 columns of dvec3
142
143 // Unknown type
144 return {0, 0};
145}
146
147static inline LayoutResult calculateStructLayout(
148 std::span<const isf::storage_input::layout_field> layout,
149 std::span<const isf::descriptor::type_definition> typeDefinitions)
150{
151 if(layout.empty())
152 return {0, 0};
153
154 // Accumulated in 64 bits although LayoutResult::size is an int: a layout that
155 // does not fit in an int is reported as invalid by the check below rather
156 // than wrapped into a plausible-looking value.
157 int64_t currentOffset = 0;
158 int maxAlignment = 0;
159
160 for(const auto& field : layout)
161 {
162 const ArrayParseResult parsed = parseArrayType(QString::fromStdString(field.type));
163 const QString baseType = parsed.baseType;
164
165 const bool isArray = (parsed.arrayCount != 0);
166 const bool isFlexibleArray = (parsed.arrayCount == -1);
167 const int arrayCount = (parsed.arrayCount > 0) ? parsed.arrayCount : 1;
168
169 if(isFlexibleArray)
170 {
171 qWarning() << "Flexible array found inside struct. Invalid GLSL, skipping:"
172 << QString::fromStdString(field.name);
173 continue;
174 }
175
176 int fieldSize = 0;
177 int fieldAlign = 0;
178
179 const Std430TypeInfo info = getStd430BaseTypeInfo(baseType);
180
181 if(info.isValid())
182 {
183 // Primitive or matrix type
184 fieldSize = info.baseSize;
185 fieldAlign = info.baseAlignment;
186 }
187 else
188 {
189 // Custom struct - search type definitions
190 bool found = false;
191 for(const auto& typeDef : typeDefinitions)
192 {
193 if(QString::fromStdString(typeDef.name) == baseType)
194 {
195 LayoutResult subStruct
196 = calculateStructLayout(typeDef.layout, typeDefinitions);
197 fieldSize = subStruct.size;
198 fieldAlign = subStruct.alignment;
199 found = true;
200 break;
201 }
202 }
203 if(!found)
204 {
205 qWarning() << "Unknown type, using fallback alignment:" << baseType;
206 fieldSize = 16;
207 fieldAlign = 16;
208 }
209 }
210
211 // --- Handle Array ---
212 int totalFieldSize = fieldSize;
213 if(isArray && arrayCount > 0)
214 {
215 // std430: Array element stride = element size rounded up to element alignment
216 int elementStride = alignUp(fieldSize, fieldAlign);
217 totalFieldSize = elementStride * arrayCount;
218 }
219
220 currentOffset = alignUp(currentOffset, fieldAlign);
221 currentOffset += totalFieldSize;
222 maxAlignment = std::max(maxAlignment, fieldAlign);
223 }
224
225 // Struct size must be a multiple of its largest member alignment
226 currentOffset = alignUp(currentOffset, maxAlignment);
227
228 // A layout too large for LayoutResult::size is reported as invalid rather
229 // than truncated; isValid() rejects {0, 0}.
230 if(currentOffset > (int64_t)std::numeric_limits<int>::max())
231 return {0, 0};
232 return {(int)currentOffset, maxAlignment};
233}
234
235// --- std140 (uniform block) layout ---------------------------------------
236//
237// OpenGL 4.6 core profile, 7.6.2.2 "Standard Uniform Block Layout": std140
238// differs from std430 in exactly two rules.
239//
240// (4) "If the member is an array of scalars or vectors, the base alignment
241// and array stride are set to match the base alignment of a single
242// array element, according to rules (1), (2), and (3), and rounded up
243// to the base alignment of a vec4."
244// (9) "If the member is a structure, the base alignment of the structure is
245// N, where N is the largest base alignment value of any of its members,
246// and rounded up to the base alignment of a vec4."
247//
248// and, via rules (5) and (7), a matrix is laid out as an array of column
249// vectors, so its columns get the same vec4 rounding.
250static constexpr int kStd140Vec4Alignment = 16;
251
253{
254 int columns{};
255 QString columnType;
256
257 bool isValid() const { return columns > 0; }
258};
259
260// "mat3x2" -> 3 columns of vec2; "mat3" -> 3 columns of vec3;
261// "dmat4x3" -> 4 columns of dvec3. Anything else -> invalid.
262static inline MatrixShape parseMatrixShape(const QString& typeStr)
263{
264 const bool isDouble = typeStr.startsWith("dmat");
265 if(!isDouble && !typeStr.startsWith("mat"))
266 return {};
267
268 const QString dims = typeStr.mid(isDouble ? 4 : 3);
269 if(dims.isEmpty())
270 return {};
271
272 int columns = 0, rows = 0;
273 const int x = dims.indexOf('x');
274 bool ok = false;
275 if(x < 0)
276 {
277 columns = rows = dims.toInt(&ok);
278 }
279 else
280 {
281 columns = dims.left(x).toInt(&ok);
282 bool ok2 = false;
283 rows = dims.mid(x + 1).toInt(&ok2);
284 ok = ok && ok2;
285 }
286 if(!ok || columns < 2 || columns > 4 || rows < 2 || rows > 4)
287 return {};
288
289 return {columns, (isDouble ? QString("dvec") : QString("vec")) + QString::number(rows)};
290}
291
292static inline Std430TypeInfo getStd140BaseTypeInfo(const QString& typeStr)
293{
294 if(const MatrixShape m = parseMatrixShape(typeStr); m.isValid())
295 {
296 const Std430TypeInfo col = getStd430BaseTypeInfo(m.columnType);
297 if(!col.isValid())
298 return {};
299 const int align = (int)std::max<int64_t>(col.baseAlignment, kStd140Vec4Alignment);
300 const int stride = (int)alignUp(col.baseSize, align);
301 return {stride * m.columns, align};
302 }
303
304 // Scalars and vectors follow the same rules (1)-(3) in std140 and std430.
305 return getStd430BaseTypeInfo(typeStr);
306}
307
308static inline LayoutResult calculateStructLayout140(
309 std::span<const isf::storage_input::layout_field> layout,
310 std::span<const isf::descriptor::type_definition> typeDefinitions);
311
312// Size + alignment of one std140 member, before any array multiplication.
313static inline LayoutResult std140MemberInfo(
314 const QString& baseType,
315 std::span<const isf::descriptor::type_definition> typeDefinitions)
316{
317 if(const Std430TypeInfo info = getStd140BaseTypeInfo(baseType); info.isValid())
318 return {info.baseSize, info.baseAlignment};
319
320 for(const auto& typeDef : typeDefinitions)
321 {
322 if(QString::fromStdString(typeDef.name) == baseType)
323 return calculateStructLayout140(typeDef.layout, typeDefinitions);
324 }
325
326 qWarning() << "Unknown type in uniform block layout:" << baseType;
327 return {kStd140Vec4Alignment, kStd140Vec4Alignment};
328}
329
330static inline LayoutResult calculateStructLayout140(
331 std::span<const isf::storage_input::layout_field> layout,
332 std::span<const isf::descriptor::type_definition> typeDefinitions)
333{
334 if(layout.empty())
335 return {0, 0};
336
337 int64_t currentOffset = 0;
338 int64_t maxAlignment = kStd140Vec4Alignment; // rule (9)
339
340 for(const auto& field : layout)
341 {
342 const ArrayParseResult parsed = parseArrayType(QString::fromStdString(field.type));
343 if(parsed.arrayCount == -1)
344 {
345 qWarning() << "Flexible array found inside std140 struct. Invalid GLSL, skipping:"
346 << QString::fromStdString(field.name);
347 continue;
348 }
349
350 const LayoutResult member = std140MemberInfo(parsed.baseType, typeDefinitions);
351 int64_t fieldAlign = member.alignment;
352 int64_t total = member.size;
353
354 if(parsed.arrayCount > 0)
355 {
356 fieldAlign = std::max<int64_t>(fieldAlign, kStd140Vec4Alignment); // rule (4)
357 total = alignUp(member.size, fieldAlign) * parsed.arrayCount;
358 }
359
360 currentOffset = alignUp(currentOffset, fieldAlign);
361 currentOffset += total;
362 maxAlignment = std::max(maxAlignment, fieldAlign);
363 }
364
365 return {(int)alignUp(currentOffset, maxAlignment), (int)maxAlignment};
366}
367
379template <typename Layout>
380static inline int64_t
381calculateUniformBlockSize(const Layout& layout, int arrayCount, const isf::descriptor& d)
382{
383 if(std::empty(layout))
384 return 0;
385
386 if(arrayCount < 0)
387 arrayCount = 0;
388
389 const auto& typeDefinitions = d.types;
390
391 int64_t currentOffset = 0;
392 int64_t maxBufferAlignment = kStd140Vec4Alignment;
393
394 for(const auto& field : layout)
395 {
396 const ArrayParseResult parsed = parseArrayType(QString::fromStdString(field.type));
397 const LayoutResult member = std140MemberInfo(parsed.baseType, typeDefinitions);
398
399 const bool isFlexibleArray = (parsed.arrayCount == -1);
400 const bool isFixedArray = (parsed.arrayCount > 0);
401
402 int64_t fieldAlign = member.alignment;
403 if(isFlexibleArray || isFixedArray)
404 fieldAlign = std::max<int64_t>(fieldAlign, kStd140Vec4Alignment); // rule (4)
405
406 const int64_t elementStride = alignUp(member.size, fieldAlign);
407
408 currentOffset = alignUp(currentOffset, fieldAlign);
409 if(isFlexibleArray)
410 currentOffset += elementStride * arrayCount;
411 else if(isFixedArray)
412 currentOffset += elementStride * parsed.arrayCount;
413 else
414 currentOffset += member.size;
415
416 maxBufferAlignment = std::max(maxBufferAlignment, fieldAlign);
417 }
418
419 return alignUp(currentOffset, maxBufferAlignment);
420}
421
422static inline int64_t calculateStorageBufferSize(
423 std::span<const isf::storage_input::layout_field> layout, int arrayCount,
424 const isf::descriptor& d)
425{
426 if(layout.empty())
427 return 0;
428
429 if(arrayCount < 0)
430 arrayCount = 0;
431
432 // Get type definitions from the node descriptor
433 const auto& typeDefinitions = d.types;
434
435 int64_t currentOffset = 0;
436 int64_t maxBufferAlignment = 0;
437
438 for(const auto& field : layout)
439 {
440 const ArrayParseResult parsed = parseArrayType(QString::fromStdString(field.type));
441 const QString baseType = parsed.baseType;
442
443 const bool isFlexibleArray = (parsed.arrayCount == -1);
444 const bool isFixedArray = (parsed.arrayCount > 0);
445 const int fixedArrayCount = isFixedArray ? parsed.arrayCount : 1;
446
447 int fieldSize = 0;
448 int64_t fieldAlign = 0;
449
450 const Std430TypeInfo info = getStd430BaseTypeInfo(baseType);
451
452 if(info.isValid())
453 {
454 // Primitive or matrix type
455 fieldSize = info.baseSize;
456 fieldAlign = info.baseAlignment;
457 }
458 else
459 {
460 // Custom struct
461 bool found = false;
462 for(const auto& typeDef : typeDefinitions)
463 {
464 if(QString::fromStdString(typeDef.name) == baseType)
465 {
466 const LayoutResult subRes
467 = calculateStructLayout(typeDef.layout, typeDefinitions);
468 fieldSize = subRes.size;
469 fieldAlign = subRes.alignment;
470 found = true;
471 break;
472 }
473 }
474 if(!found)
475 {
476 qWarning() << "Unknown type in buffer layout:" << baseType;
477 fieldSize = 16;
478 fieldAlign = 16;
479 }
480 }
481
482 // The multiplication itself must be 64-bit: `int stride * int count` wraps
483 // before it is widened -- a 16-byte element with 134217728 entries is
484 // exactly 2^31, which would report a negative buffer size.
485 const int64_t elementStride = alignUp(fieldSize, fieldAlign);
486 currentOffset = alignUp(currentOffset, fieldAlign);
487 if(isFlexibleArray)
488 {
489 // Variable-length array: use provided arrayCount
490 currentOffset += elementStride * (int64_t)arrayCount;
491 }
492 else if(isFixedArray)
493 {
494 // Fixed-length array: use parsed count
495 currentOffset += elementStride * (int64_t)fixedArrayCount;
496 }
497 else
498 {
499 // Single field (not an array)
500 currentOffset += fieldSize;
501 }
502
503 maxBufferAlignment = std::max(maxBufferAlignment, fieldAlign);
504 }
505
506 currentOffset = alignUp(currentOffset, maxBufferAlignment);
507
508 return currentOffset;
509}
510}
constexpr std::size_t alignUp(std::size_t v, std::size_t a) noexcept
Definition VideoPixelFormat.hpp:286
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
Definition SSBO.hpp:26
Definition SSBO.hpp:18
Definition SSBO.hpp:253
Definition SSBO.hpp:10