Compare commits
11 Commits
b74bffe0ab
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c946859796 | ||
|
|
e0af6d5898 | ||
|
|
4e91b71d81 | ||
|
|
a3747dd615 | ||
|
|
f7dcb3be4c | ||
|
|
6e68d0ed4d | ||
|
|
6ee2b3a8b4 | ||
|
|
21d550a3fe | ||
|
|
55413a3991 | ||
|
|
9a1632f634 | ||
|
|
d3962eb6f3 |
96
README.md
96
README.md
@@ -1,27 +1,30 @@
|
||||
# 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.
|
||||
|
||||
[](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
|
||||
- Simple
|
||||
- Komplex
|
||||
|
||||
### Supported Channel Buffers
|
||||
- Shaders*
|
||||
- Shaders
|
||||
- Images
|
||||
- Videos
|
||||
- Cubemaps**
|
||||
- Audio***
|
||||
- Cubemaps
|
||||
- Audio
|
||||
- QML Scenes
|
||||
- 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)
|
||||
|
||||
***Audio reactivity requires KDE to be using Pipewire
|
||||
> [!IMPORTANT]
|
||||
> Audio reactivity requires Pipewire be the active audio server
|
||||
|
||||
### Engine Mode: Simple
|
||||
|
||||
@@ -36,29 +39,8 @@ The **Komplex** engine mode also allows for direct ShaderToy importing through t
|
||||
|
||||
## 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
|
||||
Use your favorite AUR helper to install `plasma6-wallpapers-komplex-bin`.
|
||||
|
||||
### 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
|
||||
```
|
||||
Use your favorite AUR helper to install `plasma6-wallpapers-komplex`.
|
||||
|
||||
## 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.
|
||||
@@ -70,8 +52,6 @@ systemctl --user restart plasma-plasmashell.service
|
||||
|
||||
## Installation (manual)
|
||||
|
||||
Contents of the `data` directory should be placed in `~/.local/komplex/`
|
||||
|
||||
### Requirements
|
||||
- qt6-base
|
||||
- qt6-multimedia
|
||||
@@ -80,57 +60,33 @@ Contents of the `data` directory should be placed in `~/.local/komplex/`
|
||||
- qt6-imageformats
|
||||
- qt6-quick3d
|
||||
- qt6-shadertools
|
||||
- qt6-webview
|
||||
- unzip
|
||||
- ECM (extra-cmake-modules)
|
||||
- plasma-desktop
|
||||
- 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
|
||||
|
||||
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
|
||||
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
|
||||
cmake -S ./ -B ./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.
|
||||
```
|
||||
sudo echo "export QT_MEDIA_BACKEND=gstreamer" >> /etc/profile
|
||||
```
|
||||
This step is automatically executed by the Arch installation package.
|
||||
> [!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`
|
||||
|
||||
## 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)
|
||||
|
||||
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)
|
||||
|
||||
4
komplex.kdev4
Normal file
4
komplex.kdev4
Normal file
@@ -0,0 +1,4 @@
|
||||
[Project]
|
||||
CreatedFrom=CMakeLists.txt
|
||||
Manager=KDevCMakeManager
|
||||
Name=komplex
|
||||
@@ -386,7 +386,6 @@ Rectangle
|
||||
channel.mipmap = typeof json.mipmap === "boolean" ? json.mipmap : true
|
||||
channel.blending = typeof json.blending === "boolean" ? json.blending : false
|
||||
channel.samples = typeof json.samples === "number" ? json.samples : 1
|
||||
channel.invert = typeof json.invert === "boolean" ? json.invert : false
|
||||
channel.visible = false
|
||||
|
||||
/*
|
||||
|
||||
@@ -825,7 +825,7 @@ Kirigami.FormLayout
|
||||
text: i18n("Play/Pause the shader")
|
||||
onCheckedChanged: () =>
|
||||
{
|
||||
wallpaper.configuration.running = checked;
|
||||
// wallpaper.configuration.running = checked;
|
||||
root.cfg_running = checked;
|
||||
}
|
||||
}
|
||||
@@ -866,7 +866,7 @@ Kirigami.FormLayout
|
||||
onValueChanged: () =>
|
||||
{
|
||||
mouseBiasField.text = String(value.toFixed(2));
|
||||
wallpaper.configuration.mouseBias = mouseBiasField.text;
|
||||
// wallpaper.configuration.mouseBias = mouseBiasField.text;
|
||||
root.cfg_mouseSpeedBias = mouseBiasField.text;
|
||||
}
|
||||
}
|
||||
@@ -890,7 +890,7 @@ Kirigami.FormLayout
|
||||
|
||||
text = inputValue.toFixed(2);
|
||||
mouseBiasSlider.value = inputValue;
|
||||
wallpaper.configuration.mouseBias = inputValue;
|
||||
// wallpaper.configuration.mouseBias = inputValue;
|
||||
root.cfg_mouseSpeedBias = inputValue;
|
||||
}
|
||||
Keys.onPressed: (event) =>
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
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.samples.reserve(4096);
|
||||
m_impl_data.smoothed.reserve(2048);
|
||||
@@ -151,7 +155,12 @@ void AudioModel::startCaptureAsync()
|
||||
QPixmap AudioModel::frame()
|
||||
{
|
||||
if(!m_instance)
|
||||
return QPixmap();
|
||||
{
|
||||
QPixmap frame = QPixmap(512, 2);
|
||||
frame.fill(QColor());
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
return m_instance->m_frame;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ add_library(
|
||||
ShaderPackModel.cpp
|
||||
AudioModel.cpp
|
||||
AudioImageProvider.cpp
|
||||
ShaderPackMetadata.cpp
|
||||
ShaderPackModel.h
|
||||
AudioModel.h
|
||||
AudioImageProvider.h
|
||||
@@ -53,7 +52,6 @@ qt_add_qml_module(
|
||||
ShaderPackModel.cpp
|
||||
AudioModel.cpp
|
||||
AudioImageProvider.cpp
|
||||
ShaderPackMetadata.cpp
|
||||
GeometryProvider.cpp
|
||||
GeometryProvider.h
|
||||
PexelsImageMetadata.h
|
||||
|
||||
@@ -76,7 +76,7 @@ QHash<int, QByteArray> KomplexSearchModel::roleNames() const
|
||||
|
||||
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);
|
||||
QNetworkReply *reply = m_manager.get(request);
|
||||
|
||||
@@ -552,7 +552,7 @@ void KomplexSearchModel::install(quint64 index)
|
||||
|
||||
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"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,14 +10,15 @@
|
||||
#include <QProcess>
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
#include <qdir.h>
|
||||
#include <qeventloop.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
#include <qjsonparseerror.h>
|
||||
#include <qnetworkaccessmanager.h>
|
||||
#include <qpixmap.h>
|
||||
#include <QDir>
|
||||
#include <QEventLoop>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QPixmap>
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
#include "Komplex_global.h"
|
||||
|
||||
@@ -530,7 +530,7 @@ void ShaderToySearchModel::install(quint64 index)
|
||||
|
||||
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"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -268,5 +268,6 @@ private:
|
||||
Q_PROPERTY(QString statusMessage READ statusMessage WRITE setStatusMessage NOTIFY statusMessageChanged FINAL)
|
||||
Q_PROPERTY(QStringList videoSelections READ videoSelections WRITE setVideoSelections NOTIFY videoSelectionsChanged FINAL)
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ShaderToySearchModel)
|
||||
#endif // ShaderToySearchModel_H
|
||||
|
||||
410
tools/stc.py
Normal file
410
tools/stc.py
Normal 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()
|
||||
Reference in New Issue
Block a user