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