Revision control

Copy as Markdown

// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "qssggltfaccessorreader_p.h"
#include <cstring>
QT_BEGIN_NAMESPACE
using namespace QSSGGltf;
/*!
\namespace QSSGGltfAccessorReader
\internal
Typed access to glTF accessor data. All functions compose the buffer view
byte stride (interleaved data), the accessor byte offset, and sparse
substitution, and return tightly packed copies. An accessor without a
buffer view yields zeros, as the specification requires.
*/
namespace QSSGGltfAccessorReader {
namespace {
// Pointer to the first element and the stride between elements for data in
// a buffer view; null when the accessor has no buffer view.
struct DataView {
const char *data = nullptr;
qsizetype stride = 0;
};
DataView viewForAccessor(const QSSGGltfDocument &document, const Accessor &accessor)
{
if (accessor.bufferView < 0)
return {};
const BufferView &view = document.bufferViews.at(accessor.bufferView);
const Buffer &buffer = document.buffers.at(view.buffer);
DataView result;
result.stride = view.byteStride > 0 ? view.byteStride : accessor.elementByteSize();
result.data = buffer.data.constData() + view.byteOffset + accessor.byteOffset;
return result;
}
quint32 readIndexValue(const char *data, Accessor::ComponentType componentType)
{
switch (componentType) {
case Accessor::ComponentType::UnsignedByte:
return *reinterpret_cast<const quint8 *>(data);
case Accessor::ComponentType::UnsignedShort: {
quint16 value;
std::memcpy(&value, data, sizeof(value));
return value;
}
case Accessor::ComponentType::UnsignedInt: {
quint32 value;
std::memcpy(&value, data, sizeof(value));
return value;
}
default:
break;
}
return 0;
}
void applySparse(const QSSGGltfDocument &document, const Accessor &accessor, QByteArray &packed)
{
const Accessor::Sparse &sparse = *accessor.sparse;
const qsizetype elementSize = accessor.elementByteSize();
const BufferView &indicesView = document.bufferViews.at(sparse.indicesBufferView);
const Buffer &indicesBuffer = document.buffers.at(indicesView.buffer);
const char *indices = indicesBuffer.data.constData() + indicesView.byteOffset + sparse.indicesByteOffset;
const qsizetype indexSize = Accessor::componentByteSize(sparse.indicesComponentType);
const BufferView &valuesView = document.bufferViews.at(sparse.valuesBufferView);
const Buffer &valuesBuffer = document.buffers.at(valuesView.buffer);
const char *values = valuesBuffer.data.constData() + valuesView.byteOffset + sparse.valuesByteOffset;
for (qint64 i = 0; i < sparse.count; ++i) {
const quint32 targetElement = readIndexValue(indices + i * indexSize, sparse.indicesComponentType);
if (qint64(targetElement) >= accessor.count)
continue; // structurally invalid substitution index; skip
std::memcpy(packed.data() + qsizetype(targetElement) * elementSize, values + i * elementSize, elementSize);
}
}
float componentToFloat(const char *data, Accessor::ComponentType componentType, bool normalized)
{
switch (componentType) {
case Accessor::ComponentType::Byte: {
const qint8 value = *reinterpret_cast<const qint8 *>(data);
return normalized ? std::max(value / 127.0f, -1.0f) : float(value);
}
case Accessor::ComponentType::UnsignedByte: {
const quint8 value = *reinterpret_cast<const quint8 *>(data);
return normalized ? value / 255.0f : float(value);
}
case Accessor::ComponentType::Short: {
qint16 value;
std::memcpy(&value, data, sizeof(value));
return normalized ? std::max(value / 32767.0f, -1.0f) : float(value);
}
case Accessor::ComponentType::UnsignedShort: {
quint16 value;
std::memcpy(&value, data, sizeof(value));
return normalized ? value / 65535.0f : float(value);
}
case Accessor::ComponentType::UnsignedInt: {
quint32 value;
std::memcpy(&value, data, sizeof(value));
return float(value);
}
case Accessor::ComponentType::Float: {
float value;
std::memcpy(&value, data, sizeof(value));
return value;
}
}
return 0.0f;
}
} // namespace
/*!
\internal
Returns the element data of accessor \a accessorIndex in \a document,
packed to accessor.elementByteSize() per element.
*/
QByteArray readPacked(const QSSGGltfDocument &document, int accessorIndex)
{
if (accessorIndex < 0 || accessorIndex >= document.accessors.size())
return {};
const Accessor &accessor = document.accessors.at(accessorIndex);
const qsizetype elementSize = accessor.elementByteSize();
QByteArray packed(accessor.count * elementSize, '\0');
const DataView view = viewForAccessor(document, accessor);
if (view.data) {
if (view.stride == elementSize) {
std::memcpy(packed.data(), view.data, packed.size());
} else {
// Bound the loop by what was actually allocated rather than by
// accessor.count, so that this stays inside the destination even
// if the count and the allocation ever disagree
const qint64 count = packed.size() / elementSize;
for (qint64 i = 0; i < count; ++i)
std::memcpy(packed.data() + i * elementSize, view.data + i * view.stride, elementSize);
}
}
if (accessor.sparse.has_value())
applySparse(document, accessor, packed);
return packed;
}
/*!
\internal
Returns the element data of accessor \a accessorIndex in \a document,
converted to floats, accessor.componentCount() per element. Normalized
integer component types are dequantized as the specification describes
(c/255, max(c/127, -1), ...), which also covers accessors quantized per
KHR_mesh_quantization; non-normalized integers are cast.
*/
QList<float> readAsFloats(const QSSGGltfDocument &document, int accessorIndex)
{
if (accessorIndex < 0 || accessorIndex >= document.accessors.size())
return {};
const Accessor &accessor = document.accessors.at(accessorIndex);
const QByteArray packed = readPacked(document, accessorIndex);
const int components = Accessor::componentCount(accessor.type);
const int componentSize = Accessor::componentByteSize(accessor.componentType);
QList<float> result(accessor.count * components);
const char *element = packed.constData();
for (qsizetype i = 0; i < result.size(); ++i)
result[i] = componentToFloat(element + i * componentSize, accessor.componentType, accessor.normalized);
return result;
}
/*!
\internal
Returns the scalar index data of accessor \a accessorIndex in \a document,
widened to 32-bit unsigned integers.
*/
QList<quint32> readIndices(const QSSGGltfDocument &document, int accessorIndex)
{
if (accessorIndex < 0 || accessorIndex >= document.accessors.size())
return {};
const Accessor &accessor = document.accessors.at(accessorIndex);
const QByteArray packed = readPacked(document, accessorIndex);
const qsizetype componentSize = Accessor::componentByteSize(accessor.componentType);
QList<quint32> result(accessor.count);
for (qint64 i = 0; i < accessor.count; ++i)
result[i] = readIndexValue(packed.constData() + i * componentSize, accessor.componentType);
return result;
}
} // namespace QSSGGltfAccessorReader
QT_END_NAMESPACE