Added PackCompiler

This commit is contained in:
Digital Artifex
2026-08-11 06:26:23 -04:00
parent 6a59a48a35
commit 7ae2ac34e8
6 changed files with 1460 additions and 473 deletions
+942
View File
@@ -0,0 +1,942 @@
#include "packcompiler.h"
#include "common/exceptions.h"
#include <utility>
PackCompiler::PackCompiler(QObject *parent)
: QObject(parent)
{
if(m_variableExpressions.isEmpty())
{
for(const QString &variable : m_updateVariables)
{
m_variableExpressions.append
(
QRegularExpression
(
QStringLiteral("\\b(?<!ubuf\\.)%1\\b").arg
(
variable
)
)
);
}
}
}
PackCompiler::~PackCompiler() = default;
auto PackCompiler::reset() -> void
{
setError({}, {});
setProgress(std::numeric_limits<qreal>::infinity());
setTotalSteps(0);
setCurrentStep(0);
setCompilerOutput({});
setStatus({});
setState(Idle);
}
auto PackCompiler::process(const QUrl &uri) -> QFuture<QUrl>
{
return QtConcurrent::run
(
[this, uri] () -> QUrl
{
if(!uri.isLocalFile())
{
setError
(
QStringLiteral("File Error"),
QStringLiteral("URI %1 is not a local file").arg
(
uri.toString()
)
);
return {};
}
reset();
setStatus(QStringLiteral("Compiling %1").arg(uri.fileName()));
setState(Compiling);
if(!validateDirectory(uri))
{
throw FileException
(
QStringLiteral("Source directory is invalid")
);
}
QUrl buildUri
(
QStringLiteral("file://%1/komplex/build/%2").arg
(
QStandardPaths::writableLocation
(
QStandardPaths::TempLocation
),
uri.fileName()
)
);
try
{
extract(uri, buildUri);
prepareShaders(buildUri);
compile(buildUri);
}
catch (FileException e)
{
removeDirectory(buildUri);
setError(QStringLiteral("File Error"), e.message);
throw e;
}
catch (ShaderCompilerException e)
{
removeDirectory(buildUri);
setError(QStringLiteral("Compiler Error"), e.message);
throw e;
}
return buildUri;
}
);
}
auto PackCompiler::prepareShaders(const QUrl &uri) noexcept(false) -> void
{
if(!uri.isLocalFile())
{
throw FileException
(
QStringLiteral("URI %1 is not a local file uri").arg
(
uri.toString()
)
);
}
QDir sourceDirectory(uri.toLocalFile());
QDir shaderDirectory
(
sourceDirectory.absoluteFilePath
(
QStringLiteral("shaders")
)
);
QStringList entries = shaderDirectory.entryList
(
QDir::NoDotAndDotDot | QDir::Files
);
setTotalSteps(entries.count() * 2);
setProgress(0);
QByteArray commonFragmentData = loadCommonFragmentData(uri);
if(commonFragmentData.length() > 0)
{
setTotalSteps(m_totalSteps -= 1);
incrementCompileStep();
}
QByteArray commonVertexData = loadCommonVertexData(uri);
if(commonVertexData.length() > 0)
{
setTotalSteps(m_totalSteps -= 1);
incrementCompileStep();
}
QByteArray globalData = loadGlobalData(uri);
if(globalData.length() > 0)
{
setTotalSteps(m_totalSteps -= 1);
incrementCompileStep();
}
for(const QString &entry : std::as_const(entries))
{
auto commonFragMatch = m_commonFragmentExpression.match(entry);
auto commonVertMatch = m_commonVertexExpression.match(entry);
if(commonFragMatch.hasMatch() || commonVertMatch.hasMatch())
{
continue;
}
auto fragMatch = m_fragmentExpression.match(entry);
auto vertMatch = m_vertexExpression.match(entry);
QFile file(shaderDirectory.absoluteFilePath(entry));
if(!file.open(QFile::ReadWrite))
{
throw FileException
(
QStringLiteral("Could not open file %1 for preperation").arg
(
shaderDirectory.absoluteFilePath(entry)
)
);
}
QByteArray fileData = file.readAll();
if(fileData.length() != file.size())
{
throw FileException
(
QStringLiteral("Could not read file %1 for preperation").arg
(
shaderDirectory.absoluteFilePath(entry)
)
);
}
file.close();
QByteArray commonData;
if(fragMatch.hasMatch())
{
commonData = commonFragmentData;
}
else if(vertMatch.hasMatch())
{
commonData = commonVertexData;
}
else
{
throw FileException
(
QStringLiteral("File %1 is an unrecognized shader type (frag/vert)").arg
(
shaderDirectory.absoluteFilePath(entry)
)
);
}
QByteArray combinedData = QByteArray
(
globalData + QByteArray("\n") +
commonData + QByteArray("\n") +
fileData
);
for(const QRegularExpression &expression : std::as_const(m_variableExpressions))
{
auto match = expression.match(combinedData);
if(match.hasMatch())
{
QByteArray replacement = QByteArray("ubuf.") + match.captured().toLocal8Bit();
combinedData.replace(match.captured().toLocal8Bit(), replacement);
}
}
QByteArray preparedData = QByteArray
(
m_header.toLocal8Bit() + QByteArray("\n") +
combinedData + QByteArray("\n") +
m_footer.toLocal8Bit()
);
if(!file.open(QFile::ReadWrite | QFile::Truncate))
{
throw FileException
(
QStringLiteral("Could not open file %1 for writing preperation").arg
(
shaderDirectory.absoluteFilePath(entry)
)
);
}
if(file.write(preparedData) != preparedData.length())
{
throw FileException
(
QStringLiteral("Could not write file %1 preperation").arg
(
shaderDirectory.absoluteFilePath(entry)
)
);
}
file.close();
incrementCompileStep();
}
}
auto PackCompiler::loadCommonFragmentData(const QUrl &uri) -> QByteArray
{
if(!uri.isLocalFile())
{
throw FileException
(
QStringLiteral("URI %1 is not a local file uri").arg
(
uri.toString()
)
);
}
QDir sourceDirectory
(
QStringLiteral("%1/shaders/").arg
(
uri.toLocalFile()
)
);
const QStringList entries = sourceDirectory.entryList
(
QDir::NoDotAndDotDot | QDir::Files
);
QByteArray data;
for(const QString &entry : entries)
{
if (entry.toLower() != QStringLiteral("common.frag"))
{
continue;
}
QFile file(sourceDirectory.absoluteFilePath(entry));
if(!file.open(QFile::ReadWrite))
{
throw FileException(QStringLiteral("Could not open common shader data"));
}
data = file.readAll();
if(data.length() != file.size())
{
throw FileException(QStringLiteral("Common fragment shader file read error"));
}
file.close();
break;
}
return data;
}
auto PackCompiler::loadCommonVertexData(const QUrl &uri) -> QByteArray
{
if(!uri.isLocalFile())
{
throw FileException
(
QStringLiteral("URI %1 is not a local file uri").arg
(
uri.toString()
)
);
}
QDir sourceDirectory
(
QStringLiteral("%1/shaders/").arg
(
uri.toLocalFile()
)
);
const QStringList entries = sourceDirectory.entryList
(
QDir::NoDotAndDotDot | QDir::Files
);
QByteArray data;
for(const QString &entry : entries)
{
if (entry.toLower() != QStringLiteral("common.vert"))
{
continue;
}
QFile file(sourceDirectory.absoluteFilePath(entry));
if(!file.open(QFile::ReadWrite))
{
throw FileException(QStringLiteral("Could not open common vertex shader data"));
}
data = file.readAll();
if(data.length() != file.size())
{
throw FileException(QStringLiteral("Common vertex shader file read error"));
}
file.close();
break;
}
return data;
}
auto PackCompiler::loadGlobalData(const QUrl &uri) -> QByteArray
{
if(!uri.isLocalFile())
{
throw FileException
(
QStringLiteral("URI %1 is not a local file uri").arg
(
uri.toString()
)
);
}
QDir sourceDirectory
(
QStringLiteral("%1/shaders/").arg
(
uri.toLocalFile()
)
);
const QStringList entries = sourceDirectory.entryList
(
QDir::NoDotAndDotDot | QDir::Files
);
QByteArray data;
for(const QString &entry : entries)
{
if (entry.toLower() != QStringLiteral("global.glsl"))
{
continue;
}
QFile file(sourceDirectory.absoluteFilePath(entry));
if(!file.open(QFile::ReadWrite))
{
throw FileException
(
QStringLiteral("Could not open global shader data")
);
}
data = file.readAll();
if(data.length() != file.size())
{
throw FileException
(
QStringLiteral("Global shader file read error")
);
}
file.close();
break;
}
return data;
}
auto PackCompiler::validateDirectory(const QUrl &uri) -> bool
{
QDir directory(uri.toLocalFile());
return
(
directory.exists(QStringLiteral("shaders")) &&
directory.exists(QStringLiteral("pack.json"))
);
}
auto PackCompiler::extract(const QUrl &sourceUri, const QUrl &destinationUri) noexcept(false) -> void
{
if(!sourceUri.isLocalFile() || !sourceUri.isValid())
{
throw FileException(QStringLiteral("Uri needs to be a local file"), 0);
}
QUrl outputUri
(
QStringLiteral("file://%1_proc").arg
(
sourceUri.toLocalFile()
)
);
QStringList arguments =
{
QStringLiteral("-xzf"),
sourceUri.toLocalFile(),
QStringLiteral("-C"),
outputUri.toLocalFile()
};
QProcess *process = new QProcess(this);
QObject::connect
(
process,
&QProcess::readyReadStandardOutput,
this,
[this, process]()
{
QByteArray processData = process->readAllStandardOutput();
setCompilerOutput(m_compilerOutput + processData);
}
);
QObject::connect
(
process,
&QProcess::readyReadStandardError,
this,
[this, process]()
{
QByteArray processData = process->readAllStandardError();
if(!processData.isValidUtf8())
{
qWarning() << QStringLiteral("Process output not valid UTF8 data");
return;
}
setCompilerOutput(m_compilerOutput + processData);
}
);
process->start(QStringLiteral("tar"), arguments);
if(!process->waitForStarted(3000))
{
process->deleteLater();
throw FileException(QStringLiteral("Could not start preprocessor"));
}
if(!process->waitForFinished())
{
process->deleteLater();
throw FileException(QStringLiteral("Preprocessor timeout"));
}
if(process->exitCode() != 0)
{
process->deleteLater();
throw ShaderCompilerException(m_compilerOutput);
}
process->deleteLater();
}
auto PackCompiler::copyFile(const QUrl &sourceUri, const QUrl &destinationUri) noexcept(false) -> void
{
if(!QFile::exists(sourceUri.toLocalFile()))
{
return;
}
if(!QFile::copy(sourceUri.toLocalFile(), destinationUri.toLocalFile()))
{
throw FileException(QStringLiteral("Could not copy file"));
}
}
auto PackCompiler::compile(const QUrl &uri) noexcept(false) -> void
{
if(!uri.isLocalFile() || !uri.isValid())
{
throw FileException(QStringLiteral("Uri needs to be a local file"), 0);
}
setState(Compiling);
QDir packDirectory(uri.toLocalFile());
QDir shaderDirectory
(
packDirectory.absoluteFilePath
(
QStringLiteral("shaders")
)
);
shaderDirectory.setNameFilters
(
{
QStringLiteral("*.frag"),
QStringLiteral("*.vert")
}
);
const QStringList shaders = shaderDirectory.entryList
(
QDir::Files | QDir::NoDotAndDotDot
);
for(const QString &shader : shaders)
{
if(shader.startsWith(QStringLiteral("common."), Qt::CaseInsensitive))
{
continue;
}
QUrl shaderUri
(
QStringLiteral("file://%1").arg
(
shaderDirectory.absoluteFilePath(shader)
)
);
preprocess(shaderUri);
appendVersion(shaderUri);
compileShader(shaderUri);
incrementCompileStep();
}
}
auto PackCompiler::preprocess(const QUrl &uri) noexcept(false) -> void
{
if(!uri.isLocalFile() || !uri.isValid())
{
throw FileException(QStringLiteral("Uri needs to be a local file"), 0);
}
QUrl outputUri
(
QStringLiteral("file://%1_proc").arg
(
uri.toLocalFile()
)
);
QStringList arguments =
{
QStringLiteral("-P"),
uri.toLocalFile(),
outputUri.toLocalFile()
};
QProcess *process = new QProcess(this);
QObject::connect
(
process,
&QProcess::readyReadStandardOutput,
this,
[this, process]()
{
QByteArray processData = process->readAllStandardOutput();
setCompilerOutput(m_compilerOutput + processData);
}
);
QObject::connect
(
process,
&QProcess::readyReadStandardError,
this,
[this, process]()
{
QByteArray processData = process->readAllStandardError();
if(!processData.isValidUtf8())
{
qWarning() << QStringLiteral("Process output not valid UTF8 data");
return;
}
setCompilerOutput(m_compilerOutput + processData);
}
);
process->start(QStringLiteral("cpp"), arguments);
if(!process->waitForStarted(3000))
{
process->deleteLater();
throw FileException(QStringLiteral("Could not start preprocessor"));
}
if(!process->waitForFinished())
{
process->deleteLater();
throw FileException(QStringLiteral("Preprocessor timeout"));
}
if(process->exitCode() != 0)
{
process->deleteLater();
throw ShaderCompilerException(m_compilerOutput);
}
process->deleteLater();
QFile::remove(uri.toLocalFile());
QFile::rename(outputUri.toLocalFile(), uri.toLocalFile());
}
auto PackCompiler::appendVersion(const QUrl &uri) noexcept(false) -> void
{
if(!uri.isValid() || !uri.isLocalFile())
{
throw FileException(QStringLiteral("File is not a valid local file"));
}
QFile file(uri.toLocalFile());
if(!file.open(QFile::ReadWrite))
{
throw FileException(QStringLiteral("Could not open file for appending"));
}
QByteArray data = file.readAll();
file.seek(0);
file.write(m_version);
file.write(QByteArray("\n\n"));
file.write(data);
file.close();
}
auto PackCompiler::compileShader(const QUrl &uri) noexcept(false) -> void
{
if(!uri.isLocalFile())
{
throw FileException
(
QStringLiteral("Uri needs to be a local file")
);
}
QString qsb = QStringLiteral("/usr/lib/qt6/bin/qsb");
if(!QFile::exists(qsb))
{
throw FileException(QStringLiteral("QSB missing"));
}
QUrl outputUri
(
QStringLiteral("file://%1.qsb").arg
(
uri.toLocalFile()
)
);
QStringList arguments =
{
QStringLiteral("--glsl"),
QStringLiteral("330es, 330, 440"),
QStringLiteral("-o"),
outputUri.toLocalFile(),
uri.toLocalFile()
};
QProcess *process = new QProcess(this);
QString path = uri.toDisplayString(QUrl::RemoveFilename | QUrl::RemoveScheme).remove(QStringLiteral("//"));
process->setWorkingDirectory(path);
QObject::connect
(
process,
&QProcess::readyReadStandardOutput,
this,
[this, process]()
{
QByteArray processData = process->readAllStandardOutput();
setCompilerOutput(m_compilerOutput + processData);
}
);
QObject::connect
(
process,
&QProcess::readyReadStandardError,
this,
[this, process]()
{
QByteArray processData = process->readAllStandardError();
if(!processData.isValidUtf8())
{
qWarning() << QStringLiteral("Process output not valid UTF8 data");
return;
}
setCompilerOutput(m_compilerOutput + processData);
}
);
process->start(qsb, arguments);
if(!process->waitForStarted(3000))
{
process->deleteLater();
throw FileException(QStringLiteral("Could not start shader compiler"));
}
if(!process->waitForFinished())
{
process->deleteLater();
throw FileException(QStringLiteral("Shader compiler timeout"));
}
if(process->exitCode() != 0)
{
process->deleteLater();
throw ShaderCompilerException(m_compilerOutput);
}
process->deleteLater();
if(!QFile::remove(uri.toLocalFile()))
{
throw FileException(QStringLiteral("Could not clean up"));
}
}
auto PackCompiler::removeDirectory(const QUrl &uri) -> bool
{
QStringList arguments =
{
QStringLiteral("-rf"),
uri.toLocalFile()
};
QProcess *process = new QProcess(this);
process->start(QStringLiteral("rm"), arguments);
if(!process->waitForStarted(3000))
{
process->deleteLater();
return false;
}
if(!process->waitForFinished())
{
process->deleteLater();
return false;
}
if(process->exitCode() != 0)
{
process->deleteLater();
return false;
}
process->deleteLater();
return true;
}
auto PackCompiler::setProgress(qreal progress) -> void
{
if(qFuzzyCompare(progress, m_progress))
{
return;
}
m_progress = progress;
Q_EMIT progressChanged();
}
void PackCompiler::setCompilerOutput(const QString &compilerOutput)
{
if (m_compilerOutput == compilerOutput)
{
return;
}
m_compilerOutput = compilerOutput;
emit compilerOutputChanged();
}
auto PackCompiler::setError(const QString &title, const QString &message, const QUrl &uri) -> void
{
if(title == m_errorTitle && message == m_errorMessage)
{
return;
}
m_errorMessage = std::move(message);
m_errorTitle = std::move(title);
if(uri.isValid() && uri.isLocalFile())
{
QFile errorFile(uri.toLocalFile());
if(errorFile.open(QFile::ReadWrite | QFile::Append))
{
errorFile.write(message.toLocal8Bit() + QByteArray("\n"));
}
}
Q_EMIT errorTitleChanged();
Q_EMIT errorMessageChanged();
Q_EMIT errorOcurred();
}
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<qreal>(m_currentStep) / m_totalSteps
);
}