Loading...
Searching...
No Matches
GeometryPacker.hpp
1#pragma once
2#include <Threedim/GeometryToBufferStrategies.hpp>
3#include <halp/buffer.hpp>
4#include <halp/meta.hpp>
5
6#include <array>
7
8namespace score::gfx
9{
10class RenderList;
11struct Edge;
12}
13
14namespace Threedim
15{
17{
18 halp::attribute_location location{};
19 bool pad_to_vec4{false};
20};
21
23{
24 int32_t src_buffer_index{-1}; // Index into mesh.buffers
25 int32_t src_stride{};
26 int32_t src_offset{}; // attribute offset + input offset
27 int32_t element_count{};
28 int32_t output_components{}; // element_count or 4 if padded
29 int32_t output_offset{}; // Offset in output stride
30 bool is_float{true};
31 bool is_active{false};
32};
33
35{
36 static constexpr int MAX_PACKED_ATTRIBUTES = 8;
37
38public:
39 bool init(
40 const score::gfx::RenderState& renderState, QRhi& rhi,
41 const halp::dynamic_gpu_geometry& mesh,
42 std::span<const packed_attribute_spec> specs)
43 {
44 if(specs.empty() || specs.size() > MAX_PACKED_ATTRIBUTES)
45 {
46 qDebug() << "PackedExtractionStrategy: Invalid attribute count:" << specs.size();
47 return false;
48 }
49
50 m_vertexCount = mesh.vertices;
51 m_hasIndexBuffer = mesh.index.buffer >= 0;
52 m_attributeCount = 0;
53 m_outputStride = 0;
54
55 // Gather unique source buffers
56 m_srcBufferCount = 0;
57 std::fill(std::begin(m_srcBufferMapping), std::end(m_srcBufferMapping), -1);
58
59 // Process each requested attribute
60 for(const auto& spec : specs)
61 {
62 auto lookup = findAttribute(mesh, spec.location);
63 if(!lookup)
64 {
65 qDebug() << "PackedExtractionStrategy: Attribute not found:"
66 << magic_enum::enum_name(spec.location);
67 continue;
68 }
69
70 auto& info = m_attributes[m_attributeCount];
71 info.is_active = true;
72 info.src_buffer_index = lookup->input->buffer;
73 info.src_stride = lookup->binding->stride;
74 info.src_offset = lookup->attribute->byte_offset
75 + static_cast<int32_t>(lookup->input->byte_offset);
76 info.element_count = attributeFormatComponents(lookup->attribute->format);
77 info.is_float = isFloatFormat(lookup->attribute->format);
78 info.output_components
79 = (spec.pad_to_vec4 && info.element_count < 4 && info.is_float)
80 ? 4
81 : info.element_count;
82 info.output_offset = m_outputStride;
83
84 m_outputStride += info.output_components * sizeof(float);
85
86 // Track unique source buffers
87 int mappedIndex = m_srcBufferMapping[info.src_buffer_index];
88 if(mappedIndex < 0)
89 {
90 if(m_srcBufferCount >= MAX_PACKED_ATTRIBUTES)
91 {
92 qDebug() << "PackedExtractionStrategy: Too many source buffers";
93 return false;
94 }
95 m_srcBufferMapping[info.src_buffer_index] = m_srcBufferCount;
96 m_srcBuffers[m_srcBufferCount]
97 = static_cast<QRhiBuffer*>(mesh.buffers[info.src_buffer_index].handle);
98 ++m_srcBufferCount;
99 }
100
101 ++m_attributeCount;
102 }
103
104 if(m_attributeCount == 0)
105 {
106 qDebug() << "PackedExtractionStrategy: No valid attributes found";
107 return false;
108 }
109
110 // Handle index buffer
111 if(m_hasIndexBuffer)
112 {
113 if(mesh.index.buffer < 0
114 || mesh.index.buffer >= static_cast<int>(mesh.buffers.size()))
115 {
116 qDebug() << "PackedExtractionStrategy: Invalid index buffer";
117 return false;
118 }
119 m_indexBuffer = static_cast<QRhiBuffer*>(mesh.buffers[mesh.index.buffer].handle);
120 m_indexOffset = static_cast<int32_t>(mesh.index.byte_offset);
121 m_indexFormat32 = (mesh.index.format == halp::index_format::uint32);
122 }
123
124 m_outputSize = static_cast<int64_t>(m_vertexCount) * m_outputStride;
125
126 if(m_outputSize == 0)
127 {
128 qDebug() << "PackedExtractionStrategy: Zero output size";
129 return false;
130 }
131
132 // Create output buffer
133 m_outputBuffer = rhi.newBuffer(
134 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
135 static_cast<quint32>(m_outputSize));
136 m_outputBuffer->setName("GeometryPacker::m_outputBuffer");
137
138 if(!m_outputBuffer || !m_outputBuffer->create())
139 {
140 qDebug() << "PackedExtractionStrategy: Failed to create output buffer";
141 return false;
142 }
143
144 return createPipeline(renderState, rhi);
145 }
146
147 void update(
148 QRhi& rhi, const halp::dynamic_gpu_geometry& mesh,
149 std::span<const packed_attribute_spec> specs)
150 {
151 // Check if vertex count changed
152 const int64_t newSize = static_cast<int64_t>(mesh.vertices) * m_outputStride;
153
154 if(newSize != m_outputSize || mesh.vertices != m_vertexCount)
155 {
156 m_vertexCount = mesh.vertices;
157 m_outputSize = newSize;
158
159 if(m_outputSize > 0)
160 {
161 m_outputBuffer->setSize(static_cast<quint32>(m_outputSize));
162 m_outputBuffer->create();
163 }
164 }
165
166 // Update source buffer handles (they may have been recreated)
167 std::fill(std::begin(m_srcBufferMapping), std::end(m_srcBufferMapping), -1);
168 m_srcBufferCount = 0;
169
170 int attrIdx = 0;
171 for(const auto& spec : specs)
172 {
173 if(attrIdx >= m_attributeCount)
174 break;
175
176 auto lookup = findAttribute(mesh, spec.location);
177 if(!lookup)
178 continue;
179
180 auto& info = m_attributes[attrIdx];
181 info.src_buffer_index = lookup->input->buffer;
182 info.src_stride = lookup->binding->stride;
183 info.src_offset = lookup->attribute->byte_offset
184 + static_cast<int32_t>(lookup->input->byte_offset);
185
186 int mappedIndex = m_srcBufferMapping[info.src_buffer_index];
187 if(mappedIndex < 0)
188 {
189 m_srcBufferMapping[info.src_buffer_index] = m_srcBufferCount;
190 m_srcBuffers[m_srcBufferCount]
191 = static_cast<QRhiBuffer*>(mesh.buffers[info.src_buffer_index].handle);
192 ++m_srcBufferCount;
193 }
194
195 ++attrIdx;
196 }
197
198 // Update index buffer
199 if(m_hasIndexBuffer && mesh.index.buffer >= 0)
200 {
201 m_indexBuffer = static_cast<QRhiBuffer*>(mesh.buffers[mesh.index.buffer].handle);
202 m_indexOffset = static_cast<int32_t>(mesh.index.byte_offset);
203 m_indexFormat32 = (mesh.index.format == halp::index_format::uint32);
204 }
205
206 updateBindings();
207 m_dirty = true;
208 }
209
210 void release() noexcept
211 {
212 delete m_pipeline;
213 m_pipeline = nullptr;
214
215 delete m_srb;
216 m_srb = nullptr;
217
218 delete m_uniformBuffer;
219 m_uniformBuffer = nullptr;
220
221 // Freed by the engine:
222 // delete m_outputBuffer;
223 // m_outputBuffer = nullptr;
224
225 std::fill(std::begin(m_srcBuffers), std::end(m_srcBuffers), nullptr);
226 m_indexBuffer = nullptr;
227 }
228
229 void runCompute(QRhi& rhi, QRhiCommandBuffer& cb, QRhiResourceUpdateBatch*& res)
230 {
231 if(!m_dirty || m_vertexCount == 0 || !m_pipeline)
232 return;
233
234 // Prepare UBO data
235 struct alignas(16) AttributeParams
236 {
237 uint32_t srcBufferIndex; // Which source buffer (0-7)
238 uint32_t srcStrideBytes;
239 uint32_t srcOffsetBytes;
240 uint32_t elementCount;
241 uint32_t outputComponents;
242 uint32_t outputOffsetBytes;
243 uint32_t isActive;
244 uint32_t _pad;
245 };
246
247 struct alignas(64) Params
248 {
249 uint32_t vertexCount;
250 uint32_t attributeCount;
251 uint32_t outputStrideBytes;
252 uint32_t hasIndexBuffer;
253 uint32_t indexOffsetBytes;
254 uint32_t index32Bit;
255 uint32_t _pad[2];
256 alignas(64) AttributeParams attributes[MAX_PACKED_ATTRIBUTES];
257 } params{};
258 static_assert(offsetof(Params, attributes[0]) == 64);
259
260 params.vertexCount = static_cast<uint32_t>(m_vertexCount);
261 params.attributeCount = static_cast<uint32_t>(m_attributeCount);
262 params.outputStrideBytes = static_cast<uint32_t>(m_outputStride);
263 params.hasIndexBuffer = m_hasIndexBuffer ? 1u : 0u;
264 params.indexOffsetBytes = static_cast<uint32_t>(m_indexOffset);
265 params.index32Bit = m_indexFormat32 ? 1u : 0u;
266
267 for(int i = 0; i < MAX_PACKED_ATTRIBUTES; ++i)
268 {
269 const auto& info = m_attributes[i];
270 auto& ap = params.attributes[i];
271
272 if(info.is_active)
273 {
274 ap.srcBufferIndex
275 = static_cast<uint32_t>(m_srcBufferMapping[info.src_buffer_index]);
276 ap.srcStrideBytes = static_cast<uint32_t>(info.src_stride);
277 ap.srcOffsetBytes = static_cast<uint32_t>(info.src_offset);
278 ap.elementCount = static_cast<uint32_t>(info.element_count);
279 ap.outputComponents = static_cast<uint32_t>(info.output_components);
280 ap.outputOffsetBytes = static_cast<uint32_t>(info.output_offset);
281 ap.isActive = 1u;
282 }
283 else
284 {
285 ap.isActive = 0u;
286 }
287 }
288
289 res->updateDynamicBuffer(m_uniformBuffer, 0, sizeof(params), &params);
290
291 cb.beginComputePass(res);
292 cb.setComputePipeline(m_pipeline);
293 cb.setShaderResources(m_srb);
294
295 const int workgroups = (m_vertexCount + 255) / 256;
296 cb.dispatch(workgroups, 1, 1);
297
298 cb.endComputePass();
299
300 res = rhi.nextResourceUpdateBatch();
301
302 m_dirty = false;
303 }
304
305 [[nodiscard]] gpu_buffer_view output() const noexcept
306 {
307 return {
308 .buffer = m_outputBuffer,
309 .offset = 0,
310 .size = m_outputSize,
311 };
312 }
313
314 [[nodiscard]] int32_t outputStride() const noexcept { return m_outputStride; }
315
316 [[nodiscard]] int32_t attributeCount() const noexcept { return m_attributeCount; }
317
318 [[nodiscard]] const packed_attribute_info& attributeInfo(int index) const noexcept
319 {
320 return m_attributes[index];
321 }
322
323 [[nodiscard]] static constexpr bool needsCompute() noexcept { return true; }
324
325private:
326 bool createPipeline(const score::gfx::RenderState& renderState, QRhi& rhi)
327 {
328 m_uniformBuffer = rhi.newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 512);
329 m_uniformBuffer->setName("GeometryPacker::m_uniformBuffer");
330
331 if(!m_uniformBuffer || !m_uniformBuffer->create())
332 {
333 qDebug() << "PackedExtractionStrategy: UBO creation failed";
334 return false;
335 }
336
337 static const QString shaderCode = QStringLiteral(R"(#version 450
338
339layout(local_size_x = 256) in;
340
341struct AttributeParams {
342 uint srcBufferIndex;
343 uint srcStrideBytes;
344 uint srcOffsetBytes;
345 uint elementCount;
346 uint outputComponents;
347 uint outputOffsetBytes;
348 uint isActive;
349 uint _pad;
350};
351
352layout(std140, binding = 0) uniform Params {
353 uint vertexCount;
354 uint attributeCount;
355 uint outputStrideBytes;
356 uint hasIndexBuffer;
357 uint indexOffsetBytes;
358 uint index32Bit;
359 uint _pad[2];
360 AttributeParams attributes[8];
361};
362
363layout(std430, binding = 1) readonly buffer SrcBuffer0 { uint src0[]; };
364layout(std430, binding = 2) readonly buffer SrcBuffer1 { uint src1[]; };
365layout(std430, binding = 3) readonly buffer SrcBuffer2 { uint src2[]; };
366layout(std430, binding = 4) readonly buffer SrcBuffer3 { uint src3[]; };
367layout(std430, binding = 5) readonly buffer SrcBuffer4 { uint src4[]; };
368layout(std430, binding = 6) readonly buffer SrcBuffer5 { uint src5[]; };
369layout(std430, binding = 7) readonly buffer SrcBuffer6 { uint src6[]; };
370layout(std430, binding = 8) readonly buffer SrcBuffer7 { uint src7[]; };
371
372layout(std430, binding = 9) readonly buffer IndexBuffer { uint index_data[]; };
373
374layout(std430, binding = 10) writeonly buffer DstBuffer { uint dst_data[]; };
375
376uint readSrcData(uint bufferIndex, uint wordIndex)
377{
378 switch (bufferIndex)
379 {
380 case 0: return src0[wordIndex];
381 case 1: return src1[wordIndex];
382 case 2: return src2[wordIndex];
383 case 3: return src3[wordIndex];
384 case 4: return src4[wordIndex];
385 case 5: return src5[wordIndex];
386 case 6: return src6[wordIndex];
387 case 7: return src7[wordIndex];
388 }
389 return 0;
390}
391
392uint readIndex(uint i)
393{
394 if (index32Bit != 0)
395 {
396 uint wordIndex = (indexOffsetBytes / 4) + i;
397 return index_data[wordIndex];
398 }
399 else
400 {
401 uint bytePos = indexOffsetBytes + i * 2;
402 uint wordIndex = bytePos / 4;
403 uint word = index_data[wordIndex];
404 uint shift = (bytePos % 4) * 8;
405 return (word >> shift) & 0xFFFFu;
406 }
407}
408
409void main()
410{
411 uint outputIdx = gl_GlobalInvocationID.x;
412 if (outputIdx >= vertexCount)
413 return;
414
415 // Get source vertex index (may differ from output index if indexed)
416 uint srcVertexIdx = hasIndexBuffer != 0 ? readIndex(outputIdx) : outputIdx;
417
418 // Output base position (in uints, since outputStrideBytes is in bytes)
419 uint dstBase = (outputIdx * outputStrideBytes) / 4;
420
421 // Process each attribute
422 for (uint a = 0; a < attributeCount; ++a)
423 {
424 AttributeParams attr = attributes[a];
425 if (attr.isActive == 0)
426 continue;
427
428 // Source position
429 uint srcBase = (srcVertexIdx * attr.srcStrideBytes + attr.srcOffsetBytes) / 4;
430
431 // Destination position for this attribute
432 uint attrDstBase = dstBase + (attr.outputOffsetBytes / 4);
433
434 // Copy elements
435 for (uint i = 0; i < attr.elementCount; ++i)
436 {
437 uint value = readSrcData(attr.srcBufferIndex, srcBase + i);
438 dst_data[attrDstBase + i] = value;
439 }
440
441 // Pad to vec4 if needed
442 if (attr.outputComponents > attr.elementCount)
443 {
444 for (uint i = attr.elementCount; i < attr.outputComponents; ++i)
445 {
446 // w = 1.0f (0x3f800000), others = 0
447 dst_data[attrDstBase + i] = (i == 3) ? 0x3f800000u : 0u;
448 }
449 }
450 }
451}
452)");
453
454 QShader shader = score::gfx::makeCompute(renderState, shaderCode);
455 if(!shader.isValid())
456 {
457 qDebug() << "PackedExtractionStrategy: Shader compilation failed";
458 return false;
459 }
460
461 m_srb = rhi.newShaderResourceBindings();
462 updateBindings();
463
464 if(!m_srb->create())
465 {
466 qDebug() << "PackedExtractionStrategy: SRB creation failed";
467 return false;
468 }
469
470 m_pipeline = rhi.newComputePipeline();
471 m_pipeline->setShaderResourceBindings(m_srb);
472 m_pipeline->setShaderStage({QRhiShaderStage::Compute, shader});
473
474 if(!m_pipeline->create())
475 {
476 qDebug() << "PackedExtractionStrategy: Pipeline creation failed";
477 return false;
478 }
479
480 m_dirty = true;
481 return true;
482 }
483
484 void updateBindings()
485 {
486 if(!m_srb)
487 return;
488
489 QVarLengthArray<QRhiShaderResourceBinding, 12> bindings;
490
491 // Binding 0: UBO
492 bindings.append(
493 QRhiShaderResourceBinding::uniformBuffer(
494 0, QRhiShaderResourceBinding::ComputeStage, m_uniformBuffer));
495
496 // Bindings 1-8: Source buffers (use first valid buffer as placeholder for unused slots)
497 QRhiBuffer* placeholderBuffer = nullptr;
498 for(int i = 0; i < m_srcBufferCount; ++i)
499 {
500 if(m_srcBuffers[i])
501 {
502 placeholderBuffer = m_srcBuffers[i];
503 break;
504 }
505 }
506 if(!placeholderBuffer && m_outputBuffer)
507 {
508 placeholderBuffer = m_outputBuffer; // Fallback
509 }
510
511 for(int i = 0; i < MAX_PACKED_ATTRIBUTES; ++i)
512 {
513 QRhiBuffer* buf = (i < m_srcBufferCount && m_srcBuffers[i]) ? m_srcBuffers[i]
514 : placeholderBuffer;
515 bindings.append(
516 QRhiShaderResourceBinding::bufferLoad(
517 1 + i, QRhiShaderResourceBinding::ComputeStage, buf));
518 }
519
520 // Binding 9: Index buffer (use placeholder if not indexed)
521 QRhiBuffer* idxBuf
522 = m_hasIndexBuffer && m_indexBuffer ? m_indexBuffer : placeholderBuffer;
523 bindings.append(
524 QRhiShaderResourceBinding::bufferLoad(
525 9, QRhiShaderResourceBinding::ComputeStage, idxBuf));
526
527 // Binding 10: Output buffer
528 bindings.append(
529 QRhiShaderResourceBinding::bufferStore(
530 10, QRhiShaderResourceBinding::ComputeStage, m_outputBuffer));
531
532 m_srb->setBindings(bindings.cbegin(), bindings.cend());
533 }
534
535 // Source buffers (up to MAX_PACKED_ATTRIBUTES unique buffers)
536 QRhiBuffer* m_srcBuffers[MAX_PACKED_ATTRIBUTES]{};
537 int m_srcBufferMapping[32]{}; // mesh buffer index -> m_srcBuffers index
538 int m_srcBufferCount{};
539
540 QRhiBuffer* m_indexBuffer{};
541 QRhiBuffer* m_outputBuffer{};
542 QRhiShaderResourceBindings* m_srb{};
543 QRhiComputePipeline* m_pipeline{};
544 QRhiBuffer* m_uniformBuffer{};
545
546 packed_attribute_info m_attributes[MAX_PACKED_ATTRIBUTES]{};
547 int32_t m_attributeCount{};
548 int32_t m_vertexCount{};
549 int32_t m_outputStride{}; // In bytes
550 int32_t m_indexOffset{};
551 int64_t m_outputSize{};
552
553 bool m_hasIndexBuffer{false};
554 bool m_indexFormat32{true};
555 bool m_dirty{true};
556};
557
559{
560public:
561 halp_meta(name, "Repack attributes")
562 halp_meta(category, "Visuals/3D")
563 halp_meta(c_name, "pack_geometry")
564 halp_meta(manual_url, "https://ossia.io/score-docs/processes/pack-geometry.html")
565 halp_meta(uuid, "7d7d5973-4aa9-4bfe-9249-8b892d92e0db")
566
567 enum class Attribute3
568 {
569 None,
570 Vec3,
571 Vec4
572 };
573 enum class Attribute2
574 {
575 None,
576 Vec2
577 };
578
579 struct ins
580 {
581 struct
582 {
583 halp_meta(name, "Geometry");
584 halp::dynamic_gpu_geometry mesh;
585 float transform[16]{};
586 bool dirty_mesh = false;
587 bool dirty_transform = false;
588 } geometry;
589
590 halp::combobox_t<"Position", Attribute3> position;
591 halp::combobox_t<"Normal", Attribute3> normal;
592 halp::combobox_t<"Color", Attribute3> color;
593 halp::combobox_t<"TexCoord", Attribute2> texcoord;
594 halp::combobox_t<"Tangent", Attribute3> tangent;
595 } inputs;
596
597 struct outs
598 {
599 halp::gpu_buffer_output<"Buffer"> buffer;
600
601 struct
602 {
603 halp_meta(name, "Stride");
604 int32_t value{};
605 } stride;
606 } outputs;
607
608 GeometryPacker() = default;
609 ~GeometryPacker() = default;
610
611 void init(score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res);
612
613 void update(
614 score::gfx::RenderList& renderer, QRhiResourceUpdateBatch& res,
616
617 void release(score::gfx::RenderList& r);
618
619 void runInitialPasses(
620 score::gfx::RenderList& renderer, QRhiCommandBuffer& commands,
621 QRhiResourceUpdateBatch*& res, score::gfx::Edge& edge);
622
623 void operator()() { }
624
625private:
626 void buildAttributeSpecs();
627 bool specsChanged() const;
628
629 PackedExtractionStrategy m_strategy;
630 std::vector<packed_attribute_spec> m_specs;
631 std::vector<packed_attribute_spec> m_lastSpecs;
632 bool m_initialized{false};
633};
634
635}
Definition GeometryPacker.hpp:559
Definition GeometryPacker.hpp:35
List of nodes to be rendered to an output.
Definition RenderList.hpp:19
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:12
QShader makeCompute(const RenderState &v, QString compute)
Compile a compute shader.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:421
Definition GeometryPacker.hpp:580
Definition GeometryPacker.hpp:598
Definition GeometryToBufferStrategies.hpp:246
Definition TinyObj.hpp:19
Definition GeometryPacker.hpp:23
Definition GeometryPacker.hpp:17
Connection between two score::gfx::Port.
Definition score-plugin-gfx/Gfx/Graph/Utils.hpp:71
Global state associated to a rendering context.
Definition RenderState.hpp:35