110 const QFileInfo rootInfo(rootDirectory);
111 if (!rootInfo.exists() || !rootInfo.isDir()) {
112 error = QObject::tr(
"Shader source directory does not exist: %1").arg(rootDirectory);
115 const QString root = QDir(rootInfo.absoluteFilePath()).canonicalPath();
116 const QString outputPath = requestedOutputPath.trimmed().isEmpty() ? QDir(root).filePath(QStringLiteral(
"library.json")) : QFileInfo(requestedOutputPath).absoluteFilePath();
117 if (!QDir().mkpath(QFileInfo(outputPath).absolutePath())) {
118 error = QObject::tr(
"Cannot create manifest directory: %1").arg(QFileInfo(outputPath).absolutePath());
122 QSet<QString> includedFiles;
123 if (includedShaders) {
124 for (QString entry : *includedShaders) {
125 entry = entry.trimmed();
126 entry.replace(QLatin1Char(
'\\'), QLatin1Char(
'/'));
127 entry = QDir::cleanPath(entry);
128 if (entry.isEmpty() || entry == QStringLiteral(
".") || entry == QStringLiteral(
"..") || entry.startsWith(QStringLiteral(
"../")) || QDir::isAbsolutePath(entry)) {
129 error = QObject::tr(
"Invalid shader path in source manifest: %1").arg(entry);
132 if (includedFiles.contains(entry)) {
133 error = QObject::tr(
"Duplicate shader path in source manifest: %1").arg(entry);
136 includedFiles.insert(entry);
140 QHash<int, QString> slotNames = default_slot_names();
141 QHash<QString, int> nameSlots;
142 for (
auto it = slotNames.constBegin(); it != slotNames.constEnd(); ++it)
143 nameSlots.insert(it.value(), it.key());
145 QStringList fragmentFiles;
146 QStringList computeFiles;
147 QSet<QString> discoveredFiles;
148 QDirIterator iterator(root, QDir::Files | QDir::Hidden, QDirIterator::Subdirectories);
149 while (iterator.hasNext()) {
150 const QString absolutePath = iterator.next();
151 QString relative = QDir(root).relativeFilePath(absolutePath);
152 relative.replace(QLatin1Char(
'\\'), QLatin1Char(
'/'));
153 const QStringList parts = relative.split(QLatin1Char(
'/'));
154 const int directoryPartCount = std::max(0,
static_cast<int>(parts.size()) - 1);
155 const bool underCompute = parts.mid(0, directoryPartCount).contains(QStringLiteral(
"compute"));
156 const bool computeShader = underCompute && relative.endsWith(QStringLiteral(
".comp"));
157 const bool fragmentShader = !underCompute && relative.endsWith(QStringLiteral(
".frag"));
158 if (!computeShader && !fragmentShader)
160 if (includedShaders && !includedFiles.contains(relative))
163 computeFiles.append(relative);
165 fragmentFiles.append(relative);
167 discoveredFiles.insert(relative);
168 if (!scan_slots(absolutePath, slotNames, nameSlots, error))
172 if (includedShaders) {
173 for (
const QString &entry : includedFiles) {
174 if (!discoveredFiles.contains(entry)) {
175 error = QObject::tr(
"Manifest shader is missing or is not a supported "
176 ".frag/compute/*.comp source: %1")
183 std::sort(fragmentFiles.begin(), fragmentFiles.end(), shader_name_less);
184 std::sort(computeFiles.begin(), computeFiles.end(), shader_name_less);
185 QStringList shaders = fragmentFiles;
186 shaders.append(computeFiles);
187 if (shaders.isEmpty()) {
188 error = QObject::tr(
"No .frag or compute/*.comp sources found in %1").arg(root);
191 if (shaders.size() > 16384) {
192 error = QObject::tr(
"Shader library exceeds ACMXVK's 16384-entry limit");
196 QHash<QString, QString> outputNames;
197 for (
const QString &shader : shaders) {
198 const QString compiled = (shader + QStringLiteral(
".spv")).toLower();
199 if (outputNames.contains(compiled)) {
200 error = QObject::tr(
"Case-insensitive duplicate shader output: %1.spv").arg(shader);
203 outputNames.insert(compiled, shader);
207 for (
auto it = slotNames.constBegin(); it != slotNames.constEnd(); ++it)
208 maximumSlot = std::max(maximumSlot, it.key());
209 for (
int slot = 0; slot <= maximumSlot; ++slot) {
210 if (!slotNames.contains(slot)) {
211 error = QObject::tr(
"No custom-uniform name was found for slot %1").arg(slot);
216 QHash<QString, UniformMetadata> metadata = default_metadata();
217 if (!read_existing_metadata(outputPath, metadata, error))
220 QJsonObject customUniforms;
221 for (
int slot = 0; slot <= maximumSlot; ++slot) {
222 const QString name = slotNames.value(slot);
223 const UniformMetadata values = metadata.value(name);
225 uniform.insert(QStringLiteral(
"slot"), slot);
226 uniform.insert(QStringLiteral(
"minimum"), values.minimum);
227 uniform.insert(QStringLiteral(
"maximum"), values.maximum);
228 uniform.insert(QStringLiteral(
"step"), values.step);
229 uniform.insert(QStringLiteral(
"value"), values.value);
230 customUniforms.insert(name, uniform);
232 QJsonArray shaderArray;
233 for (
const QString &shader : shaders)
234 shaderArray.append(shader);
236 QJsonObject manifest;
237 manifest.insert(QStringLiteral(
"version"), 1);
238 manifest.insert(QStringLiteral(
"backend"), QStringLiteral(
"acmxvk"));
239 manifest.insert(QStringLiteral(
"library_type"), QStringLiteral(
"source"));
240 manifest.insert(QStringLiteral(
"custom_uniforms"), customUniforms);
241 manifest.insert(QStringLiteral(
"shaders"), shaderArray);
243 QSaveFile output(outputPath);
244 if (!output.open(QIODevice::WriteOnly)) {
245 error = QObject::tr(
"Cannot write %1: %2").arg(outputPath, output.errorString());
248 const QByteArray json = QJsonDocument(manifest).toJson(QJsonDocument::Indented);
249 if (output.write(json) != json.size() || !output.commit()) {
250 error = QObject::tr(
"Cannot replace %1: %2").arg(outputPath, output.errorString());