Loading...
Searching...
No Matches
GeometryToBufferStrategies.hpp
1#pragma once
2#include <Crousti/GpuUtils.hpp>
3#include <score/tools/std/String.hpp>
4#include <Gfx/Graph/Node.hpp>
5#include <Gfx/Graph/NodeRenderer.hpp>
6
7#include <ossia/detail/pod_vector.hpp>
8
9#include <boost/container/vector.hpp>
10
11#include <avnd/introspection/input.hpp>
12#include <avnd/introspection/output.hpp>
13#include <halp/controls.hpp>
14#include <halp/geometry.hpp>
15#include <halp/meta.hpp>
16#include <halp/texture.hpp>
17
18#include <optional>
19#include <variant>
20namespace Threedim
21{
22[[nodiscard]] constexpr int32_t attributeFormatSize(halp::attribute_format fmt) noexcept
23{
24 using namespace halp;
25 switch(fmt)
26 {
27 case attribute_format::float4:
28 return 4 * sizeof(float);
29 case attribute_format::float3:
30 return 3 * sizeof(float);
31 case attribute_format::float2:
32 return 2 * sizeof(float);
33 case attribute_format::float1:
34 return sizeof(float);
35
36 case attribute_format::uint4:
37 return 4 * sizeof(uint32_t);
38 case attribute_format::uint3:
39 return 3 * sizeof(uint32_t);
40 case attribute_format::uint2:
41 return 2 * sizeof(uint32_t);
42 case attribute_format::uint1:
43 return sizeof(uint32_t);
44
45 case attribute_format::sint4:
46 return 4 * sizeof(int32_t);
47 case attribute_format::sint3:
48 return 3 * sizeof(int32_t);
49 case attribute_format::sint2:
50 return 2 * sizeof(int32_t);
51 case attribute_format::sint1:
52 return sizeof(int32_t);
53
54 case attribute_format::unormbyte4:
55 return 4 * sizeof(uint8_t);
56 case attribute_format::unormbyte2:
57 return 2 * sizeof(uint8_t);
58 case attribute_format::unormbyte1:
59 return sizeof(uint8_t);
60
61 case attribute_format::half4:
62 return 4 * sizeof(uint16_t);
63 case attribute_format::half3:
64 return 3 * sizeof(uint16_t);
65 case attribute_format::half2:
66 return 2 * sizeof(uint16_t);
67 case attribute_format::half1:
68 return sizeof(uint16_t);
69
70 case attribute_format::ushort4:
71 return 4 * sizeof(uint16_t);
72 case attribute_format::ushort3:
73 return 3 * sizeof(uint16_t);
74 case attribute_format::ushort2:
75 return 2 * sizeof(uint16_t);
76 case attribute_format::ushort1:
77 return sizeof(uint16_t);
78
79 case attribute_format::sshort4:
80 return 4 * sizeof(int16_t);
81 case attribute_format::sshort3:
82 return 3 * sizeof(int16_t);
83 case attribute_format::sshort2:
84 return 2 * sizeof(int16_t);
85 case attribute_format::sshort1:
86 return sizeof(int16_t);
87
88 default:
89 return 0;
90 }
91 return 0;
92}
93
94[[nodiscard]] constexpr int32_t
95attributeFormatComponents(halp::attribute_format fmt) noexcept
96{
97 using namespace halp;
98 switch(fmt)
99 {
100 case attribute_format::float4:
101 case attribute_format::uint4:
102 case attribute_format::sint4:
103 case attribute_format::unormbyte4:
104 case attribute_format::half4:
105 case attribute_format::ushort4:
106 case attribute_format::sshort4:
107 return 4;
108 case attribute_format::float3:
109 case attribute_format::uint3:
110 case attribute_format::sint3:
111 case attribute_format::half3:
112 case attribute_format::ushort3:
113 case attribute_format::sshort3:
114 return 3;
115 case attribute_format::float2:
116 case attribute_format::uint2:
117 case attribute_format::sint2:
118 case attribute_format::unormbyte2:
119 case attribute_format::half2:
120 case attribute_format::ushort2:
121 case attribute_format::sshort2:
122 return 2;
123 case attribute_format::float1:
124 case attribute_format::uint1:
125 case attribute_format::sint1:
126 case attribute_format::unormbyte1:
127 case attribute_format::half1:
128 case attribute_format::ushort1:
129 case attribute_format::sshort1:
130 return 1;
131
132 default:
133 return 0;
134 }
135 return 0;
136}
137
138[[nodiscard]] constexpr bool isFloatFormat(halp::attribute_format fmt) noexcept
139{
140 const int f = static_cast<int>(fmt);
141 return (f >= int(halp::attribute_format::float4)
142 && f <= int(halp::attribute_format::float1))
143 || (f >= int(halp::attribute_format::half4)
144 && f <= int(halp::attribute_format::half1));
145}
146
147//=============================================================================
148// Attribute Lookup Result
149//=============================================================================
150
152{
153 const halp::geometry_attribute* attribute{};
154 const halp::geometry_binding* binding{};
155 const halp::geometry_input* input{};
156 const halp::geometry_gpu_buffer* buffer{};
157 int32_t attribute_size{};
158 int32_t binding_index{};
159
160 [[nodiscard]] bool valid() const noexcept { return attribute != nullptr; }
161
162 [[nodiscard]] bool canDirectReference() const noexcept
163 {
164 if(!valid())
165 return false;
166 return attribute->byte_offset == 0 && attribute_size == binding->stride;
167 }
168};
169
170[[nodiscard]] inline std::optional<attribute_lookup> findAttribute(
171 const halp::dynamic_gpu_geometry& mesh, halp::attribute_semantic location) noexcept
172{
173 for(const auto& attr : mesh.attributes)
174 {
175 if(attr.semantic == location)
176 {
177 const auto binding_idx = attr.binding;
178 if(binding_idx < 0 || binding_idx >= static_cast<int>(mesh.bindings.size()))
179 {
180 qDebug() << "GeometryExtraction: Invalid binding index" << binding_idx;
181 return std::nullopt;
182 }
183
184 if(binding_idx >= static_cast<int>(mesh.input.size()))
185 {
186 qDebug() << "GeometryExtraction: Missing input for binding" << binding_idx;
187 return std::nullopt;
188 }
189
190 const auto& inp = mesh.input[binding_idx];
191 if(inp.buffer < 0 || inp.buffer >= static_cast<int>(mesh.buffers.size()))
192 {
193 qDebug() << "GeometryExtraction: Invalid buffer index" << inp.buffer;
194 return std::nullopt;
195 }
196
197 return attribute_lookup{
198 .attribute = &attr,
199 .binding = &mesh.bindings[binding_idx],
200 .input = &inp,
201 .buffer = &mesh.buffers[inp.buffer],
202 .attribute_size = attributeFormatSize(attr.format),
203 .binding_index = binding_idx};
204 }
205 }
206 return std::nullopt;
207}
208
209[[nodiscard]] inline std::optional<attribute_lookup>
210findAttribute(const halp::dynamic_gpu_geometry& mesh, int index) noexcept
211{
212 if(index < 0 || index >= mesh.attributes.size())
213 return {};
214
215 const auto& attr = mesh.attributes[index];
216 const auto binding_idx = attr.binding;
217 if(binding_idx < 0 || binding_idx >= static_cast<int>(mesh.bindings.size()))
218 {
219 qDebug() << "GeometryExtraction: Invalid binding index" << binding_idx;
220 return std::nullopt;
221 }
222
223 if(binding_idx >= static_cast<int>(mesh.input.size()))
224 {
225 qDebug() << "GeometryExtraction: Missing input for binding" << binding_idx;
226 return std::nullopt;
227 }
228
229 const auto& inp = mesh.input[binding_idx];
230 if(inp.buffer < 0 || inp.buffer >= static_cast<int>(mesh.buffers.size()))
231 {
232 qDebug() << "GeometryExtraction: Invalid buffer index" << inp.buffer;
233 return std::nullopt;
234 }
235
236 return attribute_lookup{
237 .attribute = &attr,
238 .binding = &mesh.bindings[binding_idx],
239 .input = &inp,
240 .buffer = &mesh.buffers[inp.buffer],
241 .attribute_size = attributeFormatSize(attr.format),
242 .binding_index = binding_idx};
243 return std::nullopt;
244}
246{
247 QRhiBuffer* buffer{};
248 int64_t offset{};
249 int64_t size{};
250
251 [[nodiscard]] bool valid() const noexcept { return buffer != nullptr && size > 0; }
252};
253
255{
256public:
257 bool init(
258 const score::gfx::RenderState& renderState, QRhi& rhi,
259 const halp::dynamic_gpu_geometry& mesh, int buffer, int64_t byte_offset,
260 int64_t byte_size)
261 {
262 m_buffer = static_cast<QRhiBuffer*>(mesh.buffers[buffer].handle);
263 m_offset = byte_offset;
264 m_size = byte_size;
265
266 if(!m_buffer)
267 {
268 qDebug() << "DirectBufferReferenceStrategy: Null buffer handle";
269 return false;
270 }
271 assert(m_buffer->size() >= byte_size + byte_offset);
272 return true;
273 }
274
275 void
276 update(QRhi&, const halp::dynamic_gpu_geometry&, const attribute_lookup& lookup, bool)
277 {
278 }
279
280 void release() noexcept
281 {
282 m_buffer = nullptr;
283 m_offset = 0;
284 m_size = 0;
285 }
286
287 void runCompute(QRhi&, QRhiCommandBuffer&, QRhiResourceUpdateBatch*&) { }
288
289 [[nodiscard]] gpu_buffer_view output() const noexcept
290 {
291 return {
292 .buffer = m_buffer,
293 .offset = m_offset,
294 .size = m_size,
295 };
296 }
297
298 [[nodiscard]] static constexpr bool needsCompute() noexcept { return false; }
299
300private:
301 QRhiBuffer* m_buffer{};
302 int64_t m_offset{};
303 int64_t m_size{};
304};
305
307{
308public:
309 bool init(
310 const score::gfx::RenderState& renderState, QRhi& rhi,
311 const halp::dynamic_gpu_geometry& mesh, const attribute_lookup& lookup,
312 bool /*padToVec4*/)
313 {
314 m_buffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
315 m_offset = lookup.input->byte_offset;
316 m_size = static_cast<int64_t>(lookup.attribute_size) * mesh.vertices;
317
318 if(!m_buffer)
319 {
320 qDebug() << "DirectReferenceStrategy: Null buffer handle";
321 return false;
322 }
323 return true;
324 }
325
326 void update(
327 QRhi& /*rhi*/, const halp::dynamic_gpu_geometry& mesh,
328 const attribute_lookup& lookup, bool /*padToVec4*/)
329 {
330 m_buffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
331 m_offset = lookup.input->byte_offset;
332 m_size = static_cast<int64_t>(lookup.attribute_size) * mesh.vertices;
333 }
334
335 void release() noexcept
336 {
337 // We don't own the buffer
338 m_buffer = nullptr;
339 m_offset = 0;
340 m_size = 0;
341 }
342
343 void runCompute(QRhi&, QRhiCommandBuffer&, QRhiResourceUpdateBatch*&) { }
344
345 [[nodiscard]] gpu_buffer_view output() const noexcept
346 {
347 return {
348 .buffer = m_buffer,
349 .offset = m_offset,
350 .size = m_size,
351 };
352 }
353
354 [[nodiscard]] static constexpr bool needsCompute() noexcept { return false; }
355
356private:
357 QRhiBuffer* m_buffer{};
358 int64_t m_offset{};
359 int64_t m_size{};
360};
361
363{
364public:
365 bool init(
366 const score::gfx::RenderState& renderState, QRhi& rhi,
367 const halp::dynamic_gpu_geometry& mesh, const attribute_lookup& lookup,
368 bool padToVec4)
369 {
370 m_vertexCount = mesh.vertices;
371 m_srcStride = lookup.binding->stride;
372 m_srcOffset = lookup.attribute->byte_offset
373 + static_cast<int32_t>(lookup.input->byte_offset);
374 m_elementCount = attributeFormatComponents(lookup.attribute->format);
375 m_padToVec4
376 = padToVec4 && m_elementCount < 4 && isFloatFormat(lookup.attribute->format);
377
378 const int32_t outputComponents = m_padToVec4 ? 4 : m_elementCount;
379 m_outputComponents = outputComponents;
380 m_outputSize
381 = static_cast<int64_t>(m_vertexCount) * outputComponents * sizeof(float);
382
383 if(m_outputSize == 0)
384 {
385 qDebug() << "ComputeExtractionStrategy: Zero output size";
386 return false;
387 }
388
389 // Create output buffer
390 m_outputBuffer = rhi.newBuffer(
391 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
392 static_cast<quint32>(m_outputSize));
393 m_outputBuffer->setName("ComputeExtractionStrategy::m_outputBuffer");
394
395 if(!m_outputBuffer || !m_outputBuffer->create())
396 {
397 qDebug() << "ComputeExtractionStrategy: Failed to create output buffer";
398 return false;
399 }
400
401 m_srcBuffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
402 if(!m_srcBuffer)
403 {
404 qDebug() << "ComputeExtractionStrategy: Null source buffer";
405 return false;
406 }
407
408 return createPipeline(renderState, rhi);
409 }
410
411 void update(
412 QRhi& rhi, const halp::dynamic_gpu_geometry& mesh, const attribute_lookup& lookup,
413 bool padToVec4)
414 {
415 m_srcBuffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
416 m_srcStride = lookup.binding->stride;
417 m_srcOffset = lookup.attribute->byte_offset
418 + static_cast<int32_t>(lookup.input->byte_offset);
419
420 const bool newPadToVec4
421 = padToVec4 && m_elementCount < 4 && isFloatFormat(lookup.attribute->format);
422 const int32_t newOutputComponents = newPadToVec4 ? 4 : m_elementCount;
423 const int64_t newSize
424 = static_cast<int64_t>(mesh.vertices) * newOutputComponents * sizeof(float);
425
426 // Check if buffer needs resize
427 if(newSize != m_outputSize || mesh.vertices != m_vertexCount
428 || newPadToVec4 != m_padToVec4)
429 {
430 m_vertexCount = mesh.vertices;
431 m_padToVec4 = newPadToVec4;
432 m_outputComponents = newOutputComponents;
433 m_outputSize = newSize;
434
435 if(m_outputSize > 0)
436 {
437 m_outputBuffer->setSize(static_cast<quint32>(m_outputSize));
438 m_outputBuffer->create();
439 }
440 }
441
442 // Rebind if source buffer changed. Must match createPipeline()'s layout
443 // exactly (uniform@0, src@1, out@2) or the SRB becomes layout-incompatible
444 // with the pipeline and the shader reads Params from the wrong binding.
445 if(m_srb)
446 {
447 m_srb->setBindings({
448 QRhiShaderResourceBinding::uniformBuffer(
449 0, QRhiShaderResourceBinding::ComputeStage, m_uniformBuffer),
450 QRhiShaderResourceBinding::bufferLoad(
451 1, QRhiShaderResourceBinding::ComputeStage, m_srcBuffer),
452 QRhiShaderResourceBinding::bufferStore(
453 2, QRhiShaderResourceBinding::ComputeStage, m_outputBuffer),
454 });
455 m_srb->create();
456 }
457
458 m_dirty = true;
459 }
460
461 void release() noexcept
462 {
463 delete m_uniformBuffer;
464 m_uniformBuffer = nullptr;
465
466 delete m_pipeline;
467 m_pipeline = nullptr;
468
469 delete m_srb;
470 m_srb = nullptr;
471
472 delete m_outputBuffer;
473 m_outputBuffer = nullptr;
474
475 m_srcBuffer = nullptr;
476 }
477
478 void runCompute(QRhi& rhi, QRhiCommandBuffer& cb, QRhiResourceUpdateBatch*& res)
479 {
480 if(!m_dirty || m_vertexCount == 0 || !m_pipeline)
481 return;
482
483 struct alignas(16) Params
484 {
485 uint32_t vertexCount;
486 uint32_t srcStrideBytes;
487 uint32_t srcOffsetBytes;
488 uint32_t elementCount;
489 uint32_t padToVec4;
490 uint32_t _pad[3]; // Padding to maintain alignment
491 } params{
492 static_cast<uint32_t>(m_vertexCount),
493 static_cast<uint32_t>(m_srcStride),
494 static_cast<uint32_t>(m_srcOffset),
495 static_cast<uint32_t>(m_elementCount),
496 m_padToVec4 ? 1u : 0u,
497 {0, 0, 0}};
498
499 res->updateDynamicBuffer(m_uniformBuffer, 0, sizeof(params), &params);
500
501 cb.beginComputePass(res);
502 cb.setComputePipeline(m_pipeline);
503 cb.setShaderResources(m_srb);
504
505 const int workgroups = (m_vertexCount + 255) / 256;
506 cb.dispatch(workgroups, 1, 1);
507
508 cb.endComputePass();
509
510 // Get new resource batch after compute pass
511 res = rhi.nextResourceUpdateBatch();
512
513 m_dirty = false;
514 }
515
516 [[nodiscard]] gpu_buffer_view output() const noexcept
517 {
518 return {
519 .buffer = m_outputBuffer,
520 .offset = 0,
521 .size = m_outputSize,
522 };
523 }
524
525 [[nodiscard]] static constexpr bool needsCompute() noexcept { return true; }
526
527private:
528 bool createPipeline(const score::gfx::RenderState& renderState, QRhi& rhi)
529 {
530 static const QString shaderCode = QStringLiteral(R"(#version 450
531
532layout(local_size_x = 256) in;
533
534layout(std140, binding = 0) uniform Params {
535 uint vertexCount;
536 uint srcStrideBytes;
537 uint srcOffsetBytes;
538 uint elementCount;
539 uint padToVec4;
540};
541
542layout(std430, binding = 1) readonly buffer SrcBuffer {
543 uint src_data[];
544};
545
546layout(std430, binding = 2) writeonly buffer DstBuffer {
547 uint dst_data[];
548};
549
550void main()
551{
552 uint idx = gl_GlobalInvocationID.x;
553 if (idx >= vertexCount)
554 return;
555
556 uint srcBase = (idx * srcStrideBytes + srcOffsetBytes) / 4;
557 uint dstComponents = padToVec4 != 0 ? 4 : elementCount;
558 uint dstBase = idx * dstComponents;
559
560 for (uint i = 0; i < elementCount; ++i)
561 dst_data[dstBase + i] = src_data[srcBase + i];
562
563 if (padToVec4 != 0)
564 {
565 for (uint i = elementCount; i < 4; ++i)
566 dst_data[dstBase + i] = (i == 3) ? 0x3f800000u : 0u;
567 }
568}
569)");
570
571 QShader shader = score::gfx::makeCompute(renderState, shaderCode);
572 if(!shader.isValid())
573 {
574 qDebug() << "ComputeExtractionStrategy: Shader compilation failed";
575 return false;
576 }
577
578 // Create uniform buffer (aligned to 256 bytes for compatibility)
579 m_uniformBuffer = rhi.newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 256);
580 m_uniformBuffer->setName("ComputeExtractionStrategy::m_uniformBuffer");
581
582 if(!m_uniformBuffer || !m_uniformBuffer->create())
583 {
584 qDebug() << "ComputeExtractionStrategy: UBO creation failed";
585 return false;
586 }
587
588 m_srb = rhi.newShaderResourceBindings();
589 m_srb->setBindings({
590 QRhiShaderResourceBinding::uniformBuffer(
591 0, QRhiShaderResourceBinding::ComputeStage, m_uniformBuffer),
592 QRhiShaderResourceBinding::bufferLoad(
593 1, QRhiShaderResourceBinding::ComputeStage, m_srcBuffer),
594 QRhiShaderResourceBinding::bufferStore(
595 2, QRhiShaderResourceBinding::ComputeStage, m_outputBuffer),
596 });
597
598 if(!m_srb->create())
599 {
600 qDebug() << "ComputeExtractionStrategy: SRB creation failed";
601 return false;
602 }
603
604 m_pipeline = rhi.newComputePipeline();
605 m_pipeline->setShaderResourceBindings(m_srb);
606 m_pipeline->setShaderStage({QRhiShaderStage::Compute, shader});
607
608 if(!m_pipeline->create())
609 {
610 qDebug() << "ComputeExtractionStrategy: Pipeline creation failed";
611 return false;
612 }
613
614 m_dirty = true;
615 return true;
616 }
617
618 QRhiBuffer* m_srcBuffer{};
619 QRhiBuffer* m_uniformBuffer{};
620 QRhiBuffer* m_outputBuffer{};
621 QRhiShaderResourceBindings* m_srb{};
622 QRhiComputePipeline* m_pipeline{};
623
624 int32_t m_vertexCount{};
625 int32_t m_srcStride{};
626 int32_t m_srcOffset{};
627 int32_t m_elementCount{};
628 int32_t m_outputComponents{};
629 int64_t m_outputSize{};
630
631 bool m_padToVec4{false};
632 bool m_dirty{true};
633};
634
636{
637public:
638 bool init(
639 const score::gfx::RenderState& renderState, QRhi& rhi,
640 const halp::dynamic_gpu_geometry& mesh, const attribute_lookup& lookup,
641 bool padToVec4)
642 {
643 if(mesh.index.buffer < 0
644 || mesh.index.buffer >= static_cast<int>(mesh.buffers.size()))
645 {
646 qDebug() << "IndexedExtractionStrategy: Invalid index buffer";
647 return false;
648 }
649
650 // For indexed geometry, we output one vertex per index
651 m_indexCount = mesh.vertices;
652 m_srcStride = lookup.binding->stride;
653 m_srcOffset = lookup.attribute->byte_offset
654 + static_cast<int32_t>(lookup.input->byte_offset);
655 m_elementCount = attributeFormatComponents(lookup.attribute->format);
656 m_padToVec4
657 = padToVec4 && m_elementCount < 4 && isFloatFormat(lookup.attribute->format);
658 m_indexFormat32 = (mesh.index.format == halp::index_format::uint32);
659 m_indexOffset = static_cast<int32_t>(mesh.index.byte_offset);
660
661 const int32_t outputComponents = m_padToVec4 ? 4 : m_elementCount;
662 m_outputComponents = outputComponents;
663 m_outputSize = static_cast<int64_t>(m_indexCount) * outputComponents * sizeof(float);
664
665 if(m_outputSize == 0)
666 {
667 qDebug() << "IndexedExtractionStrategy: Zero output size";
668 return false;
669 }
670
671 m_outputBuffer = rhi.newBuffer(
672 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
673 static_cast<quint32>(m_outputSize));
674 m_outputBuffer->setName("IndexedExtractionStrategy::m_outputBuffer");
675
676 if(!m_outputBuffer || !m_outputBuffer->create())
677 {
678 qDebug() << "IndexedExtractionStrategy: Failed to create output buffer";
679 return false;
680 }
681
682 m_srcBuffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
683 m_indexBuffer = static_cast<QRhiBuffer*>(mesh.buffers[mesh.index.buffer].handle);
684
685 if(!m_srcBuffer || !m_indexBuffer)
686 {
687 qDebug() << "IndexedExtractionStrategy: Null source or index buffer";
688 return false;
689 }
690
691 return createPipeline(renderState, rhi);
692 }
693
694 void update(
695 QRhi& rhi, const halp::dynamic_gpu_geometry& mesh, const attribute_lookup& lookup,
696 bool padToVec4)
697 {
698 m_srcBuffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
699 m_srcStride = lookup.binding->stride;
700 m_srcOffset = lookup.attribute->byte_offset
701 + static_cast<int32_t>(lookup.input->byte_offset);
702
703 if(mesh.index.buffer >= 0
704 && mesh.index.buffer < static_cast<int>(mesh.buffers.size()))
705 {
706 m_indexBuffer = static_cast<QRhiBuffer*>(mesh.buffers[mesh.index.buffer].handle);
707 m_indexOffset = static_cast<int32_t>(mesh.index.byte_offset);
708 m_indexFormat32 = (mesh.index.format == halp::index_format::uint32);
709 }
710
711 const bool newPadToVec4
712 = padToVec4 && m_elementCount < 4 && isFloatFormat(lookup.attribute->format);
713 const int32_t newOutputComponents = newPadToVec4 ? 4 : m_elementCount;
714 const int64_t newSize
715 = static_cast<int64_t>(mesh.vertices) * newOutputComponents * sizeof(float);
716
717 if(newSize != m_outputSize || mesh.vertices != m_indexCount
718 || newPadToVec4 != m_padToVec4)
719 {
720 m_indexCount = mesh.vertices;
721 m_padToVec4 = newPadToVec4;
722 m_outputComponents = newOutputComponents;
723 m_outputSize = newSize;
724
725 if(m_outputSize > 0)
726 {
727 m_outputBuffer->setSize(static_cast<quint32>(m_outputSize));
728 m_outputBuffer->create();
729 }
730 }
731
732 // Rebind if source buffer changed. Must match createPipeline()'s layout
733 // exactly (uniform@0, src@1, index@2, out@3) or the SRB becomes
734 // layout-incompatible with the pipeline and the shader reads Params from
735 // the wrong binding.
736 if(m_srb)
737 {
738 m_srb->setBindings({
739 QRhiShaderResourceBinding::uniformBuffer(
740 0, QRhiShaderResourceBinding::ComputeStage, m_uniformBuffer),
741 QRhiShaderResourceBinding::bufferLoad(
742 1, QRhiShaderResourceBinding::ComputeStage, m_srcBuffer),
743 QRhiShaderResourceBinding::bufferLoad(
744 2, QRhiShaderResourceBinding::ComputeStage, m_indexBuffer),
745 QRhiShaderResourceBinding::bufferStore(
746 3, QRhiShaderResourceBinding::ComputeStage, m_outputBuffer),
747 });
748 m_srb->create();
749 }
750
751 m_dirty = true;
752 }
753
754 void release() noexcept
755 {
756 delete m_pipeline;
757 m_pipeline = nullptr;
758
759 delete m_srb;
760 m_srb = nullptr;
761
762 delete m_uniformBuffer;
763 m_uniformBuffer = nullptr;
764
765 delete m_outputBuffer;
766 m_outputBuffer = nullptr;
767
768 m_srcBuffer = nullptr;
769 m_indexBuffer = nullptr;
770 }
771
772 void runCompute(QRhi& rhi, QRhiCommandBuffer& cb, QRhiResourceUpdateBatch*& res)
773 {
774 if(!m_dirty || m_indexCount == 0 || !m_pipeline)
775 return;
776
777 struct alignas(16) Params
778 {
779 uint32_t indexCount;
780 uint32_t srcStrideBytes;
781 uint32_t srcOffsetBytes;
782 uint32_t elementCount;
783 uint32_t padToVec4;
784 uint32_t indexOffsetBytes;
785 uint32_t index32Bit;
786 uint32_t _pad;
787 } params{
788 static_cast<uint32_t>(m_indexCount),
789 static_cast<uint32_t>(m_srcStride),
790 static_cast<uint32_t>(m_srcOffset),
791 static_cast<uint32_t>(m_elementCount),
792 m_padToVec4 ? 1u : 0u,
793 static_cast<uint32_t>(m_indexOffset),
794 m_indexFormat32 ? 1u : 0u,
795 0};
796
797 res->updateDynamicBuffer(m_uniformBuffer, 0, sizeof(params), &params);
798
799 cb.beginComputePass(res);
800 cb.setComputePipeline(m_pipeline);
801 cb.setShaderResources(m_srb);
802
803 const int workgroups = (m_indexCount + 255) / 256;
804 cb.dispatch(workgroups, 1, 1);
805
806 cb.endComputePass();
807
808 res = rhi.nextResourceUpdateBatch();
809
810 m_dirty = false;
811 }
812
813 [[nodiscard]] gpu_buffer_view output() const noexcept
814 {
815 return {
816 .buffer = m_outputBuffer,
817 .offset = 0,
818 .size = m_outputSize,
819 };
820 }
821
822 [[nodiscard]] static constexpr bool needsCompute() noexcept { return true; }
823
824private:
825 bool createPipeline(const score::gfx::RenderState& renderState, QRhi& rhi)
826 {
827 static const QString shaderCode = QStringLiteral(R"(#version 450
828
829layout(local_size_x = 256) in;
830
831layout(std140, binding = 0) uniform Params {
832 uint indexCount;
833 uint srcStrideBytes;
834 uint srcOffsetBytes;
835 uint elementCount;
836 uint padToVec4;
837 uint indexOffsetBytes;
838 uint index32Bit;
839};
840
841layout(std430, binding = 1) readonly buffer SrcBuffer {
842 uint src_data[];
843};
844
845layout(std430, binding = 2) readonly buffer IndexBuffer {
846 uint index_data[];
847};
848
849layout(std430, binding = 3) writeonly buffer DstBuffer {
850 uint dst_data[];
851};
852
853uint readIndex(uint i)
854{
855 if (index32Bit != 0)
856 {
857 uint wordIndex = (indexOffsetBytes / 4) + i;
858 return index_data[wordIndex];
859 }
860 else
861 {
862 uint bytePos = indexOffsetBytes + i * 2;
863 uint wordIndex = bytePos / 4;
864 uint word = index_data[wordIndex];
865 uint shift = (bytePos % 4) * 8;
866 return (word >> shift) & 0xFFFFu;
867 }
868}
869
870void main()
871{
872 uint outputIdx = gl_GlobalInvocationID.x;
873 if (outputIdx >= indexCount)
874 return;
875
876 uint vertexIdx = readIndex(outputIdx);
877 uint srcBase = (vertexIdx * srcStrideBytes + srcOffsetBytes) / 4;
878 uint dstComponents = padToVec4 != 0 ? 4 : elementCount;
879 uint dstBase = outputIdx * dstComponents;
880
881 for (uint i = 0; i < elementCount; ++i)
882 dst_data[dstBase + i] = src_data[srcBase + i];
883
884 if (padToVec4 != 0)
885 {
886 for (uint i = elementCount; i < 4; ++i)
887 dst_data[dstBase + i] = (i == 3) ? 0x3f800000u : 0u;
888 }
889}
890)");
891
892 QShader shader = score::gfx::makeCompute(renderState, shaderCode);
893 if(!shader.isValid())
894 {
895 qDebug() << "IndexedExtractionStrategy: Shader compilation failed";
896 return false;
897 }
898
899 m_uniformBuffer = rhi.newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 256);
900 m_uniformBuffer->setName("IndexedExtractionStrategy::m_uniformBuffer");
901
902 if(!m_uniformBuffer || !m_uniformBuffer->create())
903 {
904 qDebug() << "IndexedExtractionStrategy: UBO creation failed";
905 return false;
906 }
907
908 m_srb = rhi.newShaderResourceBindings();
909 m_srb->setBindings({
910 QRhiShaderResourceBinding::uniformBuffer(
911 0, QRhiShaderResourceBinding::ComputeStage, m_uniformBuffer),
912 QRhiShaderResourceBinding::bufferLoad(
913 1, QRhiShaderResourceBinding::ComputeStage, m_srcBuffer),
914 QRhiShaderResourceBinding::bufferLoad(
915 2, QRhiShaderResourceBinding::ComputeStage, m_indexBuffer),
916 QRhiShaderResourceBinding::bufferStore(
917 3, QRhiShaderResourceBinding::ComputeStage, m_outputBuffer),
918 });
919
920 if(!m_srb->create())
921 {
922 qDebug() << "IndexedExtractionStrategy: SRB creation failed";
923 return false;
924 }
925
926 m_pipeline = rhi.newComputePipeline();
927 m_pipeline->setShaderResourceBindings(m_srb);
928 m_pipeline->setShaderStage({QRhiShaderStage::Compute, shader});
929
930 if(!m_pipeline->create())
931 {
932 qDebug() << "IndexedExtractionStrategy: Pipeline creation failed";
933 return false;
934 }
935
936 m_dirty = true;
937 return true;
938 }
939
940 QRhiBuffer* m_srcBuffer{};
941 QRhiBuffer* m_uniformBuffer{};
942 QRhiBuffer* m_indexBuffer{};
943 QRhiBuffer* m_outputBuffer{};
944 QRhiShaderResourceBindings* m_srb{};
945 QRhiComputePipeline* m_pipeline{};
946
947 int32_t m_indexCount{};
948 int32_t m_srcStride{};
949 int32_t m_srcOffset{};
950 int32_t m_indexOffset{};
951 int32_t m_elementCount{};
952 int32_t m_outputComponents{};
953 int64_t m_outputSize{};
954
955 bool m_padToVec4{false};
956 bool m_indexFormat32{true};
957 bool m_dirty{true};
958};
959
960using ExtractionStrategyVariant = std::variant<
963
964}
Definition GeometryToBufferStrategies.hpp:363
Definition GeometryToBufferStrategies.hpp:255
Definition GeometryToBufferStrategies.hpp:307
Definition GeometryToBufferStrategies.hpp:636
QShader makeCompute(const RenderState &v, QString compute)
Compile a compute shader.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:1327
Definition GeometryToBufferStrategies.hpp:152
Definition GeometryToBufferStrategies.hpp:246
Definition TinyObj.hpp:27
Global state associated to a rendering context.
Definition RenderState.hpp:37