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 // The spec can name a range the buffer doesn't (or no longer) cover: an
272 // upstream that shrank or replaced its buffer while playing. Release
273 // builds compiled the assert out and bound past the buffer's end.
274 if(byte_offset < 0 || byte_size < 0
275 || int64_t(m_buffer->size()) < byte_size + byte_offset)
276 {
277 qWarning() << "DirectBufferReferenceStrategy: range" << byte_offset << "+"
278 << byte_size << "exceeds buffer size" << m_buffer->size();
279 m_buffer = nullptr;
280 m_offset = 0;
281 m_size = 0;
282 return false;
283 }
284 return true;
285 }
286
287 void
288 update(QRhi&, const halp::dynamic_gpu_geometry&, const attribute_lookup& lookup, bool)
289 {
290 }
291
292 void release() noexcept
293 {
294 m_buffer = nullptr;
295 m_offset = 0;
296 m_size = 0;
297 }
298
299 void runCompute(QRhi&, QRhiCommandBuffer&, QRhiResourceUpdateBatch*&) { }
300
301 [[nodiscard]] gpu_buffer_view output() const noexcept
302 {
303 return {
304 .buffer = m_buffer,
305 .offset = m_offset,
306 .size = m_size,
307 };
308 }
309
310 [[nodiscard]] static constexpr bool needsCompute() noexcept { return false; }
311
312private:
313 QRhiBuffer* m_buffer{};
314 int64_t m_offset{};
315 int64_t m_size{};
316};
317
319{
320public:
321 bool init(
322 const score::gfx::RenderState& renderState, QRhi& rhi,
323 const halp::dynamic_gpu_geometry& mesh, const attribute_lookup& lookup,
324 bool /*padToVec4*/)
325 {
326 m_buffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
327 m_offset = lookup.input->byte_offset;
328 m_size = static_cast<int64_t>(lookup.attribute_size) * mesh.vertices;
329
330 if(!m_buffer)
331 {
332 qDebug() << "DirectReferenceStrategy: Null buffer handle";
333 return false;
334 }
335 return true;
336 }
337
338 void update(
339 QRhi& /*rhi*/, const halp::dynamic_gpu_geometry& mesh,
340 const attribute_lookup& lookup, bool /*padToVec4*/)
341 {
342 m_buffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
343 m_offset = lookup.input->byte_offset;
344 m_size = static_cast<int64_t>(lookup.attribute_size) * mesh.vertices;
345 }
346
347 void release() noexcept
348 {
349 // We don't own the buffer
350 m_buffer = nullptr;
351 m_offset = 0;
352 m_size = 0;
353 }
354
355 void runCompute(QRhi&, QRhiCommandBuffer&, QRhiResourceUpdateBatch*&) { }
356
357 [[nodiscard]] gpu_buffer_view output() const noexcept
358 {
359 return {
360 .buffer = m_buffer,
361 .offset = m_offset,
362 .size = m_size,
363 };
364 }
365
366 [[nodiscard]] static constexpr bool needsCompute() noexcept { return false; }
367
368private:
369 QRhiBuffer* m_buffer{};
370 int64_t m_offset{};
371 int64_t m_size{};
372};
373
375{
376public:
377 bool init(
378 const score::gfx::RenderState& renderState, QRhi& rhi,
379 const halp::dynamic_gpu_geometry& mesh, const attribute_lookup& lookup,
380 bool padToVec4)
381 {
382 m_vertexCount = mesh.vertices;
383 m_srcStride = lookup.binding->stride;
384 m_srcOffset = lookup.attribute->byte_offset
385 + static_cast<int32_t>(lookup.input->byte_offset);
386 m_elementCount = attributeFormatComponents(lookup.attribute->format);
387 m_padToVec4
388 = padToVec4 && m_elementCount < 4 && isFloatFormat(lookup.attribute->format);
389
390 const int32_t outputComponents = m_padToVec4 ? 4 : m_elementCount;
391 m_outputComponents = outputComponents;
392 m_outputSize
393 = static_cast<int64_t>(m_vertexCount) * outputComponents * sizeof(float);
394
395 if(m_outputSize == 0)
396 {
397 qDebug() << "ComputeExtractionStrategy: Zero output size";
398 return false;
399 }
400
401 // Create output buffer
402 m_outputBuffer = rhi.newBuffer(
403 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
404 static_cast<quint32>(m_outputSize));
405 m_outputBuffer->setName("ComputeExtractionStrategy::m_outputBuffer");
406
407 if(!m_outputBuffer || !m_outputBuffer->create())
408 {
409 qDebug() << "ComputeExtractionStrategy: Failed to create output buffer";
410 return false;
411 }
412
413 m_srcBuffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
414 if(!m_srcBuffer)
415 {
416 qDebug() << "ComputeExtractionStrategy: Null source buffer";
417 return false;
418 }
419
420 return createPipeline(renderState, rhi);
421 }
422
423 void update(
424 QRhi& rhi, const halp::dynamic_gpu_geometry& mesh, const attribute_lookup& lookup,
425 bool padToVec4)
426 {
427 m_srcBuffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
428 m_srcStride = lookup.binding->stride;
429 m_srcOffset = lookup.attribute->byte_offset
430 + static_cast<int32_t>(lookup.input->byte_offset);
431
432 const bool newPadToVec4
433 = padToVec4 && m_elementCount < 4 && isFloatFormat(lookup.attribute->format);
434 const int32_t newOutputComponents = newPadToVec4 ? 4 : m_elementCount;
435 const int64_t newSize
436 = static_cast<int64_t>(mesh.vertices) * newOutputComponents * sizeof(float);
437
438 // Check if buffer needs resize
439 if(newSize != m_outputSize || mesh.vertices != m_vertexCount
440 || newPadToVec4 != m_padToVec4)
441 {
442 m_vertexCount = mesh.vertices;
443 m_padToVec4 = newPadToVec4;
444 m_outputComponents = newOutputComponents;
445 m_outputSize = newSize;
446
447 if(m_outputSize > 0)
448 {
449 m_outputBuffer->setSize(static_cast<quint32>(m_outputSize));
450 m_outputBuffer->create();
451 }
452 }
453
454 // Rebind if source buffer changed. Must match createPipeline()'s layout
455 // exactly (uniform@0, src@1, out@2) or the SRB becomes layout-incompatible
456 // with the pipeline and the shader reads Params from the wrong binding.
457 if(m_srb)
458 {
459 m_srb->setBindings({
460 QRhiShaderResourceBinding::uniformBuffer(
461 0, QRhiShaderResourceBinding::ComputeStage, m_uniformBuffer),
462 QRhiShaderResourceBinding::bufferLoad(
463 1, QRhiShaderResourceBinding::ComputeStage, m_srcBuffer),
464 QRhiShaderResourceBinding::bufferStore(
465 2, QRhiShaderResourceBinding::ComputeStage, m_outputBuffer),
466 });
467 m_srb->create();
468 }
469
470 m_dirty = true;
471 }
472
473 void release() noexcept
474 {
475 delete m_uniformBuffer;
476 m_uniformBuffer = nullptr;
477
478 delete m_pipeline;
479 m_pipeline = nullptr;
480
481 delete m_srb;
482 m_srb = nullptr;
483
484 delete m_outputBuffer;
485 m_outputBuffer = nullptr;
486
487 m_srcBuffer = nullptr;
488 }
489
490 void runCompute(QRhi& rhi, QRhiCommandBuffer& cb, QRhiResourceUpdateBatch*& res)
491 {
492 if(!m_dirty || m_vertexCount == 0 || !m_pipeline)
493 return;
494
495 struct alignas(16) Params
496 {
497 uint32_t vertexCount;
498 uint32_t srcStrideBytes;
499 uint32_t srcOffsetBytes;
500 uint32_t elementCount;
501 uint32_t padToVec4;
502 uint32_t _pad[3]; // Padding to maintain alignment
503 } params{
504 static_cast<uint32_t>(m_vertexCount),
505 static_cast<uint32_t>(m_srcStride),
506 static_cast<uint32_t>(m_srcOffset),
507 static_cast<uint32_t>(m_elementCount),
508 m_padToVec4 ? 1u : 0u,
509 {0, 0, 0}};
510
511 res->updateDynamicBuffer(m_uniformBuffer, 0, sizeof(params), &params);
512
513 cb.beginComputePass(res);
514 cb.setComputePipeline(m_pipeline);
515 cb.setShaderResources(m_srb);
516
517 const int workgroups = (m_vertexCount + 255) / 256;
518 cb.dispatch(workgroups, 1, 1);
519
520 cb.endComputePass();
521
522 // Get new resource batch after compute pass
523 res = rhi.nextResourceUpdateBatch();
524
525 m_dirty = false;
526 }
527
528 [[nodiscard]] gpu_buffer_view output() const noexcept
529 {
530 return {
531 .buffer = m_outputBuffer,
532 .offset = 0,
533 .size = m_outputSize,
534 };
535 }
536
537 [[nodiscard]] static constexpr bool needsCompute() noexcept { return true; }
538
539private:
540 bool createPipeline(const score::gfx::RenderState& renderState, QRhi& rhi)
541 {
542 static const QString shaderCode = QStringLiteral(R"(#version 450
543
544layout(local_size_x = 256) in;
545
546layout(std140, binding = 0) uniform Params {
547 uint vertexCount;
548 uint srcStrideBytes;
549 uint srcOffsetBytes;
550 uint elementCount;
551 uint padToVec4;
552};
553
554layout(std430, binding = 1) readonly buffer SrcBuffer {
555 uint src_data[];
556};
557
558layout(std430, binding = 2) writeonly buffer DstBuffer {
559 uint dst_data[];
560};
561
562void main()
563{
564 uint idx = gl_GlobalInvocationID.x;
565 if (idx >= vertexCount)
566 return;
567
568 uint srcBase = (idx * srcStrideBytes + srcOffsetBytes) / 4;
569 uint dstComponents = padToVec4 != 0 ? 4 : elementCount;
570 uint dstBase = idx * dstComponents;
571
572 for (uint i = 0; i < elementCount; ++i)
573 dst_data[dstBase + i] = src_data[srcBase + i];
574
575 if (padToVec4 != 0)
576 {
577 for (uint i = elementCount; i < 4; ++i)
578 dst_data[dstBase + i] = (i == 3) ? 0x3f800000u : 0u;
579 }
580}
581)");
582
583 QShader shader = score::gfx::makeCompute(renderState, shaderCode);
584 if(!shader.isValid())
585 {
586 qDebug() << "ComputeExtractionStrategy: Shader compilation failed";
587 return false;
588 }
589
590 // Create uniform buffer (aligned to 256 bytes for compatibility)
591 m_uniformBuffer = rhi.newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 256);
592 m_uniformBuffer->setName("ComputeExtractionStrategy::m_uniformBuffer");
593
594 if(!m_uniformBuffer || !m_uniformBuffer->create())
595 {
596 qDebug() << "ComputeExtractionStrategy: UBO creation failed";
597 return false;
598 }
599
600 m_srb = rhi.newShaderResourceBindings();
601 m_srb->setBindings({
602 QRhiShaderResourceBinding::uniformBuffer(
603 0, QRhiShaderResourceBinding::ComputeStage, m_uniformBuffer),
604 QRhiShaderResourceBinding::bufferLoad(
605 1, QRhiShaderResourceBinding::ComputeStage, m_srcBuffer),
606 QRhiShaderResourceBinding::bufferStore(
607 2, QRhiShaderResourceBinding::ComputeStage, m_outputBuffer),
608 });
609
610 if(!m_srb->create())
611 {
612 qDebug() << "ComputeExtractionStrategy: SRB creation failed";
613 return false;
614 }
615
616 m_pipeline = rhi.newComputePipeline();
617 m_pipeline->setShaderResourceBindings(m_srb);
618 m_pipeline->setShaderStage({QRhiShaderStage::Compute, shader});
619
620 if(!m_pipeline->create())
621 {
622 qDebug() << "ComputeExtractionStrategy: Pipeline creation failed";
623 return false;
624 }
625
626 m_dirty = true;
627 return true;
628 }
629
630 QRhiBuffer* m_srcBuffer{};
631 QRhiBuffer* m_uniformBuffer{};
632 QRhiBuffer* m_outputBuffer{};
633 QRhiShaderResourceBindings* m_srb{};
634 QRhiComputePipeline* m_pipeline{};
635
636 int32_t m_vertexCount{};
637 int32_t m_srcStride{};
638 int32_t m_srcOffset{};
639 int32_t m_elementCount{};
640 int32_t m_outputComponents{};
641 int64_t m_outputSize{};
642
643 bool m_padToVec4{false};
644 bool m_dirty{true};
645};
646
648{
649public:
650 bool init(
651 const score::gfx::RenderState& renderState, QRhi& rhi,
652 const halp::dynamic_gpu_geometry& mesh, const attribute_lookup& lookup,
653 bool padToVec4)
654 {
655 if(mesh.index.buffer < 0
656 || mesh.index.buffer >= static_cast<int>(mesh.buffers.size()))
657 {
658 qDebug() << "IndexedExtractionStrategy: Invalid index buffer";
659 return false;
660 }
661
662 // For indexed geometry, we output one vertex per index
663 m_indexCount = mesh.vertices;
664 m_srcStride = lookup.binding->stride;
665 m_srcOffset = lookup.attribute->byte_offset
666 + static_cast<int32_t>(lookup.input->byte_offset);
667 m_elementCount = attributeFormatComponents(lookup.attribute->format);
668 m_padToVec4
669 = padToVec4 && m_elementCount < 4 && isFloatFormat(lookup.attribute->format);
670 m_indexFormat32 = (mesh.index.format == halp::index_format::uint32);
671 m_indexOffset = static_cast<int32_t>(mesh.index.byte_offset);
672
673 const int32_t outputComponents = m_padToVec4 ? 4 : m_elementCount;
674 m_outputComponents = outputComponents;
675 m_outputSize = static_cast<int64_t>(m_indexCount) * outputComponents * sizeof(float);
676
677 if(m_outputSize == 0)
678 {
679 qDebug() << "IndexedExtractionStrategy: Zero output size";
680 return false;
681 }
682
683 m_outputBuffer = rhi.newBuffer(
684 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
685 static_cast<quint32>(m_outputSize));
686 m_outputBuffer->setName("IndexedExtractionStrategy::m_outputBuffer");
687
688 if(!m_outputBuffer || !m_outputBuffer->create())
689 {
690 qDebug() << "IndexedExtractionStrategy: Failed to create output buffer";
691 return false;
692 }
693
694 m_srcBuffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
695 m_indexBuffer = static_cast<QRhiBuffer*>(mesh.buffers[mesh.index.buffer].handle);
696
697 if(!m_srcBuffer || !m_indexBuffer)
698 {
699 qDebug() << "IndexedExtractionStrategy: Null source or index buffer";
700 return false;
701 }
702
703 return createPipeline(renderState, rhi);
704 }
705
706 void update(
707 QRhi& rhi, const halp::dynamic_gpu_geometry& mesh, const attribute_lookup& lookup,
708 bool padToVec4)
709 {
710 m_srcBuffer = static_cast<QRhiBuffer*>(lookup.buffer->handle);
711 m_srcStride = lookup.binding->stride;
712 m_srcOffset = lookup.attribute->byte_offset
713 + static_cast<int32_t>(lookup.input->byte_offset);
714
715 if(mesh.index.buffer >= 0
716 && mesh.index.buffer < static_cast<int>(mesh.buffers.size()))
717 {
718 m_indexBuffer = static_cast<QRhiBuffer*>(mesh.buffers[mesh.index.buffer].handle);
719 m_indexOffset = static_cast<int32_t>(mesh.index.byte_offset);
720 m_indexFormat32 = (mesh.index.format == halp::index_format::uint32);
721 }
722
723 const bool newPadToVec4
724 = padToVec4 && m_elementCount < 4 && isFloatFormat(lookup.attribute->format);
725 const int32_t newOutputComponents = newPadToVec4 ? 4 : m_elementCount;
726 const int64_t newSize
727 = static_cast<int64_t>(mesh.vertices) * newOutputComponents * sizeof(float);
728
729 if(newSize != m_outputSize || mesh.vertices != m_indexCount
730 || newPadToVec4 != m_padToVec4)
731 {
732 m_indexCount = mesh.vertices;
733 m_padToVec4 = newPadToVec4;
734 m_outputComponents = newOutputComponents;
735 m_outputSize = newSize;
736
737 if(m_outputSize > 0)
738 {
739 m_outputBuffer->setSize(static_cast<quint32>(m_outputSize));
740 m_outputBuffer->create();
741 }
742 }
743
744 // Rebind if source buffer changed. Must match createPipeline()'s layout
745 // exactly (uniform@0, src@1, index@2, out@3) or the SRB becomes
746 // layout-incompatible with the pipeline and the shader reads Params from
747 // the wrong binding.
748 if(m_srb)
749 {
750 m_srb->setBindings({
751 QRhiShaderResourceBinding::uniformBuffer(
752 0, QRhiShaderResourceBinding::ComputeStage, m_uniformBuffer),
753 QRhiShaderResourceBinding::bufferLoad(
754 1, QRhiShaderResourceBinding::ComputeStage, m_srcBuffer),
755 QRhiShaderResourceBinding::bufferLoad(
756 2, QRhiShaderResourceBinding::ComputeStage, m_indexBuffer),
757 QRhiShaderResourceBinding::bufferStore(
758 3, QRhiShaderResourceBinding::ComputeStage, m_outputBuffer),
759 });
760 m_srb->create();
761 }
762
763 m_dirty = true;
764 }
765
766 void release() noexcept
767 {
768 delete m_pipeline;
769 m_pipeline = nullptr;
770
771 delete m_srb;
772 m_srb = nullptr;
773
774 delete m_uniformBuffer;
775 m_uniformBuffer = nullptr;
776
777 delete m_outputBuffer;
778 m_outputBuffer = nullptr;
779
780 m_srcBuffer = nullptr;
781 m_indexBuffer = nullptr;
782 }
783
784 void runCompute(QRhi& rhi, QRhiCommandBuffer& cb, QRhiResourceUpdateBatch*& res)
785 {
786 if(!m_dirty || m_indexCount == 0 || !m_pipeline)
787 return;
788
789 struct alignas(16) Params
790 {
791 uint32_t indexCount;
792 uint32_t srcStrideBytes;
793 uint32_t srcOffsetBytes;
794 uint32_t elementCount;
795 uint32_t padToVec4;
796 uint32_t indexOffsetBytes;
797 uint32_t index32Bit;
798 uint32_t _pad;
799 } params{
800 static_cast<uint32_t>(m_indexCount),
801 static_cast<uint32_t>(m_srcStride),
802 static_cast<uint32_t>(m_srcOffset),
803 static_cast<uint32_t>(m_elementCount),
804 m_padToVec4 ? 1u : 0u,
805 static_cast<uint32_t>(m_indexOffset),
806 m_indexFormat32 ? 1u : 0u,
807 0};
808
809 res->updateDynamicBuffer(m_uniformBuffer, 0, sizeof(params), &params);
810
811 cb.beginComputePass(res);
812 cb.setComputePipeline(m_pipeline);
813 cb.setShaderResources(m_srb);
814
815 const int workgroups = (m_indexCount + 255) / 256;
816 cb.dispatch(workgroups, 1, 1);
817
818 cb.endComputePass();
819
820 res = rhi.nextResourceUpdateBatch();
821
822 m_dirty = false;
823 }
824
825 [[nodiscard]] gpu_buffer_view output() const noexcept
826 {
827 return {
828 .buffer = m_outputBuffer,
829 .offset = 0,
830 .size = m_outputSize,
831 };
832 }
833
834 [[nodiscard]] static constexpr bool needsCompute() noexcept { return true; }
835
836private:
837 bool createPipeline(const score::gfx::RenderState& renderState, QRhi& rhi)
838 {
839 static const QString shaderCode = QStringLiteral(R"(#version 450
840
841layout(local_size_x = 256) in;
842
843layout(std140, binding = 0) uniform Params {
844 uint indexCount;
845 uint srcStrideBytes;
846 uint srcOffsetBytes;
847 uint elementCount;
848 uint padToVec4;
849 uint indexOffsetBytes;
850 uint index32Bit;
851};
852
853layout(std430, binding = 1) readonly buffer SrcBuffer {
854 uint src_data[];
855};
856
857layout(std430, binding = 2) readonly buffer IndexBuffer {
858 uint index_data[];
859};
860
861layout(std430, binding = 3) writeonly buffer DstBuffer {
862 uint dst_data[];
863};
864
865uint readIndex(uint i)
866{
867 if (index32Bit != 0)
868 {
869 uint wordIndex = (indexOffsetBytes / 4) + i;
870 return index_data[wordIndex];
871 }
872 else
873 {
874 uint bytePos = indexOffsetBytes + i * 2;
875 uint wordIndex = bytePos / 4;
876 uint word = index_data[wordIndex];
877 uint shift = (bytePos % 4) * 8;
878 return (word >> shift) & 0xFFFFu;
879 }
880}
881
882void main()
883{
884 uint outputIdx = gl_GlobalInvocationID.x;
885 if (outputIdx >= indexCount)
886 return;
887
888 uint vertexIdx = readIndex(outputIdx);
889 uint srcBase = (vertexIdx * srcStrideBytes + srcOffsetBytes) / 4;
890 uint dstComponents = padToVec4 != 0 ? 4 : elementCount;
891 uint dstBase = outputIdx * dstComponents;
892
893 for (uint i = 0; i < elementCount; ++i)
894 dst_data[dstBase + i] = src_data[srcBase + i];
895
896 if (padToVec4 != 0)
897 {
898 for (uint i = elementCount; i < 4; ++i)
899 dst_data[dstBase + i] = (i == 3) ? 0x3f800000u : 0u;
900 }
901}
902)");
903
904 QShader shader = score::gfx::makeCompute(renderState, shaderCode);
905 if(!shader.isValid())
906 {
907 qDebug() << "IndexedExtractionStrategy: Shader compilation failed";
908 return false;
909 }
910
911 m_uniformBuffer = rhi.newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 256);
912 m_uniformBuffer->setName("IndexedExtractionStrategy::m_uniformBuffer");
913
914 if(!m_uniformBuffer || !m_uniformBuffer->create())
915 {
916 qDebug() << "IndexedExtractionStrategy: UBO creation failed";
917 return false;
918 }
919
920 m_srb = rhi.newShaderResourceBindings();
921 m_srb->setBindings({
922 QRhiShaderResourceBinding::uniformBuffer(
923 0, QRhiShaderResourceBinding::ComputeStage, m_uniformBuffer),
924 QRhiShaderResourceBinding::bufferLoad(
925 1, QRhiShaderResourceBinding::ComputeStage, m_srcBuffer),
926 QRhiShaderResourceBinding::bufferLoad(
927 2, QRhiShaderResourceBinding::ComputeStage, m_indexBuffer),
928 QRhiShaderResourceBinding::bufferStore(
929 3, QRhiShaderResourceBinding::ComputeStage, m_outputBuffer),
930 });
931
932 if(!m_srb->create())
933 {
934 qDebug() << "IndexedExtractionStrategy: SRB creation failed";
935 return false;
936 }
937
938 m_pipeline = rhi.newComputePipeline();
939 m_pipeline->setShaderResourceBindings(m_srb);
940 m_pipeline->setShaderStage({QRhiShaderStage::Compute, shader});
941
942 if(!m_pipeline->create())
943 {
944 qDebug() << "IndexedExtractionStrategy: Pipeline creation failed";
945 return false;
946 }
947
948 m_dirty = true;
949 return true;
950 }
951
952 QRhiBuffer* m_srcBuffer{};
953 QRhiBuffer* m_uniformBuffer{};
954 QRhiBuffer* m_indexBuffer{};
955 QRhiBuffer* m_outputBuffer{};
956 QRhiShaderResourceBindings* m_srb{};
957 QRhiComputePipeline* m_pipeline{};
958
959 int32_t m_indexCount{};
960 int32_t m_srcStride{};
961 int32_t m_srcOffset{};
962 int32_t m_indexOffset{};
963 int32_t m_elementCount{};
964 int32_t m_outputComponents{};
965 int64_t m_outputSize{};
966
967 bool m_padToVec4{false};
968 bool m_indexFormat32{true};
969 bool m_dirty{true};
970};
971
972using ExtractionStrategyVariant = std::variant<
975
976}
Definition GeometryToBufferStrategies.hpp:375
Definition GeometryToBufferStrategies.hpp:255
Definition GeometryToBufferStrategies.hpp:319
Definition GeometryToBufferStrategies.hpp:648
QShader makeCompute(const RenderState &v, QString compute)
Compile a compute shader.
Definition score-plugin-gfx/Gfx/Graph/Utils.cpp:1806
Definition GeometryToBufferStrategies.hpp:152
Definition GeometryToBufferStrategies.hpp:246
Definition TinyObj.hpp:27
Global state associated to a rendering context.
Definition RenderState.hpp:40