/*
* Komplex Wallpaper Engine
* Copyright (C) 2026 @DigitalArtifex
* https://digitalartifex.dev - https://github.com/DigitalArtifex
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
*/
#ifndef PACKCOMPILER_H
#define PACKCOMPILER_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "common/komplex_global.h"
#if __has_include("vulkan/vulkan_core.h")
#define HAS_VULKAN
#include
#endif
#if __has_include("GL/gl.h")
#define HAS_OPENGL
#include
#endif
struct KOMPLEX_EXPORT BuildContext
{
QMap commonData;
};
/**
* @brief The PackCompiler class
* [1] Extract Pack (GZip)
* [2] Verify directory is a pack and has shaders
* [3] Load Common shader code
* [4] Walk the shader directory
* [5] Load shader code
* [6] Append type specific common code and global code
* [7] Replace uniform variable use
* [8] Add uniform header
* [9] Add uniform footer
* [10] Run preprocessor to process macros
* [11] Append version macro
* [12] Compile with QShaderBaker
* [13] Save
*/
class PackCompiler : public QObject
{
Q_OBJECT
public:
/**
* @brief The State enum
* Enum of the possible states of the compiler
*/
enum State
{
Idle,
Compiling,
Complete,
Error
};
Q_ENUM(State)
explicit PackCompiler(QObject *parent = nullptr);
/**
* @brief progress
* Current compiler progress
* @return
*/
auto progress() -> qreal { return m_progress; }
/**
* @brief currentStep
* Current compiler step
* @return
*/
auto currentStep() -> qint64 { return m_currentStep; }
/**
* @brief totalSteps
* Total steps in the current compile
* @return
*/
auto totalSteps() -> qint64 { return m_totalSteps; }
/**
* @brief status
* Current status message
* @return
*/
auto status() const -> const QString & { return m_status; }
/**
* @brief state
* Current compiler state
* @return
*/
auto state() const -> State { return m_state; }
/**
* @brief reset
* Resets the compiler state
*/
auto reset() -> void;
/**
* @brief build
* Builds a Komplex pack from a tar.gz file
* @param uri
* Uri of the tar.gz
* @return
* Uri of the built pack
*/
auto build(const QUrl &uri) -> QUrl;
signals:
auto compilerOutputChanged() -> void;
auto stateChanged() -> void;
auto progressChanged() -> void;
auto currentStepChanged() -> void;
auto totalStepsChanged() -> void;
auto compileComplete(const QUrl &uri) -> void;
auto statusChanged() -> void;
auto packReady(const QString &) -> void;
auto errorStepChanged() -> void;
auto errorRatingChanged() -> void;
private:
//[1]
/**
* @brief extract
* Extracts the packfile and deletes the source
* @param uri of the tar.gz pack
* @return uri of the extracted directory
*/
auto extract(const QUrl &uri) noexcept(false) -> const QUrl;
//[2]
/**
* @brief validatePack
* Validates that the directory is a valid pack and has shaders to compile
* @param uri of the directory provided by extract
*/
auto validatePack(const QUrl &uri) const noexcept(false) -> void;
//[3]
/**
* @brief loadCommonData
* Loads the common and global shader code from the shader directory of the uri
* @param uri
*/
auto loadCommonData(const QUrl &uri) -> QMap;
//[4-5]
/**
* @brief processShaders
* Walks the shader directory, loads the code and calls 6-13 for each
* @param uri
* @param context
*/
auto processShaders(const QUrl &uri, BuildContext *context) -> void;
//[6]
/**
* @brief appendCommonData
* Appends the type specific common code to the shader data
* @param data
* Pointer to shader data
* @param suffix
* Shader file suffix
* @param context
* Pointer to build context object
*/
auto appendCommonData(QByteArray *data, const QByteArray &suffix, BuildContext *context) noexcept(true) -> void;
//[7]
/**
* @brief replaceUniformVariables
* Replaces standard variables such as iFrame with their uniform buffer name
* @param data
* Pointer to the shader data
*/
auto replaceUniformVariables(QByteArray *data) noexcept(true) -> void;
//[8]
/**
* @brief appendUniformHeader
* Adds header and uniform buffer to the shader data
* @param data
* Pointer to the shader data
*/
auto appendUniformHeader(QByteArray *data) noexcept(true) -> void;
//[9]
/**
* @brief appendUniformFooter
* Adds the uniform footer to the shader data
* @param data
* Pointer to the shader data
*/
auto appendUniformFooter(QByteArray *data) noexcept(true) -> void;
//[10]
/**
* @brief preprocess
* I have had issues with defines in code so passing it to a c/c++
* preprocessor is required. I am now using clang for this as clang
* is more modern and can use stdin
* @param data
*/
auto preprocess(QByteArray *data) noexcept(false) -> void;
//[11]
/**
* @brief appendVersion
* The version header confuses the preprocessor, so add it at the end
* @param data
*/
auto appendVersion(QByteArray *data) noexcept(true) -> void;
//[12]
/**
* @brief compile
* Compiles the shader data in memory and returns the shader object.
* @param data
* Shader data
* @param filename
* Filename of the shader
* @param stage
* Type of shader, based on suffix of original file
* @return
* Compiled shader object
*/
auto compile(const QByteArray &data, const QString &filename, const QShader::Stage stage) noexcept(false) -> QShader;
//[13]
/**
* @brief save
* Saves a serialized version of the shader to the specified uri
* @param uri
* Uri of the file to create
* @param shader
* The compiled shader
*/
auto save(const QUrl &uri, const QShader &shader) noexcept(false) -> void;
//@BEGIN helpers
/**
* @brief validateUri
* Validates that the uri is a local file or direcotry
* @param uri
*/
auto validateUri(const QUrl &uri) const noexcept(false) -> void;
/**
* @brief run
* Runs the supplied command with the given arguments as a QProcess. If
* data is supplied, it is written to the standard input channel after the
* process has started
* @param command
* System command or program to run
* @param arguments
* QStringList of the given arguments. Minimal wildcard expansion available.
* @param data
* Optional data to write
* @return
* Standard non-error output
*/
auto run(const QString &command, const QStringList &arguments, const QByteArray &data = {}) noexcept(false) -> QByteArray;
/**
* @brief readFile
* Reads the file at the given uri and returns its data
* @param uri
* Uri of the file
* @return
* File contents
*/
auto readFile(const QUrl &uri) noexcept(false) -> QByteArray;
/**
* @brief writeFile
* Writes the supplied data to the given uri
* @param uri
* Uri of the file
* @param data
* Data to write
*/
auto writeFile(const QUrl &uri, const QByteArray &data) noexcept(false) -> void;
/**
* @brief copyFile
* Copys file to new uri
* @param sourceUri
* File to copy
* @param destinationUri
* New file location/name
*/
auto copyFile(const QUrl &sourceUri, const QUrl &destinationUri) noexcept(false) -> void;
/**
* @brief createDirectory
* QDir::mkpath was failing, so this helper function calls mkdir -p
* @param uri
* Directory path to create
*/
auto createDirectory(const QUrl &uri) noexcept(false) -> void;
/**
* @brief removeDirectory
* Calls rm -rf, but should be replaced with an agnostic solution
* @param uri
* Directory to remove
*/
auto removeDirectory(const QUrl &uri) noexcept(false) -> void;
/**
* @brief getStageFromSuffix
* Supplies the stage/type of shader based on the shaders file extension
* @param suffix
* @return
*/
auto getStageFromSuffix(const QString &suffix) const -> QShader::Stage;
/**
* @brief setState
* Sets the current state of the compiler
* @param state
*/
auto setState(State state) -> void;
/**
* @brief setProgress
* Sets the current progress of the compiler
* @param progress
*/
auto setProgress(qreal progress) -> void;
/**
* @brief setTotalSteps
* Sets the total steps pack will take to build
* @param steps
*/
auto setTotalSteps(qint64 steps) -> void;
/**
* @brief setCurrentStep
* Sets the current step
* @param step
*/
auto setCurrentStep(qint64 step) -> void;
/**
* @brief setStatus
* Sets the current status message
* @param status
*/
auto setStatus(const QString &status) -> void;
/**
* @brief incrementCompileStep
* Increments the current step and updates progress
*/
auto incrementCompileStep() -> void;
//@END helpers
#ifdef HAS_VULKAN
/**
* @brief vulkanVersion
* Returns the current version reported by the vulkan api
* @return
*/
auto vulkanVersion() const -> qint32;
#endif
#ifdef HAS_OPENGL
/**
* @brief openGlVersion
* Returns the current version reported by the opengl api
* @return
*/
auto openGlVersion() const -> qint32;
#endif
//members
/**
* @brief m_updateVariables
* Standard variables expected by most online shaders, mainly
* shadertoy and similar.
*/
static inline const QStringList m_updateVariables
{
QStringLiteral("iTime"),
QStringLiteral("iTimeDelta"),
QStringLiteral("iFrameRate"),
QStringLiteral("iSampleRate"),
QStringLiteral("iFrame"),
QStringLiteral("iDate"),
QStringLiteral("iMouse"),
QStringLiteral("iResolution"),
QStringLiteral("iColorTheme"),
QStringLiteral("iChannelTime"),
QStringLiteral("iChannelResolution")
};
/**
* @brief m_variableExpressions
* Regular expressions of standardized variables. Built at runtime
*/
static inline QList m_variableExpressions;
/**
* @brief m_shaderNameFilters
* Name filters for supported shader types
*/
static inline const QStringList m_shaderNameFilters
{
QStringLiteral("*.frag"),
QStringLiteral("*.vert"),
QStringLiteral("*.tese"),
QStringLiteral("*.tesc"),
QStringLiteral("*.geom"),
QStringLiteral("*.comp"),
QStringLiteral("*.glsl")
};
/**
* @brief m_shaderStages
* Map of supported file suffix to their QShader::Stage
*/
static inline const QMap m_shaderStages
{
{
QStringLiteral("frag"),
QShader::FragmentStage
},
{
QStringLiteral("vert"),
QShader::VertexStage
},
{
QStringLiteral("tese"),
QShader::TessellationEvaluationStage
},
{
QStringLiteral("tesc"),
QShader::TessellationControlStage
},
{
QStringLiteral("geom"),
QShader::GeometryStage
},
{
QStringLiteral("comp"),
QShader::ComputeStage
}
};
/**
* @brief m_version
* Version to be appended to the shader before build
*/
static inline const QByteArray m_version
{
R"(#version 450)"
};
/**
* @brief m_header
* Header to be appended to the shader before build
*/
static inline const QByteArray m_header
{
R"(layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float iTime;
float iTimeDelta;
float iFrameRate;
float iSampleRate;
int iFrame;
vec4 iDate;
vec4 iMouse;
vec3 iResolution;
float iChannelTime[4];
vec3 iChannelResolution[4];
vec4 iColorTheme[4];
} ubuf;
layout(binding = 1) uniform sampler2D iChannel0;
layout(binding = 2) uniform sampler2D iChannel1;
layout(binding = 3) uniform sampler2D iChannel2;
layout(binding = 4) uniform sampler2D iChannel3;
vec2 fragCoord = vec2(qt_TexCoord0.x, 1.0 - qt_TexCoord0.y) * ubuf.iResolution.xy;)"
};
/**
* @brief m_footer
* Footer to be appended to the shader before build
*/
static inline const QByteArray m_footer
{
R"(
void main() {
vec4 color = vec4(0.0);
mainImage(color, fragCoord);
fragColor = color;
})"
};
State m_state;
qreal m_currentStep = 0;
qreal m_progress = 0;
qreal m_totalSteps = 0;
QString m_status;
Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged FINAL)
Q_PROPERTY(qint64 totalSteps READ totalSteps NOTIFY totalStepsChanged FINAL)
Q_PROPERTY(qint64 currentStep READ currentStep NOTIFY currentStepChanged FINAL)
Q_PROPERTY(QString status READ status WRITE setStatus NOTIFY statusChanged FINAL)
Q_PROPERTY(State state READ state NOTIFY stateChanged FINAL)
};
Q_DECLARE_METATYPE(PackCompiler)
#endif // PackCompiler_H