#include "packcompiler.h" #include "common/exceptions.h" PackCompiler::PackCompiler(QObject *parent) : QObject{parent} { if(m_variableExpressions.isEmpty()) { for(const QString &variable : m_updateVariables) { m_variableExpressions.append ( QRegularExpression ( QStringLiteral("\\b(? void { setProgress(std::numeric_limits::infinity()); setTotalSteps(0); setCurrentStep(0); setState(Idle); } auto PackCompiler::build(const QUrl &uri) -> QUrl { setState(Compiling); setStatus(QStringLiteral("Preparing Komplex Pack For Building")); BuildContext *context = new BuildContext; QUrl extractedUri = extract(uri); validatePack(extractedUri); context->commonData = loadCommonData(extractedUri); processShaders(extractedUri, context); setState(Complete); setStatus(QStringLiteral("Complete")); delete context; return extractedUri; } auto PackCompiler::extract(const QUrl &sourceUri) noexcept(false) -> const QUrl { validateUri(sourceUri); setStatus(QStringLiteral("Extracting Komplex Pack")); QFileInfo info(sourceUri.toLocalFile()); QUrl outputUri ( QStringLiteral("file:///tmp/%1").arg ( info.baseName() ) ); QStringList arguments = { QStringLiteral("-xzf"), sourceUri.toLocalFile(), QStringLiteral("-C"), outputUri.toLocalFile() }; QDir outputDir(outputUri.toLocalFile()); if(!outputDir.exists()) { createDirectory(outputUri); } run(QStringLiteral("tar"), arguments); QFile::remove(sourceUri.toLocalFile()); return std::move(outputUri); } auto PackCompiler::validatePack(const QUrl &uri) const noexcept(false) -> void { QDir directory(uri.toLocalFile()); if (!directory.exists(QStringLiteral("pack.json"))) { throw shader::logic_error("Compiler Error", QStringLiteral("Invalid pack file").toStdString(), ENOEXEC); } if (!directory.exists(QStringLiteral("shaders"))) { throw shader::logic_error("Compiler Error", QStringLiteral("Nothing to compile").toStdString(), ENOEXEC); } } auto PackCompiler::loadCommonData(const QUrl &uri) -> QMap { validateUri(uri); setStatus(QStringLiteral("Loading Common Data")); QMap commonData; QDir packDirectory(uri.toLocalFile()); QDir shaderDirectory(packDirectory.absoluteFilePath(QStringLiteral("shaders"))); shaderDirectory.setNameFilters(m_shaderNameFilters); shaderDirectory.setFilter(QDir::Files | QDir::NoDotAndDotDot); const QStringList entries = shaderDirectory.entryList(); for(const QString &entry : entries) { if(entry.startsWith(QStringLiteral("common"), Qt::CaseInsensitive)) { QFileInfo entryInfo(shaderDirectory.absoluteFilePath(entry)); QByteArray data = commonData.take(entryInfo.suffix().toUtf8()); data += readFile ( QStringLiteral("file://%1").arg ( entryInfo.absoluteFilePath() ) ); commonData.insert(entryInfo.suffix().toUpper().toUtf8(), data); QFile::remove(entryInfo.absoluteFilePath()); } else if(entry.toLower() == QStringLiteral("global.glsl")) { if(commonData.contains(QByteArray("GLOBAL"))) { commonData.remove(QByteArray("GLOBAL")); } QFileInfo entryInfo(shaderDirectory.absoluteFilePath(entry)); QByteArray data = readFile ( QStringLiteral("file://%1").arg ( entryInfo.absoluteFilePath() ) ); commonData.insert(QByteArray("GLOBAL"), data); QFile::remove(entryInfo.absoluteFilePath()); } } return std::move(commonData); } auto PackCompiler::processShaders(const QUrl &uri, BuildContext *context) -> void { validateUri(uri); QDir packDirectory(uri.toLocalFile()); QDir shaderDirectory(packDirectory.absoluteFilePath(QStringLiteral("shaders"))); shaderDirectory.setNameFilters(m_shaderNameFilters); shaderDirectory.setFilter(QDir::Files | QDir::NoDotAndDotDot); const QStringList entries = shaderDirectory.entryList(); setTotalSteps(entries.count()); setProgress(0); for(const QString &entry : entries) { QFileInfo entryInfo(shaderDirectory.absoluteFilePath(entry)); QUrl entryUri = QStringLiteral("file://%1").arg ( entryInfo.absoluteFilePath() ); setStatus(QStringLiteral("Compiling: %1").arg(entry)); QShader::Stage stage = getStageFromSuffix(entryInfo.suffix()); QByteArray shaderData = readFile(entryUri); appendCommonData(&shaderData, entryInfo.suffix().toUtf8(), context); replaceUniformVariables(&shaderData); appendUniformHeader(&shaderData); appendUniformFooter(&shaderData); preprocess(&shaderData); appendVersion(&shaderData); QUrl outputUri = QStringLiteral ( "file://%1.qsb" ).arg(entryInfo.absoluteFilePath()); QShader shader = compile(shaderData, outputUri.fileName(), stage); save(outputUri, shader); QFile::remove(entryUri.toLocalFile()); incrementCompileStep(); } } auto PackCompiler::preprocess(QByteArray *data) noexcept(false) -> void { QStringList arguments; arguments << "-E" // Run only the preprocessor << "-P" // Omit #line markers << "-x" << "c++" << "-"; // Read source from stdin QByteArray temp = run(QStringLiteral("clang"), arguments, *data); data->clear(); data->swap(temp); } auto PackCompiler::appendVersion(QByteArray *data) noexcept(true) -> void { data->push_front(m_version + QByteArray("\n\n")); } auto PackCompiler::compile(const QByteArray &data, const QString &filename, const QShader::Stage stage) noexcept(false) -> QShader { QList targets; #ifdef HAS_VULKAN targets.append({ QShader::SpirvShader, QShaderVersion(vulkanVersion()) }); #endif #ifdef HAS_OPENGL targets.append({ QShader::GlslShader, QShaderVersion(openGlVersion()) }); #endif QShaderBaker baker; baker.setGeneratedShaderVariants({ QShader::StandardShader }); baker.setGeneratedShaders(targets); baker.setSourceString(data, stage, filename); QShader shader = baker.bake(); if(!shader.isValid()) { throw shader::logic_error ( std::string("Compiler Error"), baker.errorMessage().toStdString() ); } return shader; } #ifdef HAS_VULKAN auto PackCompiler::vulkanVersion() const -> qint32 { uint32_t instanceVersion = VK_API_VERSION_1_0; auto FN_vkEnumerateInstanceVersion = PFN_vkEnumerateInstanceVersion(vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceVersion")); if(FN_vkEnumerateInstanceVersion) { FN_vkEnumerateInstanceVersion(&instanceVersion); } uint32_t major = VK_VERSION_MAJOR(instanceVersion); uint32_t minor = VK_VERSION_MINOR(instanceVersion); QString versionString = QStringLiteral("%1%2%3").arg(major).arg(minor).arg(0); return versionString.toInt(); } #endif auto PackCompiler::save(const QUrl &uri, const QShader &shader) noexcept(false) -> void { writeFile(uri, shader.serialized()); } auto PackCompiler::appendCommonData(QByteArray *data, const QByteArray &suffix, BuildContext *context) noexcept(true) -> void { if(context->commonData.contains(QByteArray("GLOBAL"))) { data->push_front ( context->commonData.value(QByteArray("GLOBAL")) + QByteArray("\n") ); } if(context->commonData.contains(suffix.toUpper())) { data->push_front ( context->commonData.value(suffix.toUpper()) + QByteArray("\n") ); } } auto PackCompiler::appendUniformHeader(QByteArray *data) noexcept(true) -> void { data->push_front(m_header + QByteArray("\n\n")); } auto PackCompiler::appendUniformFooter(QByteArray *data) noexcept(true) -> void { data->push_back(QByteArray("\n\n") + m_footer); } auto PackCompiler::replaceUniformVariables(QByteArray *data) noexcept(true) -> void { for(const QRegularExpression &expression : std::as_const(m_variableExpressions)) { auto match = expression.match(*data); if(match.hasMatch()) { QByteArray replacement = QByteArray("ubuf.") + match.captured().toLocal8Bit(); data->replace(match.captured().toLocal8Bit(), replacement); } } } auto PackCompiler::copyFile(const QUrl &sourceUri, const QUrl &destinationUri) noexcept(false) -> void { if(!QFile::exists(sourceUri.toLocalFile())) { throw file_exception(QStringLiteral("File does not exist").toStdString(), ENOENT); } if(!QFile::copy(sourceUri.toLocalFile(), destinationUri.toLocalFile())) { throw file_exception(QStringLiteral("Could not copy file").toStdString()); } } auto PackCompiler::createDirectory(const QUrl &uri) -> void { validateUri(uri); QStringList arguments = { QStringLiteral("-p"), uri.toLocalFile() }; run(QStringLiteral("mkdir"), arguments); } auto PackCompiler::removeDirectory(const QUrl &uri) noexcept(false) -> void { QStringList arguments = { QStringLiteral("-rf"), uri.toLocalFile() }; run(QStringLiteral("rm"), arguments); } auto PackCompiler::getStageFromSuffix(const QString &suffix) const -> QShader::Stage { return m_shaderStages.value(suffix.toLower(), QShader::FragmentStage); } #ifdef HAS_OPENGL auto PackCompiler::openGlVersion() const -> qint32 { qint32 glVersion = 330; QString versionString = QString::fromUtf8(glGetString(GL_SHADING_LANGUAGE_VERSION)); QStringList parts = versionString.split(QChar(' '), Qt::SkipEmptyParts); if(parts.count() >= 1) { QString version = parts.at(0); version.remove(QChar('.')); bool okay = true; qint32 temp = version.toInt(&okay); if(okay) { glVersion = temp; } } return glVersion; } #endif auto PackCompiler::validateUri(const QUrl &uri) const noexcept(false) -> void { if(!uri.isValid() || !uri.isLocalFile()) { throw file_exception ( QStringLiteral("URI %1 is not a local file uri").arg ( uri.toString() ).toStdString(), std::error_code ( ENOENT, std::system_category() ) ); } } auto PackCompiler::run(const QString &command, const QStringList &arguments, const QByteArray &data) noexcept(false) -> QByteArray { QProcess process; process.start(command, arguments); if(!process.waitForStarted(3000)) { throw process_exception ( QStringLiteral("Could not start process for %1").arg(command).toStdString(), process.errorString().toStdString() ); } if(data.length() > 0) { process.write(data); process.closeWriteChannel(); } if(!process.waitForFinished()) { throw process_exception ( QStringLiteral("Process timeout for %1").arg(command).toStdString(), process.errorString().toStdString() ); } if(process.exitCode() != 0) { throw process_exception ( QStringLiteral("Process exited abnormally: %1").arg ( process.readAllStandardError() ).toStdString(), process.errorString().toStdString(), std::error_code ( process.exitCode(), std::system_category() ) ); } return process.readAllStandardOutput(); } auto PackCompiler::readFile(const QUrl &uri) -> QByteArray { validateUri(uri); QByteArray data; QFile file(uri.toLocalFile()); if(!file.open(QFile::ReadOnly)) { throw file_exception ( QStringLiteral("Could not file").toStdString(), std::error_code ( errno, std::system_category() ) ); } data = file.readAll(); if(data.length() != file.size()) { throw file_exception ( QStringLiteral("File read error").toStdString(), std::error_code ( EIO, std::system_category() ) ); } file.close(); return data; } auto PackCompiler::writeFile(const QUrl &uri, const QByteArray &data) noexcept(false) -> void { validateUri(uri); QFile file(uri.toLocalFile()); if(!file.open(QFile::ReadWrite)) { throw file_exception ( QStringLiteral("Could not file").toStdString(), std::error_code ( errno, std::system_category() ) ); } file.write(data); if(data.length() != file.size()) { throw file_exception ( QStringLiteral("File write error").toStdString(), std::error_code ( EIO, std::system_category() ) ); } file.close(); } auto PackCompiler::setProgress(qreal progress) -> void { if(qFuzzyCompare(progress, m_progress)) { return; } m_progress = progress; Q_EMIT progressChanged(); } auto PackCompiler::setState(State state) -> void { if(state == m_state) { return; } m_state = state; Q_EMIT stateChanged(); } auto PackCompiler::setStatus(const QString &status) -> void { if (m_status == status) { return; } m_status = status; Q_EMIT statusChanged(); } auto PackCompiler::setTotalSteps(qint64 steps) -> void { if(steps == m_totalSteps) { return; } m_totalSteps = steps; Q_EMIT totalStepsChanged(); } auto PackCompiler::setCurrentStep(qint64 step) -> void { if(step == m_currentStep) { return; } m_currentStep = step; Q_EMIT currentStepChanged(); } auto PackCompiler::incrementCompileStep() -> void { setCurrentStep(m_currentStep + 1); setProgress ( static_cast(m_currentStep) / m_totalSteps ); }