158 const QString &rootDirectory,
const QString &requestedOutputPath,
159 const QStringList *includedShaders,
164 const QFileInfo rootInfo(rootDirectory);
165 if (!rootInfo.exists() || !rootInfo.isDir()) {
166 error = QObject::tr(
"Shader source directory does not exist: %1")
170 const QString root = QDir(rootInfo.absoluteFilePath()).canonicalPath();
171 const QString outputPath = requestedOutputPath.trimmed().isEmpty()
172 ? QDir(root).filePath(QStringLiteral(
"library.json"))
173 : QFileInfo(requestedOutputPath).absoluteFilePath();
174 if (!QDir().mkpath(QFileInfo(outputPath).absolutePath())) {
175 error = QObject::tr(
"Cannot create manifest directory: %1")
176 .arg(QFileInfo(outputPath).absolutePath());
180 QSet<QString> includedFiles;
181 if (includedShaders) {
182 for (QString entry : *includedShaders) {
183 entry = entry.trimmed();
184 entry.replace(QLatin1Char(
'\\'), QLatin1Char(
'/'));
185 entry = QDir::cleanPath(entry);
186 if (entry.isEmpty() || entry == QStringLiteral(
".") ||
187 entry == QStringLiteral(
"..") ||
188 entry.startsWith(QStringLiteral(
"../")) ||
189 QDir::isAbsolutePath(entry)) {
190 error = QObject::tr(
"Invalid shader path in source manifest: %1")
194 if (includedFiles.contains(entry)) {
195 error = QObject::tr(
"Duplicate shader path in source manifest: %1")
199 includedFiles.insert(entry);
203 QHash<int, QString> slotNames = default_slot_names();
204 QHash<QString, int> nameSlots;
205 for (
auto it = slotNames.constBegin(); it != slotNames.constEnd(); ++it)
206 nameSlots.insert(it.value(), it.key());
208 QStringList fragmentFiles;
209 QStringList computeFiles;
210 QSet<QString> discoveredFiles;
211 QDirIterator iterator(root, QDir::Files | QDir::Hidden,
212 QDirIterator::Subdirectories);
213 while (iterator.hasNext()) {
214 const QString absolutePath = iterator.next();
215 QString relative = QDir(root).relativeFilePath(absolutePath);
216 relative.replace(QLatin1Char(
'\\'), QLatin1Char(
'/'));
217 const QStringList parts = relative.split(QLatin1Char(
'/'));
218 const int directoryPartCount =
219 std::max(0,
static_cast<int>(parts.size()) - 1);
220 const bool underCompute =
221 parts.mid(0, directoryPartCount)
222 .contains(QStringLiteral(
"compute"));
223 const bool computeShader =
224 underCompute && relative.endsWith(QStringLiteral(
".comp"));
225 const bool fragmentShader =
226 !underCompute && relative.endsWith(QStringLiteral(
".frag"));
227 if (!computeShader && !fragmentShader)
229 if (includedShaders && !includedFiles.contains(relative))
232 computeFiles.append(relative);
234 fragmentFiles.append(relative);
236 discoveredFiles.insert(relative);
237 if (!scan_slots(absolutePath, slotNames, nameSlots, error))
241 if (includedShaders) {
242 for (
const QString &entry : includedFiles) {
243 if (!discoveredFiles.contains(entry)) {
245 "Manifest shader is missing or is not a supported "
246 ".frag/compute/*.comp source: %1")
253 std::sort(fragmentFiles.begin(), fragmentFiles.end(), shader_name_less);
254 std::sort(computeFiles.begin(), computeFiles.end(), shader_name_less);
255 QStringList shaders = fragmentFiles;
256 shaders.append(computeFiles);
257 if (shaders.isEmpty()) {
258 error = QObject::tr(
"No .frag or compute/*.comp sources found in %1")
262 if (shaders.size() > 16384) {
263 error = QObject::tr(
"Shader library exceeds ACMXVK's 16384-entry limit");
267 QHash<QString, QString> outputNames;
268 for (
const QString &shader : shaders) {
269 const QString compiled = (shader + QStringLiteral(
".spv")).toLower();
270 if (outputNames.contains(compiled)) {
271 error = QObject::tr(
"Case-insensitive duplicate shader output: %1.spv")
275 outputNames.insert(compiled, shader);
279 for (
auto it = slotNames.constBegin(); it != slotNames.constEnd(); ++it)
280 maximumSlot = std::max(maximumSlot, it.key());
281 for (
int slot = 0; slot <= maximumSlot; ++slot) {
282 if (!slotNames.contains(slot)) {
283 error = QObject::tr(
"No custom-uniform name was found for slot %1")
289 QHash<QString, UniformMetadata> metadata = default_metadata();
290 if (!read_existing_metadata(outputPath, metadata, error))
293 QJsonObject customUniforms;
294 for (
int slot = 0; slot <= maximumSlot; ++slot) {
295 const QString name = slotNames.value(slot);
296 const UniformMetadata values = metadata.value(name);
298 uniform.insert(QStringLiteral(
"slot"), slot);
299 uniform.insert(QStringLiteral(
"minimum"), values.minimum);
300 uniform.insert(QStringLiteral(
"maximum"), values.maximum);
301 uniform.insert(QStringLiteral(
"step"), values.step);
302 uniform.insert(QStringLiteral(
"value"), values.value);
303 customUniforms.insert(name, uniform);
305 QJsonArray shaderArray;
306 for (
const QString &shader : shaders)
307 shaderArray.append(shader);
309 QJsonObject manifest;
310 manifest.insert(QStringLiteral(
"version"), 1);
311 manifest.insert(QStringLiteral(
"backend"), QStringLiteral(
"acmxvk"));
312 manifest.insert(QStringLiteral(
"library_type"), QStringLiteral(
"source"));
313 manifest.insert(QStringLiteral(
"custom_uniforms"), customUniforms);
314 manifest.insert(QStringLiteral(
"shaders"), shaderArray);
316 QSaveFile output(outputPath);
317 if (!output.open(QIODevice::WriteOnly)) {
318 error = QObject::tr(
"Cannot write %1: %2")
319 .arg(outputPath, output.errorString());
322 const QByteArray json = QJsonDocument(manifest).toJson(QJsonDocument::Indented);
323 if (output.write(json) != json.size() || !output.commit()) {
324 error = QObject::tr(
"Cannot replace %1: %2")
325 .arg(outputPath, output.errorString());