Compare commits

..

11 Commits

Author SHA1 Message Date
Digital Artifex
c946859796 Initial GITEA commit 2026-04-15 19:49:06 -04:00
Digital Artifex
e0af6d5898 Removed empty ShaderPackMetadata.cpp file reference 2025-12-23 02:07:27 -05:00
Digital Artifex
4e91b71d81 Fixed implicit UTF8 conversions 2025-12-23 02:07:16 -05:00
Digital Artifex
a3747dd615 Removed empty ShaderPackMetadata.cpp file 2025-12-23 02:05:19 -05:00
Digital Artifex
f7dcb3be4c Changed default audio texture init 2025-11-25 15:53:05 -05:00
Digital Artifex
6e68d0ed4d Removed missed reference to channel invert setting 2025-11-25 15:52:12 -05:00
Digital Artifex
6ee2b3a8b4 Removed binding loop in a couple of settings 2025-11-25 15:50:16 -05:00
Digital Artifex
21d550a3fe Changed project picture 2025-11-22 16:41:58 -05:00
Digital Artifex
55413a3991 Revise README with new features and clarifications
Updated README to reflect new features and important notes.
2025-11-21 12:37:51 -05:00
Digital Artifex
9a1632f634 Merge branch 'main' of github.com:DigitalArtifex/kde-komplex-wallpaper-engine 2025-11-20 10:28:33 -05:00
Digital Artifex
d3962eb6f3 Revise installation instructions and requirements
Updated installation instructions for Arch and Debian, removed obsolete sections, and clarified API key setup.
2025-11-20 05:03:34 -05:00
13 changed files with 468 additions and 90 deletions

View File

@@ -1,27 +1,30 @@
# Komplex Wallpaper Engine # Komplex Wallpaper Engine
<img src="https://github.com/DigitalArtifex/DigitalArtifex/blob/f3eb531a9afda9a36d5753a82974b12e54172ad2/komplex.gif" width="100%" />
Komplex Wallpaper Engine is an advanced wallpaper engine for the KDE Plasma 6 Desktop Environment that allows the use of complex shader arrangements as a Wallpaper. Shader arrangements are a collection of shaders and various channel buffers that the shader is intended to manipulate, resulting in visually stunning live motion and reactive wallpapers. Komplex Wallpaper Engine is an advanced wallpaper engine for the KDE Plasma 6 Desktop Environment that allows the use of complex shader arrangements as a Wallpaper. Shader arrangements are a collection of shaders and various channel buffers that the shader is intended to manipulate, resulting in visually stunning live motion and reactive wallpapers.
[![Youtube Video](https://github.com/user-attachments/assets/19196d80-0a30-4e94-9260-6e450ae0f325)](https://www.youtube.com/watch?v=qjKEwrNts1A) > [!NOTE]
> `api.artifex.services` is now live! Shaders, Images, Videos and Cubemaps can now be obtained with the in-app media hubs
## Supported Engine Modes ## Supported Engine Modes
- Simple - Simple
- Komplex - Komplex
### Supported Channel Buffers ### Supported Channel Buffers
- Shaders* - Shaders
- Images - Images
- Videos - Videos
- Cubemaps** - Cubemaps
- Audio*** - Audio
- QML Scenes - QML Scenes
- Recursive Frame Buffer - Recursive Frame Buffer
*Shaders must be compiled with the `qsb` tool supplied with Qt. It may be available through your distribution's package manager. Follow the instructions in /tools/README.md to compile shaders for use with Qt. > [!IMPORTANT]
> GLSL Shaders can be manually imported but must be compiled with the `qsb` tool supplied with Qt. A tool has been provided as `~/.local/share/komplex/tools/stc.py` to assist with this process and to process shader importing. Please see the [Wiki](https://github.com/DigitalArtifex/kde-komplex-wallpaper-engine/wiki/Converting-ShaderToy-Pages-To-Komplex-Packs) for more information.
**The Cubemaps provided in this package are released under Creative Commons Attribution 3.0 Unported License and were obtained from [Humus](http://www.humus.name) > [!IMPORTANT]
> Audio reactivity requires Pipewire be the active audio server
***Audio reactivity requires KDE to be using Pipewire
### Engine Mode: Simple ### Engine Mode: Simple
@@ -36,29 +39,8 @@ The **Komplex** engine mode also allows for direct ShaderToy importing through t
## Installation (Release Packages) ## Installation (Release Packages)
Currently only x86_64 binaries are provided through release packages.
### Arch - Manual
Download the provided `zst` package from one of the releases and install it via pacman. This example assumes you downloaded release 1.0.6.
```
sudo pacman -U plasma6-wallpapers-komplex-bin-1.0.6-1-x86_64.pkg.tar.zst
```
### Arch - AUR ### Arch - AUR
Use your favorite AUR helper to install `plasma6-wallpapers-komplex-bin`. Use your favorite AUR helper to install `plasma6-wallpapers-komplex`.
### Debian - Manual
Download the provided `deb` package from one of the releases and install it via apt-get. This example assumes you downloaded release 1.0.6.
```
sudo apt-get install ./plasma6-wallpapers-komplex-bin-1.0.6-1-x86_64.deb
```
### Generic x86_64 Linux - Qt 6.9.1
Download the provided `tar.gz` from one of the releases, unpack it and run `install.sh`. This example assumes you downloaded release 1.0.6
```
tar -xvzf plasma6-wallpapers-komplex_1-0-6_linux_x86.tar.gz
cd plasma6-wallpapers-komplex_1-0-6_linux_x86.tar.gz
sudo ./install.sh
```
## Post-installation ## Post-installation
In order for the plugin to be registered with Plasma 6, we will need to restart the plasmashell session. In order for the g-streamer backend to take effect, a reboot may be required. In order for the plugin to be registered with Plasma 6, we will need to restart the plasmashell session. In order for the g-streamer backend to take effect, a reboot may be required.
@@ -70,8 +52,6 @@ systemctl --user restart plasma-plasmashell.service
## Installation (manual) ## Installation (manual)
Contents of the `data` directory should be placed in `~/.local/komplex/`
### Requirements ### Requirements
- qt6-base - qt6-base
- qt6-multimedia - qt6-multimedia
@@ -80,57 +60,33 @@ Contents of the `data` directory should be placed in `~/.local/komplex/`
- qt6-imageformats - qt6-imageformats
- qt6-quick3d - qt6-quick3d
- qt6-shadertools - qt6-shadertools
- qt6-webview
- unzip
- ECM (extra-cmake-modules) - ECM (extra-cmake-modules)
- plasma-desktop - plasma-desktop
- cmake - cmake
### Additional Requirements
- Pexels API Key - Free from [pexels.com](http://www.pexels.com)
- ShaderToy API Key - Free from [shadertoy.com](http://www.shadertoy.com)
### Instructions ### Instructions
After ensuring your system has all the required packages, run the following commands to clone the repo and enter it's directory. After ensuring your system has all the required packages, run the following commands to clone the repo and enter it's directory.
``` ```
git clone https://github.com/DigitalArtifex/kde-komplex-wallpaper-engine.git git clone https://github.com/DigitalArtifex/kde-komplex-wallpaper-engine.git
cd kde-komplex-wallpaper-engine cd kde-komplex-wallpaper-engine
```
Before we can compile the plugin, we will need to provide the ShaderToy and Pexels API keys through defines in `plugin/ShaderToyAPI.h` and `plugin/PlexelsAPI.h`
`plugin/ShaderToyAPI.h`
```cpp
#ifndef SHADERTOYAPI_H
#define SHADERTOYAPI_H
#define STK "SHADERTOYKEY"
#endif // SHADERTOYAPI_H
```
`plugin/ShaderToyAPI.h`
```cpp
#ifndef PEXELSAPI_H
#define PEXELSAPI_H
#define PAK "PEXELSKEY"
#endif // PEXELSAPI_H
```
Now that we have the API keys setup, we can finally build from source
```
mkdir build mkdir build
cmake -S ./ -B ./build cmake -S ./ -B ./build
cmake --build ./build cmake --build ./build
cmake --install ./build sudo cmake --install ./build
```
The contents of data/* should be copied to the system
```
sudo mkdir /usr/share/komplex/
cp -r data/* /usr/share/komplex/
``` ```
After installation, it is likely that you may experience scan lines and other artifacts when using the `Video Channel Buffer`. This is due to the default backend being FFMPEG, which is known to have such issues with Qt. You can correct this issue by using the `gstreamer` backend provided by qt6-multimedia-gstreamer. > [!WARNING]
``` > After installation, it is likely that you may experience scan lines and other artifacts when using the `Video Channel Buffer`. This is due to the default backend being FFMPEG, which is known to have such issues with Qt. You can correct this issue by using the following command to change to the `gstreamer` backend provided by qt6-multimedia-gstreamer.
sudo echo "export QT_MEDIA_BACKEND=gstreamer" >> /etc/profile >
``` > `sudo echo "export QT_MEDIA_BACKEND=gstreamer" >> /etc/profile`
This step is automatically executed by the Arch installation package.
## Credits ## Credits
@@ -138,8 +94,8 @@ This project was inspired by `KDE Shader Wallpaper`, `Wallpaper Engine` and othe
This project uses icons donated by [Icons8](http://www.icons8.com) This project uses icons donated by [Icons8](http://www.icons8.com)
Shader import functionality provided by [ShaderToy](http://www.shadertoy.com) API Shaders provided by various artists from [ShaderToy](http://www.shadertoy.com) (API)
Image and Video import functionality provided by [Pexels](http://www.pexels.com) API Image and Videos provided by [Pexels](http://www.pexels.com) (API)
To support me, this project or to find Linux themed hot sauces, you can [Buy me a coffee](https://ko-fi.com/digitalartifex) To support me, this project or to find Linux themed hot sauces, you can [Buy me a coffee](https://ko-fi.com/digitalartifex)

4
komplex.kdev4 Normal file
View File

@@ -0,0 +1,4 @@
[Project]
CreatedFrom=CMakeLists.txt
Manager=KDevCMakeManager
Name=komplex

View File

@@ -386,7 +386,6 @@ Rectangle
channel.mipmap = typeof json.mipmap === "boolean" ? json.mipmap : true channel.mipmap = typeof json.mipmap === "boolean" ? json.mipmap : true
channel.blending = typeof json.blending === "boolean" ? json.blending : false channel.blending = typeof json.blending === "boolean" ? json.blending : false
channel.samples = typeof json.samples === "number" ? json.samples : 1 channel.samples = typeof json.samples === "number" ? json.samples : 1
channel.invert = typeof json.invert === "boolean" ? json.invert : false
channel.visible = false channel.visible = false
/* /*

View File

@@ -825,7 +825,7 @@ Kirigami.FormLayout
text: i18n("Play/Pause the shader") text: i18n("Play/Pause the shader")
onCheckedChanged: () => onCheckedChanged: () =>
{ {
wallpaper.configuration.running = checked; // wallpaper.configuration.running = checked;
root.cfg_running = checked; root.cfg_running = checked;
} }
} }
@@ -866,7 +866,7 @@ Kirigami.FormLayout
onValueChanged: () => onValueChanged: () =>
{ {
mouseBiasField.text = String(value.toFixed(2)); mouseBiasField.text = String(value.toFixed(2));
wallpaper.configuration.mouseBias = mouseBiasField.text; // wallpaper.configuration.mouseBias = mouseBiasField.text;
root.cfg_mouseSpeedBias = mouseBiasField.text; root.cfg_mouseSpeedBias = mouseBiasField.text;
} }
} }
@@ -890,7 +890,7 @@ Kirigami.FormLayout
text = inputValue.toFixed(2); text = inputValue.toFixed(2);
mouseBiasSlider.value = inputValue; mouseBiasSlider.value = inputValue;
wallpaper.configuration.mouseBias = inputValue; // wallpaper.configuration.mouseBias = inputValue;
root.cfg_mouseSpeedBias = inputValue; root.cfg_mouseSpeedBias = inputValue;
} }
Keys.onPressed: (event) => Keys.onPressed: (event) =>

View File

@@ -16,6 +16,10 @@
AudioModel::AudioModel(QObject *parent) : QObject(parent) AudioModel::AudioModel(QObject *parent) : QObject(parent)
{ {
//init a blank 512x2 texture for the audio provider
m_frame = QPixmap(512, 2);
m_frame.fill(QColor());
m_impl_data = { nullptr, nullptr, {0, 0, {}}, 1, {}, {}, 0.0}; m_impl_data = { nullptr, nullptr, {0, 0, {}}, 1, {}, {}, 0.0};
m_impl_data.samples.reserve(4096); m_impl_data.samples.reserve(4096);
m_impl_data.smoothed.reserve(2048); m_impl_data.smoothed.reserve(2048);
@@ -151,7 +155,12 @@ void AudioModel::startCaptureAsync()
QPixmap AudioModel::frame() QPixmap AudioModel::frame()
{ {
if(!m_instance) if(!m_instance)
return QPixmap(); {
QPixmap frame = QPixmap(512, 2);
frame.fill(QColor());
return frame;
}
return m_instance->m_frame; return m_instance->m_frame;
} }

View File

@@ -13,7 +13,6 @@ add_library(
ShaderPackModel.cpp ShaderPackModel.cpp
AudioModel.cpp AudioModel.cpp
AudioImageProvider.cpp AudioImageProvider.cpp
ShaderPackMetadata.cpp
ShaderPackModel.h ShaderPackModel.h
AudioModel.h AudioModel.h
AudioImageProvider.h AudioImageProvider.h
@@ -53,7 +52,6 @@ qt_add_qml_module(
ShaderPackModel.cpp ShaderPackModel.cpp
AudioModel.cpp AudioModel.cpp
AudioImageProvider.cpp AudioImageProvider.cpp
ShaderPackMetadata.cpp
GeometryProvider.cpp GeometryProvider.cpp
GeometryProvider.h GeometryProvider.h
PexelsImageMetadata.h PexelsImageMetadata.h

View File

@@ -76,7 +76,7 @@ QHash<int, QByteArray> KomplexSearchModel::roleNames() const
void KomplexSearchModel::downloadMedia(QString fileLocation, QString fileUrl) void KomplexSearchModel::downloadMedia(QString fileLocation, QString fileUrl)
{ {
QUrl remoteUrl(QStringLiteral("http://api.artifex.services/v1%2").arg(fileUrl)); QUrl remoteUrl(QStringLiteral("https://api.artifex.services/v1%2").arg(fileUrl));
QNetworkRequest request(remoteUrl); QNetworkRequest request(remoteUrl);
QNetworkReply *reply = m_manager.get(request); QNetworkReply *reply = m_manager.get(request);
@@ -552,7 +552,7 @@ void KomplexSearchModel::install(quint64 index)
if(!process.waitForStarted(3000)) if(!process.waitForStarted(3000))
{ {
qWarning() << QStringLiteral("Could not start copy process: %1").arg(process.readAllStandardError()); qWarning() << QStringLiteral("Could not start copy process: %1").arg(QString::fromUtf8(process.readAllStandardError()));
setStatus(Error, QStringLiteral("Could not start install process")); setStatus(Error, QStringLiteral("Could not start install process"));
return; return;
} }
@@ -1201,4 +1201,4 @@ int KomplexSearchModel::rowCount(const QModelIndex &parent) const
{ {
Q_UNUSED(parent) Q_UNUSED(parent)
return m_data.size(); return m_data.size();
} }

View File

@@ -271,4 +271,4 @@ private:
}; };
Q_DECLARE_METATYPE(KomplexSearchModel) Q_DECLARE_METATYPE(KomplexSearchModel)
#endif // KomplexSearchModel_H #endif // KomplexSearchModel_H

View File

@@ -10,14 +10,15 @@
#include <QProcess> #include <QProcess>
#include <QTimer> #include <QTimer>
#include <QMutex> #include <QMutex>
#include <qdir.h> #include <QDir>
#include <qeventloop.h> #include <QEventLoop>
#include <qjsonarray.h> #include <QJsonDocument>
#include <qjsondocument.h> #include <QJsonParseError>
#include <qjsonobject.h> #include <QJsonObject>
#include <qjsonparseerror.h> #include <QJsonArray>
#include <qnetworkaccessmanager.h> #include <QJsonValue>
#include <qpixmap.h> #include <QNetworkAccessManager>
#include <QPixmap>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include "Komplex_global.h" #include "Komplex_global.h"

View File

@@ -530,7 +530,7 @@ void ShaderToySearchModel::install(quint64 index)
if(!process.waitForStarted(3000)) if(!process.waitForStarted(3000))
{ {
qWarning() << QStringLiteral("Could not start copy process: %1").arg(process.readAllStandardError()); qWarning() << QStringLiteral("Could not start copy process: %1").arg(QString::fromUtf8(process.readAllStandardError()));
setStatus(Error, QStringLiteral("Could not start install process")); setStatus(Error, QStringLiteral("Could not start install process"));
return; return;
} }

View File

@@ -268,5 +268,6 @@ private:
Q_PROPERTY(QString statusMessage READ statusMessage WRITE setStatusMessage NOTIFY statusMessageChanged FINAL) Q_PROPERTY(QString statusMessage READ statusMessage WRITE setStatusMessage NOTIFY statusMessageChanged FINAL)
Q_PROPERTY(QStringList videoSelections READ videoSelections WRITE setVideoSelections NOTIFY videoSelectionsChanged FINAL) Q_PROPERTY(QStringList videoSelections READ videoSelections WRITE setVideoSelections NOTIFY videoSelectionsChanged FINAL)
}; };
Q_DECLARE_METATYPE(ShaderToySearchModel) Q_DECLARE_METATYPE(ShaderToySearchModel)
#endif // ShaderToySearchModel_H #endif // ShaderToySearchModel_H

410
tools/stc.py Normal file
View File

@@ -0,0 +1,410 @@
# Komplex Wallpaper Engine
# Copyright (C) 2025 @DigitalArtifex | github.com/DigitalArtifex
#
# ShaderToyProcessor.py
#
# This file is used to convert the shaders in a shadertoy entry to a Komplex wallpaper
# package. It is designed to automate the process of preparing and compiling the shader
# files.
#
# The process is as follows:
# 1) Process the Common.frag file, if it exists
# 2) Read in the source file (.frag)
# 3) Append the Common.frag file, if it exists
# 4) Save file as `Name.tmp` into the temp directory
# 5) Process `Name.tmp` with `cpp -P`, outputting it as `Name.frag`
# 6) Delete the temp file
# 7) Prepare `Name.frag` by adding ubuf struct and version info
# 8) Replace known buffer calls to their ubuf equivalent
# 9) Compile `Name.frag`
# 10) Copy non-shader files
#
# Usage:
# python ShaderToyProcessor.py [options] -i input_directory [-o output_dirctory] [-t temp_directory]
#
# This file uses code that was originally part of the KDE Shader Wallpaper Project.
#
# 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 <https://www.gnu.org/licenses/>
import os
import re
import shutil
import argparse
import sys
import subprocess
import traceback
# Specify the directory where your .frag files are located
source_directory = 'src'
temp_directory = 'packs_processed'
output_directory = 'build'
dirname = ''
DELETE_AFTER_COMPILATION = False
# List of variables to update
variables_to_update = [
'iTime', 'iTimeDelta', 'iFrameRate', 'iSampleRate',
'iFrame', 'iDate', 'iMouse', 'iResolution',
r'iChannelTime', r'iChannelResolution'
]
# Header to be prepended to the shader file
# do not include the version declarative
header = '''#version 450
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];
} 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;
'''
# Footer to be appended, containing the main entry point
footer = '''
void main() {
vec4 color = vec4(0.0);
mainImage(color, fragCoord);
fragColor = color;
}
'''
def parse_arguments():
parser = argparse.ArgumentParser(
description='A shader processor for ShaderToy',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python ShaderToyProcessor.py -i ./src/deadly_halftones
"""
)
parser.add_argument('-i', '--input',
help='Input directory to process',
required=True)
parser.add_argument('-v', '--verbose',
action='store_true',
help='Enable verbose output')
parser.add_argument('-o', '--output',
default='packs_build',
help='Output Directory')
parser.add_argument('-t', '--temp',
default='packs_processed',
help='Temporary Files Directory')
parser.add_argument('-q', '--qsb',
default='/usr/lib/qt6/bin/qsb',
help='Path to QSB Compiler')
return parser.parse_args()
# 9) Compile `Name.frag`
# 10) Copy non-shader files
def compile():
args = parse_arguments()
if args.input:
source_directory = args.input
dirname = os.path.basename(source_directory)
qsb = args.qsb
output_directory = args.output + '/' + dirname
source_directory = temp_directory + '/' + dirname
if args.verbose:
print(f"Compiling: {source_directory}")
print(f"Output directory: {output_directory}")
last_file = ""
# Ensure output directory exists
os.makedirs(output_directory, exist_ok=True)
try:
# Iterate over all .frag files in the source directory
for root, dirs, files in os.walk(source_directory):
for file in files:
if file.endswith('.frag') and not file == 'Common.frag':
last_file = file
# Construct the full path to the source file
source_file_path = os.path.join(root, file)
# Construct new output path
relative_path = os.path.relpath(root, source_directory)
new_root = os.path.join(output_directory, relative_path)
os.makedirs(new_root, exist_ok=True)
output_file_name = file.replace('.frag', '.frag.qsb')
output_file_path = os.path.join(new_root, output_file_name)
# Construct and execute the command
cmd = [
qsb, '--glsl', '330', '--hlsl', '50', '--msl', '12',
'-o', output_file_path, source_file_path
]
subprocess.run(cmd, check=True)
# If the command was successful, delete the source file
if (DELETE_AFTER_COMPILATION):
os.remove(source_file_path)
if args.verbose:
print(f"Successfully converted and deleted: {file}")
elif args.verbose:
print(f"--Successfully compiled: {file}")
# Otherwise, just copy the file
else:
file_path = os.path.join(root, file)
# Construct new output path
relative_path = os.path.relpath(root, source_directory)
new_root = os.path.join(output_directory, relative_path)
os.makedirs(new_root, exist_ok=True)
new_file_path = os.path.join(new_root, file)
if args.verbose:
print(f"--Writing to: '{new_file_path}'")
shutil.copy(file_path, new_file_path)
except subprocess.CalledProcessError:
# If the command failed, do not delete the source file
print(f"Compiling failed for: {last_file}")
sys.exit(1)
except FileNotFoundError:
print(f"Error: Directory '{args.input}' not found")
sys.exit(1)
except PermissionError:
print(f"Error: Permission denied: '{args.input}'")
sys.exit(1)
except Exception as e:
print(traceback.format_exc())
sys.exit(1)
# 1) Process the Common.frag file, if it exists
# 2) Read in the source file (.frag)
# 3) Append the Common.frag file, if it exists
# 4) Save file as `Name.tmp` into the temp directory
# 5) Process `Name.tmp` with `cpp -P`, outputting it as `Name.frag`
# 6) Delete the temp file
# 10) Copy non-shader files
def process():
args = parse_arguments()
if args.temp:
temp_directory = args.temp
if args.input:
source_directory = args.input
else:
print(f"No input directory given")
sys.exit(1)
if args.verbose:
print(f"Processing: {source_directory}")
print(f"--Output directory: {temp_directory}")
last_file = ""
try:
for root, dirs, files in os.walk(source_directory):
# Grab the Common shader file, if it exists
common_file_path = os.path.join(root, 'Common.frag')
common_file_contents = ""
if os.path.exists(common_file_path):
with open(common_file_path, 'r') as f:
common_file_contents = f.read()
# 1. Remove any existing #version directive to avoid conflicts
common_file_contents = re.sub(r'^\s*#version\s+.*?\n', '', common_file_contents, flags=re.MULTILINE)
# 2. Remove any pre-existing main() function
common_file_contents = re.sub(r'void\s+main\s*\([^)]*\)\s*\{[\s\S]*?\}', '', common_file_contents)
# 3. Remove declarations in the common file that match the replacement vars
for var in variables_to_update:
pattern = r'(\w*\s*)(' + var + ')'
replacement = r'\1_\2'
common_file_contents = re.sub(pattern, replacement, common_file_contents)
for file in files:# Stage for processing, if a shader
if file.endswith('.frag') and not file == 'Common.frag':
last_file = file
file_path = os.path.join(root, file)
if args.verbose:
print(f"--Preparing: {file}")
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 1. Remove any existing #version directive to avoid conflicts
content = re.sub(r'^\s*#version\s+.*?\n', '', content, flags=re.MULTILINE)
# 2. Remove any pre-existing main() function
content = re.sub(r'void\s+main\s*\([^)]*\)\s*\{[\s\S]*?\}', '', content)
# 4. Assemble the final, complete shader
final_content = common_file_contents.strip() + '\n' + content.strip()
# Construct new output path
base_name, old_name = os.path.splitext(file)
relative_path = os.path.relpath(root, os.path.dirname(source_directory))
new_root = os.path.join(temp_directory, relative_path)
os.makedirs(new_root, exist_ok=True)
new_file_path = os.path.join(new_root, base_name + '.tmp')
if args.verbose:
print(f"--Writing to: '{new_file_path}'")
# Write to the new file
with open(new_file_path, 'w', encoding='utf-8') as f:
f.write(final_content)
# Process file with cpp
prepared_file_path = os.path.join(new_root, file)
if args.verbose:
print(f"--Processing to: '{prepared_file_path}'")
# Construct the command
cmd = [
'cpp', '-P', new_file_path, prepared_file_path
]
subprocess.run(cmd, check=True)
os.remove(new_file_path) #remove the temp file
# Otherwise, just copy the file
elif not file == 'Common.frag':
file_path = os.path.join(root, file)
# Construct new output path
relative_path = os.path.relpath(root, os.path.dirname(source_directory))
new_root = os.path.join(temp_directory, relative_path)
os.makedirs(new_root, exist_ok=True)
new_file_path = os.path.join(new_root, file)
if args.verbose:
print(f"--Writing to: '{new_file_path}'")
shutil.copy(file_path, new_file_path)
except subprocess.CalledProcessError:
# If the command failed, do not delete the source file
print(f"Compiling failed for: {last_file}")
sys.exit(1)
except FileNotFoundError:
print(f"Error: Directory '{args.input}' not found")
sys.exit(1)
except PermissionError:
print(f"Error: Permission denied: '{args.input}'")
sys.exit(1)
except Exception as e:
print(traceback.format_exc())
sys.exit(1)
# 7) Prepare `Name.frag` by adding ubuff struct and version info
# 8) Replace known buffer calls to their ubuff equivalent
# 10) Copy non-shader files
def prepare():
args = parse_arguments()
if args.temp:
temp_directory = args.temp
if args.verbose:
print(f"Preparing: {temp_directory}")
last_file = ""
try:
for root, dirs, files in os.walk(temp_directory):
for file in files:
# Stage for compiling, if a shader
if file.endswith('.frag') and not file == 'Common.frag':
last_file = file
file_path = os.path.join(root, file)
if args.verbose:
print(f"--Preparing: {file}")
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 1. Remove any existing #version directive to avoid conflicts
content = re.sub(r'^\s*#version\s+.*?\n', '', content, flags=re.MULTILINE)
# 2. Remove any pre-existing main() function
content = re.sub(r'void\s+main\s*\([^)]*\)\s*\{[\s\S]*?\}', '', content)
# 3. Prepend 'ubuf.' to all shadertoy uniforms
for var in variables_to_update:
pattern = r'(?<!\w)' + var
replacement = 'ubuf.' + var
content = re.sub(pattern, replacement, content)
# 4. Assemble the final, complete shader
final_content = header + '\n' + content.strip() + '\n' + footer
# Write to the new file
with open(file_path, 'w', encoding='utf-8') as f:
f.write(final_content)
except subprocess.CalledProcessError:
# If the command failed, do not delete the source file
print(f"Compiling failed for: {last_file}")
sys.exit(1)
except FileNotFoundError:
print(f"Error: Directory '{args.input}' not found")
sys.exit(1)
except PermissionError:
print(f"Error: Permission denied: '{args.input}'")
sys.exit(1)
except Exception as e:
print(traceback.format_exc())
sys.exit(1)
if __name__ == '__main__':
process()
prepare()
compile()