Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqsbcollection.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7#include <QtCore/QLockFile>
8#include <QtCore/QSaveFile>
9#include <QtCore/QCryptographicHash>
10#include <QtCore/QLoggingCategory>
11#include <rhi/qrhi.h>
12
14
15Q_STATIC_LOGGING_CATEGORY(lcQuick3DCache, "qt.quick3d.cache")
16
17QQsbCollection::~QQsbCollection()
18{
19}
20
21QDataStream &operator<<(QDataStream &stream, const QQsbCollection::Entry &entry)
22{
23 return (stream << entry.key << entry.value);
24}
25
26QDataStream &operator>>(QDataStream &stream, QQsbCollection::Entry &entry)
27{
28 QByteArray key;
29 qint64 value;
30 stream >> key >> value;
31 entry = QQsbCollection::Entry(key, value);
32 return stream;
33}
34
35size_t qHash(const QQsbCollection::Entry &entry, size_t)
36{
37 return entry.hashKey;
38}
39
40bool operator==(const QQsbCollection::Entry &l, const QQsbCollection::Entry &r)
41{
42 return (l.key == r.key);
43}
44
45QDataStream &operator<<(QDataStream &stream, const QQsbCollection::EntryDesc &entryDesc)
46{
47 return (stream << entryDesc.materialKey
48 << entryDesc.featureSet
49 << entryDesc.vertShader.serialized()
50 << entryDesc.fragShader.serialized());
51}
52
53QDataStream &operator>>(QDataStream &stream, QQsbCollection::EntryDesc &entryDesc)
54{
55 QByteArray desc;
56 QQsbCollection::FeatureSet fs;
57 QByteArray vertData;
58 QByteArray fragData;
59 stream >> desc >> fs >> vertData >> fragData;
60 entryDesc.materialKey = desc;
61 entryDesc.featureSet = fs;
62 entryDesc.vertShader = QShader::fromSerialized(vertData);
63 entryDesc.fragShader = QShader::fromSerialized(fragData);
64 return stream;
65}
66
67static constexpr quint64 MagicaDS = 0x3933333335346337;
68static constexpr qint64 HeaderSize = sizeof(qint64 /*startOffs*/) + sizeof(quint8 /*version*/) + sizeof(quint32 /*qtVersion*/) + sizeof(MagicaDS);
69static constexpr quint32 QtVersion = (QT_VERSION_MAJOR << 16) | (QT_VERSION_MINOR << 8) | (QT_VERSION_PATCH);
70
71bool QQsbCollection::readEndHeader(QDataStream &ds, qint64 *startPos, quint8 *version)
72{
73 quint64 fileId = 0;
74 quint32 qtver = 0;
75 ds >> *startPos >> *version >> qtver >> fileId;
76 if (fileId != MagicaDS) {
77 qCDebug(lcQuick3DCache, "Corrupt qsbc file");
78 return false;
79 }
80 if (*version != Version::Two) {
81 qCDebug(lcQuick3DCache, "qsbc file has an unsupported version");
82 return false;
83 }
84 if (qtver != QtVersion) {
85 qCDebug(lcQuick3DCache, "qsbc file is for a different Qt version");
86 return false;
87 }
88 return true;
89}
90
91bool QQsbCollection::readEndHeader(QIODevice *device, EntryMap *entries, quint8 *version)
92{
93 bool result = false;
94 const qint64 size = device->size();
95 if (device->seek(size - HeaderSize)) {
96 QDataStream ds(device);
97 ds.setVersion(QDataStream::Qt_6_0);
98 qint64 startPos = 0;
99 if (readEndHeader(ds, &startPos, version)) {
100 if (startPos >= 0 && startPos < size && device->seek(startPos)) {
101 ds >> *entries;
102 result = true;
103 }
104 }
105 }
106 return result;
107}
108
109void QQsbCollection::writeEndHeader(QDataStream &ds, qint64 startPos, quint8 version, quint64 magic)
110{
111 ds << startPos << version << QtVersion << magic;
112}
113
114void QQsbCollection::writeEndHeader(QIODevice *device, const EntryMap &entries)
115{
116 if (!device->atEnd()) {
117 device->seek(device->size() - 1);
118 Q_ASSERT(device->atEnd());
119 }
120 QDataStream ds(device);
121 ds.setVersion(QDataStream::Qt_6_0);
122 const qint64 startPos = device->pos();
123 ds << entries;
124 writeEndHeader(ds, startPos, quint8(Version::Two), MagicaDS);
125}
126
127QByteArray QQsbCollection::EntryDesc::generateSha(const QByteArray &materialKey, const FeatureSet &featureSet)
128{
129 QCryptographicHash h(QCryptographicHash::Algorithm::Sha1);
130 h.addData(materialKey);
131 for (auto it = featureSet.cbegin(), end = featureSet.cend(); it != end; ++it) {
132 if (it.value())
133 h.addData(it.key());
134 }
135 return h.result().toHex();
136}
137
138QByteArray QQsbCollection::EntryDesc::generateSha() const
139{
140 return generateSha(materialKey, featureSet);
141}
142
143QQsbCollection::EntryMap QQsbInMemoryCollection::availableEntries() const
144{
145 return EntryMap(entries.keyBegin(), entries.keyEnd());
146}
147
148QQsbCollection::Entry QQsbInMemoryCollection::addEntry(const QByteArray &key, const EntryDesc &entryDesc)
149{
150 Entry e(key);
151 if (!entries.contains(e)) {
152 entries.insert(e, entryDesc);
153 return e;
154 }
155 return {}; // can only add with a given key once
156}
157
158bool QQsbInMemoryCollection::extractEntry(Entry entry, EntryDesc &entryDesc)
159{
160 auto it = entries.constFind(entry);
161 if (it != entries.constEnd()) {
162 entryDesc = *it;
163 return true;
164 }
165 return false;
166}
167
168void QQsbInMemoryCollection::clear()
169{
170 entries.clear();
171}
172
173static inline QString lockFileName(const QString &name)
174{
175 return name + QLatin1String(".lck");
176}
177
178bool QQsbInMemoryCollection::load(const QString &filename)
179{
180 QLockFile lock(lockFileName(filename));
181 if (!lock.lock()) {
182 qCDebug(lcQuick3DCache, "Could not create shader cache lock file '%s'",
183 qPrintable(lock.fileName()));
184 return false;
185 }
186
187 QFile f(filename);
188 if (!f.open(QIODevice::ReadOnly)) {
189 qCDebug(lcQuick3DCache, "Failed to open qsbc file %s", qPrintable(filename));
190 return false;
191 }
192
193 EntryMap entryMap;
194 quint8 version = 0;
195 if (!readEndHeader(&f, &entryMap, &version)) {
196 qCDebug(lcQuick3DCache, "Ignoring qsbc file %s", qPrintable(filename));
197 return false;
198 }
199
200 f.seek(0);
201 const qint64 size = f.size();
202
203 clear();
204
205 const qsizetype entryCountBefore = entries.size();
206 for (const Entry &e : entryMap) {
207 const qint64 offset = e.value;
208 if (e.isValid() && offset >= 0 && size > offset && f.seek(offset)) {
209 QDataStream ds(&f);
210 ds.setVersion(QDataStream::Qt_6_0);
211 EntryDesc entryDesc;
212 ds >> entryDesc;
213 entries.insert(Entry(e.key), entryDesc);
214 }
215 }
216 qCDebug(lcQuick3DCache, "Loaded %d material cache entries from %s",
217 int(entries.size() - entryCountBefore),
218 qPrintable(filename));
219
220 return true;
221}
222
223bool QQsbInMemoryCollection::save(const QString &filename)
224{
225 QLockFile lock(lockFileName(filename));
226 if (!lock.lock()) {
227 qCDebug(lcQuick3DCache, "Could not create shader cache lock file '%s'",
228 qPrintable(lock.fileName()));
229 return false;
230 }
231
232#if QT_CONFIG(temporaryfile)
233 QSaveFile f(filename);
234#else
235 QFile f(filename);
236#endif
237 if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
238 qCDebug(lcQuick3DCache, "Failed to write qsbc file %s", qPrintable(filename));
239 return false;
240 }
241
242 QDataStream ds(&f);
243 ds.setVersion(QDataStream::Qt_6_0);
244
245 EntryMap entryMap;
246 for (auto it = entries.cbegin(), end = entries.cend(); it != end; ++it) {
247 const qint64 offset = f.pos();
248 ds << it.value();
249 entryMap.insert(Entry(it.key().key, offset));
250 }
251
252 writeEndHeader(&f, entryMap);
253
254 qCDebug(lcQuick3DCache, "Writing %d material cache entries to %s",
255 int(entryMap.size()),
256 qPrintable(filename));
257
258#if QT_CONFIG(temporaryfile)
259 return f.commit();
260#else
261 return true;
262#endif
263}
264
265QQsbIODeviceCollection::QQsbIODeviceCollection(const QString &filePath)
266 : file(filePath)
267 , device(file)
268{
269}
270
271QQsbIODeviceCollection::QQsbIODeviceCollection(QIODevice &dev)
272 : device(dev)
273 , devOwner(DeviceOwner::Extern)
274{
275
276}
277
278QQsbIODeviceCollection::~QQsbIODeviceCollection()
279{
280 if (!entries.isEmpty() || device.isOpen())
281 unmap();
282}
283
284bool QQsbIODeviceCollection::map(MapMode mode)
285{
286 if (device.isOpen()) {
287 // Make sure Truncate is set if we're writing.
288 if ((device.openMode() & QIODevice::WriteOnly) != 0) {
289 if ((device.openMode() & QIODevice::Truncate) == 0) {
290 qWarning("Open mode needs to have Truncate set for writing!");
291 return false;
292 }
293 if ((device.openMode() & QIODevice::Text) != 0) {
294 qWarning("Open mode can't have Text mode set!");
295 return false;
296 }
297 }
298 } else if (!device.open(QIODevice::OpenMode(mode))) {
299 qWarning("Unable to open device!");
300 return false;
301 }
302
303 if (mode == Write)
304 return true;
305
306 Q_ASSERT(mode == Read);
307
308 const bool ret = readEndHeader(&device, &entries, &version);
309
310 if (!ret)
311 unmap();
312
313 return ret;
314}
315
316void QQsbIODeviceCollection::unmap()
317{
318 if (device.isOpen() && ((device.openMode() & Write) == Write)) {
319 if (!entries.isEmpty()) {
320 writeEndHeader(&device, entries);
321 } else {
322 if (devOwner == DeviceOwner::Self)
323 file.remove();
324 }
325 }
326 device.close();
327 entries.clear();
328}
329
330QQsbCollection::EntryMap QQsbIODeviceCollection::availableEntries() const
331{
332 return entries;
333}
334
335QQsbCollection::Entry QQsbIODeviceCollection::addEntry(const QByteArray &key, const EntryDesc &entryDesc)
336{
337 if (entries.contains(Entry(key)) || !map(MapMode::Write))
338 return {};
339
340 QDataStream ds(&device);
341 ds.setVersion(QDataStream::Qt_6_0);
342 const auto offset = device.pos();
343 ds << entryDesc;
344 Entry e(key, offset);
345 entries.insert(e);
346 return e;
347}
348
349bool QQsbIODeviceCollection::extractEntry(Entry entry, EntryDesc &entryDesc)
350{
351 if (device.isOpen() && device.isReadable()) {
352 const qint64 offset = entry.value;
353 if (entry.isValid() && offset >= 0) {
354 const qint64 size = device.size();
355 if (size > offset && device.seek(offset)) {
356 QDataStream ds(&device);
357 ds.setVersion(QDataStream::Qt_6_0);
358 ds >> entryDesc;
359 return true;
360 }
361 } else {
362 qWarning("Entry not found id(%s), offset(%lld)", entry.key.constData(), entry.value);
363 }
364 } else {
365 qWarning("Unable to open file for reading");
366 }
367
368 return false;
369}
370
371static const char *borderText() { return "--------------------------------------------------------------------------------"; }
372
373void QQsbIODeviceCollection::dumpInfo()
374{
375 if (map(QQsbIODeviceCollection::Read)) {
376 qDebug("Number of entries in collection: %zu\n", size_t(entries.size()));
377 int i = 0;
378 qDebug("Qsbc version: %u", version);
379 for (const auto &e : std::as_const(entries)) {
380 qDebug("%s\n"
381 "Entry %d\n%s\n"
382 "Key: %s\n"
383 "Offset: %llu", borderText(), i++, borderText(), e.key.constData(), e.value);
384
385 QQsbCollection::EntryDesc ed;
386 if (extractEntry(e, ed)) {
387 qDebug() << ed.materialKey << Qt::endl
388 << ed.featureSet << Qt::endl
389 << ed.vertShader << Qt::endl
390 << ed.fragShader;
391 } else {
392 qWarning("Extracting Qsb entry failed!");
393 }
394 }
395 }
396 unmap();
397}
398
399void QQsbIODeviceCollection::dumpInfo(const QString &file)
400{
401 QQsbIODeviceCollection qsbc(file);
402 qsbc.dumpInfo();
403}
404
405void QQsbIODeviceCollection::dumpInfo(QIODevice &device)
406{
407 QQsbIODeviceCollection qsbc(device);
408 qsbc.dumpInfo();
409}
410
411QT_END_NAMESPACE
friend bool operator==(const QByteArray::FromBase64Result &lhs, const QByteArray::FromBase64Result &rhs) noexcept
Returns true if lhs and rhs are equal, otherwise returns false.
Definition qbytearray.h:803
Combined button and popup list for selecting options.
QDataStream & operator>>(QDataStream &s, QKeyCombination &combination)
static const char * borderText()
static constexpr qint64 HeaderSize
static QString lockFileName(const QString &name)
static constexpr quint64 MagicaDS
static constexpr quint32 QtVersion
constexpr size_t qHash(const QSize &s, size_t seed=0) noexcept
Definition qsize.h:192