37 static constexpr int MAX_PACKED_ATTRIBUTES = 8;
42 const halp::dynamic_gpu_geometry&
mesh,
43 std::span<const packed_attribute_spec> specs)
45 if(specs.empty() || specs.size() > MAX_PACKED_ATTRIBUTES)
47 qDebug() <<
"PackedExtractionStrategy: Invalid attribute count:" << specs.size();
51 m_hasIndexBuffer =
mesh.index.buffer >= 0;
52 m_vertexCount = m_hasIndexBuffer ?
mesh.indices :
mesh.vertices;
58 std::fill(std::begin(m_srcBufferMapping), std::end(m_srcBufferMapping), -1);
61 for(
const auto& spec : specs)
63 auto lookup = findAttribute(
mesh, spec.location);
66 qDebug() <<
"PackedExtractionStrategy: Attribute not found:"
67 << magic_enum::enum_name(spec.location);
71 auto& info = m_attributes[m_attributeCount];
72 info.is_active =
true;
73 info.src_buffer_index = lookup->input->buffer;
74 info.src_stride = lookup->binding->stride;
75 info.src_offset = lookup->attribute->byte_offset
76 +
static_cast<int32_t
>(lookup->input->byte_offset);
77 info.element_count = attributeFormatComponents(lookup->attribute->format);
78 info.is_float = isFloatFormat(lookup->attribute->format);
79 info.output_components
80 = (spec.pad_to_vec4 && info.element_count < 4 && info.is_float)
83 info.output_offset = m_outputStride;
85 m_outputStride += info.output_components *
sizeof(float);
88 int mappedIndex = m_srcBufferMapping[info.src_buffer_index];
91 if(m_srcBufferCount >= MAX_PACKED_ATTRIBUTES)
93 qDebug() <<
"PackedExtractionStrategy: Too many source buffers";
96 m_srcBufferMapping[info.src_buffer_index] = m_srcBufferCount;
97 m_srcBuffers[m_srcBufferCount]
98 =
static_cast<QRhiBuffer*
>(
mesh.buffers[info.src_buffer_index].handle);
105 if(m_attributeCount == 0)
107 qDebug() <<
"PackedExtractionStrategy: No valid attributes found";
114 if(
mesh.index.buffer < 0
115 ||
mesh.index.buffer >=
static_cast<int>(
mesh.buffers.size()))
117 qDebug() <<
"PackedExtractionStrategy: Invalid index buffer";
120 m_indexBuffer =
static_cast<QRhiBuffer*
>(
mesh.buffers[
mesh.index.buffer].handle);
121 m_indexOffset =
static_cast<int32_t
>(
mesh.index.byte_offset);
122 m_indexFormat32 = (
mesh.index.format == halp::index_format::uint32);
125 m_outputSize =
static_cast<int64_t
>(m_vertexCount) * m_outputStride;
127 if(m_outputSize == 0)
129 qDebug() <<
"PackedExtractionStrategy: Zero output size";
134 m_outputBuffer = rhi.newBuffer(
135 QRhiBuffer::Static, QRhiBuffer::StorageBuffer | QRhiBuffer::VertexBuffer,
136 static_cast<quint32
>(m_outputSize));
137 m_outputBuffer->setName(
"GeometryPacker::m_outputBuffer");
139 if(!m_outputBuffer || !m_outputBuffer->create())
141 qDebug() <<
"PackedExtractionStrategy: Failed to create output buffer";
145 return createPipeline(renderState, rhi);
149 QRhi& rhi,
const halp::dynamic_gpu_geometry&
mesh,
150 std::span<const packed_attribute_spec> specs)
153 const int32_t count = m_hasIndexBuffer ?
mesh.indices :
mesh.vertices;
154 const int64_t newSize =
static_cast<int64_t
>(count) * m_outputStride;
156 if(newSize != m_outputSize || count != m_vertexCount)
158 m_vertexCount = count;
159 m_outputSize = newSize;
163 m_outputBuffer->setSize(
static_cast<quint32
>(m_outputSize));
164 m_outputBuffer->create();
169 std::fill(std::begin(m_srcBufferMapping), std::end(m_srcBufferMapping), -1);
170 m_srcBufferCount = 0;
173 for(
const auto& spec : specs)
175 if(attrIdx >= m_attributeCount)
178 auto lookup = findAttribute(
mesh, spec.location);
182 auto& info = m_attributes[attrIdx];
183 info.src_buffer_index = lookup->input->buffer;
184 info.src_stride = lookup->binding->stride;
185 info.src_offset = lookup->attribute->byte_offset
186 +
static_cast<int32_t
>(lookup->input->byte_offset);
188 int mappedIndex = m_srcBufferMapping[info.src_buffer_index];
191 m_srcBufferMapping[info.src_buffer_index] = m_srcBufferCount;
192 m_srcBuffers[m_srcBufferCount]
193 =
static_cast<QRhiBuffer*
>(
mesh.buffers[info.src_buffer_index].handle);
201 if(m_hasIndexBuffer &&
mesh.index.buffer >= 0)
203 m_indexBuffer =
static_cast<QRhiBuffer*
>(
mesh.buffers[
mesh.index.buffer].handle);
204 m_indexOffset =
static_cast<int32_t
>(
mesh.index.byte_offset);
205 m_indexFormat32 = (
mesh.index.format == halp::index_format::uint32);
212 void release()
noexcept
215 m_pipeline =
nullptr;
220 delete m_uniformBuffer;
221 m_uniformBuffer =
nullptr;
223 delete m_placeholderBuffer;
224 m_placeholderBuffer =
nullptr;
225 std::fill(std::begin(m_boundBuffers), std::end(m_boundBuffers),
nullptr);
226 m_sourcesValid =
false;
232 std::fill(std::begin(m_srcBuffers), std::end(m_srcBuffers),
nullptr);
233 m_indexBuffer =
nullptr;
236 void runCompute(QRhi& rhi, QRhiCommandBuffer& cb, QRhiResourceUpdateBatch*& res)
238 if(!m_dirty || m_vertexCount == 0 || !m_pipeline || !m_sourcesValid)
242 struct alignas(16) AttributeParams
244 uint32_t srcBufferIndex;
245 uint32_t srcStrideBytes;
246 uint32_t srcOffsetBytes;
247 uint32_t elementCount;
248 uint32_t outputComponents;
249 uint32_t outputOffsetBytes;
254 struct alignas(64) Params
256 uint32_t vertexCount;
257 uint32_t attributeCount;
258 uint32_t outputStrideBytes;
259 uint32_t hasIndexBuffer;
260 uint32_t indexOffsetBytes;
263 alignas(64) AttributeParams attributes[MAX_PACKED_ATTRIBUTES];
265 static_assert(offsetof(Params, attributes[0]) == 64);
267 params.vertexCount =
static_cast<uint32_t
>(m_vertexCount);
268 params.attributeCount =
static_cast<uint32_t
>(m_attributeCount);
269 params.outputStrideBytes =
static_cast<uint32_t
>(m_outputStride);
270 params.hasIndexBuffer = m_hasIndexBuffer ? 1u : 0u;
271 params.indexOffsetBytes =
static_cast<uint32_t
>(m_indexOffset);
272 params.index32Bit = m_indexFormat32 ? 1u : 0u;
274 for(
int i = 0; i < MAX_PACKED_ATTRIBUTES; ++i)
276 const auto& info = m_attributes[i];
277 auto& ap = params.attributes[i];
282 =
static_cast<uint32_t
>(m_srcBufferMapping[info.src_buffer_index]);
283 ap.srcStrideBytes =
static_cast<uint32_t
>(info.src_stride);
284 ap.srcOffsetBytes =
static_cast<uint32_t
>(info.src_offset);
285 ap.elementCount =
static_cast<uint32_t
>(info.element_count);
286 ap.outputComponents =
static_cast<uint32_t
>(info.output_components);
287 ap.outputOffsetBytes =
static_cast<uint32_t
>(info.output_offset);
296 res->updateDynamicBuffer(m_uniformBuffer, 0,
sizeof(params), ¶ms);
298 cb.beginComputePass(res);
299 cb.setComputePipeline(m_pipeline);
300 cb.setShaderResources(m_srb);
302 const int workgroups = (m_vertexCount + 255) / 256;
303 cb.dispatch(workgroups, 1, 1);
307 res = rhi.nextResourceUpdateBatch();
315 .buffer = m_outputBuffer,
317 .size = m_outputSize,
321 [[nodiscard]] int32_t outputStride()
const noexcept {
return m_outputStride; }
323 [[nodiscard]] int32_t attributeCount()
const noexcept {
return m_attributeCount; }
327 return m_attributes[index];
330 [[nodiscard]]
static constexpr bool needsCompute()
noexcept {
return true; }
335 m_uniformBuffer = rhi.newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 512);
336 m_uniformBuffer->setName(
"GeometryPacker::m_uniformBuffer");
338 if(!m_uniformBuffer || !m_uniformBuffer->create())
340 qDebug() <<
"PackedExtractionStrategy: UBO creation failed";
344 static const QString shaderCode = QStringLiteral(R
"(#version 450
346layout(local_size_x = 256) in;
348struct AttributeParams {
353 uint outputComponents;
354 uint outputOffsetBytes;
359layout(std140, binding = 0) uniform Params {
362 uint outputStrideBytes;
364 uint indexOffsetBytes;
367 AttributeParams attributes[8];
370layout(std430, binding = 1) readonly buffer SrcBuffer0 { uint src0[]; };
371layout(std430, binding = 2) readonly buffer SrcBuffer1 { uint src1[]; };
372layout(std430, binding = 3) readonly buffer SrcBuffer2 { uint src2[]; };
373layout(std430, binding = 4) readonly buffer SrcBuffer3 { uint src3[]; };
374layout(std430, binding = 5) readonly buffer SrcBuffer4 { uint src4[]; };
375layout(std430, binding = 6) readonly buffer SrcBuffer5 { uint src5[]; };
376layout(std430, binding = 7) readonly buffer SrcBuffer6 { uint src6[]; };
377layout(std430, binding = 8) readonly buffer SrcBuffer7 { uint src7[]; };
379layout(std430, binding = 9) readonly buffer IndexBuffer { uint index_data[]; };
381layout(std430, binding = 10) writeonly buffer DstBuffer { uint dst_data[]; };
383uint readSrcData(uint bufferIndex, uint wordIndex)
387 case 0: return src0[wordIndex];
388 case 1: return src1[wordIndex];
389 case 2: return src2[wordIndex];
390 case 3: return src3[wordIndex];
391 case 4: return src4[wordIndex];
392 case 5: return src5[wordIndex];
393 case 6: return src6[wordIndex];
394 case 7: return src7[wordIndex];
399uint readIndex(uint i)
403 uint wordIndex = (indexOffsetBytes / 4) + i;
404 return index_data[wordIndex];
408 uint bytePos = indexOffsetBytes + i * 2;
409 uint wordIndex = bytePos / 4;
410 uint word = index_data[wordIndex];
411 uint shift = (bytePos % 4) * 8;
412 return (word >> shift) & 0xFFFFu;
418 uint outputIdx = gl_GlobalInvocationID.x;
419 if (outputIdx >= vertexCount)
422 // Get source vertex index (may differ from output index if indexed)
423 uint srcVertexIdx = hasIndexBuffer != 0 ? readIndex(outputIdx) : outputIdx;
425 // Output base position (in uints, since outputStrideBytes is in bytes)
426 uint dstBase = (outputIdx * outputStrideBytes) / 4;
428 // Process each attribute
429 for (uint a = 0; a < attributeCount; ++a)
431 AttributeParams attr = attributes[a];
432 if (attr.isActive == 0)
436 uint srcBase = (srcVertexIdx * attr.srcStrideBytes + attr.srcOffsetBytes) / 4;
438 // Destination position for this attribute
439 uint attrDstBase = dstBase + (attr.outputOffsetBytes / 4);
442 for (uint i = 0; i < attr.elementCount; ++i)
444 uint value = readSrcData(attr.srcBufferIndex, srcBase + i);
445 dst_data[attrDstBase + i] = value;
448 // Pad to vec4 if needed
449 if (attr.outputComponents > attr.elementCount)
451 for (uint i = attr.elementCount; i < attr.outputComponents; ++i)
453 // w = 1.0f (0x3f800000), others = 0
454 dst_data[attrDstBase + i] = (i == 3) ? 0x3f800000u : 0u;
462 if(!shader.isValid())
464 qDebug() <<
"PackedExtractionStrategy: Shader compilation failed";
468 m_placeholderBuffer = rhi.newBuffer(QRhiBuffer::Static, QRhiBuffer::StorageBuffer, 16);
469 m_placeholderBuffer->setName(
"GeometryPacker::m_placeholderBuffer");
470 if(!m_placeholderBuffer->create())
472 qDebug() <<
"PackedExtractionStrategy: Placeholder buffer creation failed";
476 m_srb = rhi.newShaderResourceBindings();
481 qDebug() <<
"PackedExtractionStrategy: SRB creation failed";
485 m_pipeline = rhi.newComputePipeline();
486 m_pipeline->setShaderResourceBindings(m_srb);
487 m_pipeline->setShaderStage({QRhiShaderStage::Compute, shader});
489 if(!m_pipeline->create())
491 qDebug() <<
"PackedExtractionStrategy: Pipeline creation failed";
499 void updateBindings()
504 m_sourcesValid = m_outputBuffer !=
nullptr;
510 QRhiBuffer*
const placeholderBuffer = m_placeholderBuffer;
511 for(
int i = 0; i < m_srcBufferCount; ++i)
512 if(!m_srcBuffers[i] || m_srcBuffers[i] == m_outputBuffer)
513 m_sourcesValid =
false;
515 QRhiBuffer* buffers[MAX_PACKED_ATTRIBUTES + 2]{};
516 for(
int i = 0; i < MAX_PACKED_ATTRIBUTES; ++i)
518 QRhiBuffer* buf = i < m_srcBufferCount ? m_srcBuffers[i] :
nullptr;
519 buffers[i] = buf && buf != m_outputBuffer ? buf : placeholderBuffer;
522 QRhiBuffer* idxBuf = placeholderBuffer;
525 if(m_indexBuffer && m_indexBuffer != m_outputBuffer)
526 idxBuf = m_indexBuffer;
528 m_sourcesValid =
false;
530 buffers[MAX_PACKED_ATTRIBUTES] = idxBuf;
531 buffers[MAX_PACKED_ATTRIBUTES + 1] = m_outputBuffer;
533 if(std::equal(std::begin(buffers), std::end(buffers), std::begin(m_boundBuffers)))
535 std::copy(std::begin(buffers), std::end(buffers), std::begin(m_boundBuffers));
537 QVarLengthArray<QRhiShaderResourceBinding, 12> bindings;
540 QRhiShaderResourceBinding::uniformBuffer(
541 0, QRhiShaderResourceBinding::ComputeStage, m_uniformBuffer));
543 for(
int i = 0; i < MAX_PACKED_ATTRIBUTES; ++i)
546 QRhiShaderResourceBinding::bufferLoad(
547 1 + i, QRhiShaderResourceBinding::ComputeStage, buffers[i]));
551 QRhiShaderResourceBinding::bufferLoad(
552 9, QRhiShaderResourceBinding::ComputeStage, idxBuf));
555 QRhiShaderResourceBinding::bufferStore(
556 10, QRhiShaderResourceBinding::ComputeStage, m_outputBuffer));
558 m_srb->setBindings(bindings.cbegin(), bindings.cend());
564 QRhiBuffer* m_srcBuffers[MAX_PACKED_ATTRIBUTES]{};
565 int m_srcBufferMapping[32]{};
566 int m_srcBufferCount{};
568 QRhiBuffer* m_indexBuffer{};
569 QRhiBuffer* m_outputBuffer{};
570 QRhiShaderResourceBindings* m_srb{};
571 QRhiComputePipeline* m_pipeline{};
572 QRhiBuffer* m_uniformBuffer{};
573 QRhiBuffer* m_placeholderBuffer{};
574 QRhiBuffer* m_boundBuffers[MAX_PACKED_ATTRIBUTES + 2]{};
575 bool m_sourcesValid{
false};
578 int32_t m_attributeCount{};
579 int32_t m_vertexCount{};
580 int32_t m_outputStride{};
581 int32_t m_indexOffset{};
582 int64_t m_outputSize{};
584 bool m_hasIndexBuffer{
false};
585 bool m_indexFormat32{
true};