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