77 if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
79 }
else if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
82 qWarning() <<
"QSSGLightmapLoader::convertEndian: Unknown endianness";
86 Q_ASSERT(buffer.size() % sizeOfDataType == 0);
87 if (buffer.size() % sizeOfDataType != 0) {
88 qWarning() <<
"QSSGLightmapLoader::convertEndian: Unexpected buffer size";
92 if (sizeOfDataType == 1)
95 const int buffSize = buffer.size();
96 for (
int offset = 0; offset < buffSize; offset += sizeOfDataType) {
97 for (
int i = 0; i < sizeOfDataType / 2; ++i) {
98 std::swap(buffer[offset + i], buffer[offset + sizeOfDataType - 1 - i]);
117 if (!stream->isOpen())
118 stream->open(QIODeviceBase::OpenModeFlag::ReadOnly);
120 if (!stream->isOpen()) {
121 qWarning() <<
"QSSGLightmapIO: Stream is not openable";
125 if (!stream->isReadable()) {
126 qWarning() <<
"QSSGLightmapIO: Stream is not readable";
131 constexpr int headerSize = 6 *
sizeof(
char) +
sizeof(qint32);
133 constexpr int footerSize = 2 *
sizeof(qint64);
135 const qint64 fileSize = stream->size();
136 if (fileSize < headerSize + footerSize) {
137 qWarning() <<
"QSSGLightmapIO: File too small to contain header and footer";
142 QByteArray headerData = stream->read(headerSize);
143 if (headerData.size() != qsizetype(headerSize)) {
144 qWarning() <<
"Failed to read header";
149 if (QByteArrayView(headerData.constData(), 6) != QByteArray::fromRawData(fileSignature, 6)) {
150 qWarning() <<
"QSSGLightmapIO: Invalid file signature";
154 qint32 fileVersion = -1;
157 const char *versionPtr = headerData.constData() + 6;
158 fileVersion = qFromLittleEndian<qint32>(versionPtr);
160 if (fileVersion != 1) {
161 qWarning() <<
"QSSGLightmapIO: Invalid file version";
166 if (!stream->seek(fileSize - footerSize)) {
167 qWarning() <<
"Failed to seek to footer";
171 QByteArray footerData = stream->read(footerSize);
172 if (footerData.size() != qsizetype(footerSize)) {
173 qWarning() <<
"Failed to read footer";
177 const char *footerPtr = footerData.constData();
178 const qint64 indexOffset = qFromLittleEndian<qint64>(footerPtr);
179 const qint64 entryCount = qFromLittleEndian<qint64>(footerPtr +
sizeof(qint64));
181 this->entryCount = entryCount;
182 this->indexOffset = indexOffset;
183 this->fileVersion = fileVersion;
184 this->fileSize = fileSize;
227 Q_ASSERT(stream->isOpen() && stream->isWritable());
228 IndexKey keyBytes = keyToIndexKey(key, tag);
229 Q_ASSERT(!entries.contains(keyBytes));
232 entry.dataOffset = stream->pos();
233 entry.dataSize = data.size();
236 if (stream->write(data) != data.size()) {
237 qWarning() <<
"QSSGLightmapIO: Failed to write entry data";
253 Q_ASSERT(stream->isOpen());
254 Q_ASSERT(stream->isWritable());
257 for (
auto it = entries.begin(); it != entries.end(); ++it) {
258 auto [dataTag, keySize, key] = it.key();
260 entry.keyOffset = stream->pos();
261 entry.keySize = keySize;
262 if (stream->write(key) != key.size()) {
263 qWarning() <<
"QSSGLightmapIO: Failed to write key";
270 const qint64 indexOffset = qToLittleEndian<qint64>(stream->pos());
271 const qint64 indexCount = qToLittleEndian<qint64>(entries.size());
273 for (
const IndexEntry &entry : std::as_const(entries)) {
274 const qint64 keyOffset = qToLittleEndian<qint64>(entry.keyOffset);
275 const qint64 keySize = qToLittleEndian<qint64>(entry.keySize);
276 const qint64 dataOffset = qToLittleEndian<qint64>(entry.dataOffset);
277 const qint64 dataSize = qToLittleEndian<qint64>(entry.dataSize);
278 const quint32 dataTag = qToLittleEndian<quint32>(std::underlying_type_t<QSSGLightmapIODataTag>(entry.dataTag));
279 const quint32 padding = qToLittleEndian<quint32>(entry.padding);
280 if (!writeType(stream, keyOffset) || !writeType(stream, keySize) || !writeType(stream, dataOffset)
281 || !writeType(stream, dataSize) || !writeType(stream, dataTag) || !writeType(stream, padding)) {
282 qWarning() <<
"QSSGLightmapIO: Failed to write entry";
288 if (!writeType(stream, indexOffset) || !writeType(stream, indexCount)) {
289 qWarning() <<
"QSSGLightmapIO: Failed to write footer";
299 Q_ASSERT(entryCount >= 0);
300 Q_ASSERT(indexOffset >= 0);
301 Q_ASSERT(fileVersion >= 0);
302 Q_ASSERT(fileSize >= 0);
306 qint64 high = entryCount;
308 auto [dataTag, keySize, key] = indexKey;
310 qint64 matchOffset = 0;
311 qint64 matchSize = 0;
314 qint64 mid = (low + high) / 2;
315 qint64 offset = indexOffset + mid *
sizeof(IndexEntry);
317 if (!stream->seek(offset)) {
318 qWarning() <<
"Failed to seek to index entry";
322 if (stream->read(
reinterpret_cast<
char *>(&entry),
sizeof(IndexEntry)) !=
sizeof(IndexEntry)) {
323 qWarning() <<
"Failed to read index entry";
328 int cmp = qint64(entry
.dataTag) - qint64(dataTag);
330 cmp = entry.keySize - keySize;
333 if (!stream->seek(entry.keyOffset)) {
334 qWarning() <<
"Failed to seek to key entry";
337 const QByteArray entryKey = stream->read(entry.keySize);
338 if (entryKey.size() != entry.keySize) {
339 qWarning() <<
"Failed to read to key entry";
342 for (
int i = 0, n = entry.keySize; i < n; ++i) {
343 cmp =
int(entryKey[i]) -
int(key[i]);
351 }
else if (cmp > 0) {
356 matchOffset = qFromLittleEndian(entry.dataOffset);
357 matchSize = qFromLittleEndian(entry.dataSize);
363 qWarning() <<
"Key not found:" << key;
367 if (matchOffset + matchSize > fileSize) {
368 qWarning() <<
"Asset data out of bounds";
373 if (!stream->seek(matchOffset)) {
374 qWarning() <<
"Failed to seek to asset data";
378 QByteArray assetData = stream->read(matchSize);
379 if (assetData.size() !=
static_cast<
int>(matchSize)) {
380 qWarning() <<
"Failed to read full asset data";
390 Q_ASSERT(entryCount >= 0);
391 Q_ASSERT(indexOffset >= 0);
392 Q_ASSERT(fileVersion >= 0);
393 Q_ASSERT(fileSize >= 0);
395 QList<std::pair<QString, QSSGLightmapIODataTag>> keys;
396 keys.resize(entryCount);
400 for (
int i = 0; i < entryCount; ++i) {
401 const qint64 offset = indexOffset + i *
sizeof(IndexEntry);
403 if (!stream->seek(offset)) {
404 qWarning() <<
"Failed to seek to index entry";
408 if (stream->read(
reinterpret_cast<
char *>(&entry),
sizeof(IndexEntry)) !=
sizeof(IndexEntry)) {
409 qWarning() <<
"Failed to read index entry";
412 if (!stream->seek(entry.keyOffset)) {
413 qWarning() <<
"Failed to seek to key entry";
416 const QByteArray entryKey = stream->read(entry.keySize);
417 if (entryKey.size() != entry.keySize) {
418 qWarning() <<
"Failed to read to key entry";
422 keys[i] = std::make_pair(QString::fromUtf8(entryKey),
static_cast<QSSGLightmapIODataTag>(entry.dataTag));
425 std::sort(keys.begin(), keys.end(), [](
const auto &a,
const auto &b) {
return a.first < b.first; });
441QSharedPointer<QSSGLightmapLoader> QSSGLightmapLoader::open(
const QSharedPointer<QIODevice> &stream)
444 qWarning() <<
"Failed to open lightmap: invalid stream";
448 auto loader = QSharedPointer<QSSGLightmapLoader>(
new QSSGLightmapLoader);
449 loader->d->stream = stream;
451 if (!loader->d->decodeHeaders()) {
452 if (loader->d->stream->isOpen())
453 loader->d->stream->close();
487QVariantMap QSSGLightmapLoader::readMap(
const QString &key, QSSGLightmapIODataTag tag)
const
489 QByteArray metadataBuffer = d->readKey(keyToIndexKey(key, tag));
490 QVariantMap metadata = byteArrayToMap(metadataBuffer);
508QSSGLoadedTexture *QSSGLightmapLoader::createTexture(QSharedPointer<QIODevice> stream,
509 const QSSGRenderTextureFormat &format,
512 QSharedPointer<QSSGLightmapLoader> loader = QSSGLightmapLoader::open(stream);
516 QVariantMap metadata = loader->readMap(key, QSSGLightmapIODataTag::Metadata);
517 if (metadata.isEmpty())
521 const int w = metadata[QStringLiteral(
"width")].toInt(&ok);
523 const int h = metadata[QStringLiteral(
"height")].toInt(&ok);
525 QSize pixelSize = QSize(w, h);
526 QByteArray imageFP32 = loader->readF32Image(key, QSSGLightmapIODataTag::Texture_Final);
527 if (imageFP32.isEmpty())
531 const int bytesPerPixel = format.getSizeofFormat();
532 const int bitCount = bytesPerPixel * 8;
533 const int pitch = calculatePitch(calculateLine(pixelSize.width(), bitCount));
534 const size_t dataSize = pixelSize.height() * pitch;
535 QSSG_CHECK_X(dataSize <= std::numeric_limits<quint32>::max(),
"Requested data size exceeds 4GB limit!");
536 QSSGLoadedTexture *imageData =
new QSSGLoadedTexture;
537 imageData->dataSizeInBytes = quint32(dataSize);
538 imageData->data = ::malloc(imageData->dataSizeInBytes);
539 imageData->width = pixelSize.width();
540 imageData->height = pixelSize.height();
541 imageData->format = format;
542 imageData->components = format.getNumberOfComponent();
543 imageData->isSRGB =
false;
545 std::array<
float, 4> *source =
reinterpret_cast<std::array<
float, 4> *>(imageFP32.data());
546 quint8 *target =
reinterpret_cast<quint8 *>(imageData->data);
551 for (
int y = imageData->height - 1; y >= 0; --y) {
552 for (
int x = 0; x < imageData->width; x++) {
553 const int i = y * imageData->width + x;
554 const int lh = i * 4 *
sizeof(
float);
555 const int rh = imageFP32.size();
557 std::array<
float, 4> v = source[i];
563 format.encodeToPixel(rgbaF32, target, idx * bytesPerPixel);
589QSharedPointer<QSSGLightmapWriter> QSSGLightmapWriter::open(
const QSharedPointer<QIODevice> &stream)
592 qWarning() <<
"Failed to open lightmap file";
596 QSharedPointer<QSSGLightmapWriter> writer = QSharedPointer<QSSGLightmapWriter>(
new QSSGLightmapWriter);
597 if (!stream->isOpen() && !stream->open(QIODeviceBase::WriteOnly)) {
598 qWarning() <<
"Failed to open lightmap file";
602 writer->d->stream = stream;
603 if (!writer->d->writeHeader())
609bool QSSGLightmapWriter::writeF32Image(
const QString &key, QSSGLightmapIODataTag tag,
const QByteArray &imageFP32)
611 QByteArray buffer = QByteArray(imageFP32.constData(), imageFP32.size());
612 convertEndian(buffer,
sizeof(
float));
613 return d->writeData(key, tag, buffer);
616bool QSSGLightmapWriter::writeU32Image(
const QString &key, QSSGLightmapIODataTag tag,
const QByteArray &imageU32)
618 QByteArray buffer = QByteArray(imageU32.constData(), imageU32.size());
619 convertEndian(buffer,
sizeof(quint32));
620 return d->writeData(key, tag, buffer);
623bool QSSGLightmapWriter::writeData(
const QString &key, QSSGLightmapIODataTag tag,
const QByteArray &buffer)
625 return d->writeData(key, tag, buffer);
628bool QSSGLightmapWriter::writeMap(
const QString &key, QSSGLightmapIODataTag tag,
const QVariantMap &data)
630 return d->writeData(key, tag, mapToByteArray(data));
QSSGLightmapIODataTag dataTag