13#include <QApplication>
16#include <QColorDialog>
22#include <QDialogButtonBox>
29#include <QGuiApplication>
33#include <QInputDialog>
39#include <QPlainTextEdit>
42#include <QRegularExpression>
45#include <QStandardPaths>
49#include <QTreeWidgetItem>
64#if defined(__linux__) || defined(__APPLE__)
76 const qsizetype newline = buffer.indexOf(
'\n');
77 const qsizetype carriage = buffer.indexOf(
'\r');
78 qsizetype separator = newline;
79 if (separator < 0 || (carriage >= 0 && carriage < separator)) {
86 line = buffer.left(separator);
87 qsizetype consumed = 1;
88 if (buffer.at(separator) == QLatin1Char(
'\r') && separator + 1 < buffer.size() && buffer.at(separator + 1) == QLatin1Char(
'\n')) {
91 buffer.remove(0, separator + consumed);
96 QString normalized = line;
97 static const QRegularExpression ANSI_ESCAPE(QStringLiteral(
"\\x1b\\[[0-9;?]*[ -/]*[@-~]"));
98 normalized.remove(ANSI_ESCAPE);
99 normalized = normalized.trimmed();
100 if (normalized.startsWith(
"[INFO ]") || normalized.startsWith(
"[DEBUG]") || normalized.startsWith(
"[TRACE]")) {
103 if (normalized.startsWith(QLatin1Char(
'|')) && normalized.contains(QRegularExpression(QStringLiteral(
"\\d+\\s*/\\s*\\d+")))) {
111 if (value.isEmpty()) {
112 return QStringLiteral(
"\"\"");
115 QString quoted = QStringLiteral(
"\"");
116 qsizetype backslash_count = 0;
117 for (
const QChar character : value) {
118 if (character == QLatin1Char(
'\\')) {
122 if (character == QLatin1Char(
'"')) {
123 quoted += QString(backslash_count * 2 + 1, QLatin1Char(
'\\'));
128 quoted += QString(backslash_count, QLatin1Char(
'\\'));
132 quoted += QString(backslash_count * 2, QLatin1Char(
'\\'));
133 quoted += QLatin1Char(
'"');
136 if (value.isEmpty()) {
140 out.replace(
"'",
"'\\''");
141 return "'" + out +
"'";
145 QString
buildShellCommand(
const QStringList &envAssignments,
const QString &program,
const QStringList &arguments) {
147 parts.reserve(envAssignments.size() + 1 + arguments.size());
148 for (
const QString &entry : envAssignments) {
149 int eq = entry.indexOf(
'=');
153 QString key = entry.left(eq);
154 QString value = entry.mid(eq + 1);
156 parts << (QStringLiteral(
"set \"") + key + QLatin1Char(
'=') + value + QStringLiteral(
"\" &&"));
162 for (
const QString &arg : arguments) {
165 return parts.join(
' ');
168 void replace_file(
const std::filesystem::path &source,
const std::filesystem::path &destination, std::error_code &error) {
170 if (MoveFileExW(source.c_str(), destination.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != FALSE) {
174 error = std::error_code(
static_cast<int>(GetLastError()), std::system_category());
176 std::filesystem::rename(source, destination, error);
181 QStringList defaultLinuxRunEnvAssignments() {
182 QStringList envAssignments;
183 QString uid = QString::number(getuid());
184 QString userRunPath =
"/run/user/" + uid;
185 QByteArray display = qgetenv(
"DISPLAY");
186 QByteArray waylandDisplay = qgetenv(
"WAYLAND_DISPLAY");
187 QByteArray sessionType = qgetenv(
"XDG_SESSION_TYPE");
188 if (!waylandDisplay.isEmpty() && sessionType ==
"wayland") {
189 envAssignments <<
"SDL_VIDEODRIVER=wayland";
190 }
else if (!display.isEmpty()) {
191 envAssignments <<
"SDL_VIDEODRIVER=x11";
192 }
else if (!waylandDisplay.isEmpty()) {
193 envAssignments <<
"SDL_VIDEODRIVER=wayland";
195 if (QDir(userRunPath).exists()) {
196 envAssignments << (
"XDG_RUNTIME_DIR=" + userRunPath);
197 envAssignments << (
"PULSE_SERVER=unix:" + userRunPath +
"/pulse/native");
199 envAssignments <<
"vblank_mode=0";
200 return envAssignments;
205 QString dirPath = QCoreApplication::applicationDirPath();
207 return dirPath +
"/../Helpers";
209 if (QFileInfo::exists(dirPath +
"/data/win-icon.png"))
211 const QString installedPath = QDir::cleanPath(dirPath +
"/../share/acmx2");
212 if (QFileInfo::exists(installedPath +
"/data/win-icon.png"))
213 return installedPath;
220 QSettings settings(
"LostSideDead");
222 if (mode == QStringLiteral(
"custom")) {
224 if (compiler.isEmpty()) {
225 error = QStringLiteral(
"The custom ACMXVK shader compiler path is empty. Select "
226 "one in Properties (Ctrl+,).");
229 if (QFileInfo(compiler).isRelative()) {
230 const QString resolved = QStandardPaths::findExecutable(compiler);
231 if (!resolved.isEmpty())
234 const QFileInfo compilerInfo(compiler);
235 if (!compilerInfo.isFile() || !compilerInfo.isExecutable()) {
236 error = QStringLiteral(
"The configured ACMXVK shader compiler is not an "
237 "executable file: %1")
241 return compilerInfo.absoluteFilePath();
246 const QString compilerName = QStringLiteral(
"glslc.exe");
247 const QFileInfo bundledCompiler(QDir(QCoreApplication::applicationDirPath()).filePath(compilerName));
248 if (bundledCompiler.isFile() && bundledCompiler.isExecutable())
249 compiler = bundledCompiler.absoluteFilePath();
251 const QString compilerName = QStringLiteral(
"glslc");
253 if (compiler.isEmpty())
254 compiler = QStandardPaths::findExecutable(compilerName);
255 if (compiler.isEmpty()) {
256 const QString sdk = QString::fromLocal8Bit(qgetenv(
"VULKAN_SDK"));
257 const QString sdkCompiler = QDir(sdk).filePath(QStringLiteral(
"bin/") + compilerName);
258 if (!sdk.isEmpty() && QFileInfo(sdkCompiler).isExecutable())
259 compiler = sdkCompiler;
261 if (compiler.isEmpty()) {
262 error = QStringLiteral(
"glslc was not found in PATH or VULKAN_SDK. Select a custom "
263 "compiler in Properties (Ctrl+,).");
270 const QFileInfo bundled_server(QDir(QCoreApplication::applicationDirPath()).filePath(QStringLiteral(
"sd-server.exe")));
271 if (bundled_server.isFile()) {
272 return bundled_server.absoluteFilePath();
274 return QStringLiteral(
"sd-server.exe");
276 const QFileInfo bundled_server(QStringLiteral(
"/app/bin/sd-server"));
277 if (bundled_server.isExecutable()) {
278 return bundled_server.absoluteFilePath();
280 return QStringLiteral(
"sd-server");
288 const QString applicationDir = QCoreApplication::applicationDirPath();
290 const QString bundleResources = QDir::cleanPath(applicationDir +
"/../Resources/acmxvk");
291 if (QFileInfo(bundleResources).isDir())
292 return bundleResources;
294 QStringList candidates;
295 QString resolvedExecutable = executable;
296 if (QFileInfo(resolvedExecutable).isRelative()) {
297 const QString pathExecutable = QStandardPaths::findExecutable(resolvedExecutable);
298 if (!pathExecutable.isEmpty())
299 resolvedExecutable = pathExecutable;
301 const QFileInfo executableInfo(resolvedExecutable);
302 if (!executableInfo.absolutePath().isEmpty()) {
303 candidates << QDir::cleanPath(executableInfo.absolutePath() +
"/../share/acmxvk");
305 if (!libraryPath.isEmpty())
306 candidates << QDir::cleanPath(QFileInfo(libraryPath).absolutePath());
307 candidates << QDir::cleanPath(applicationDir +
"/../share/acmxvk") << QStringLiteral(
"/usr/local/share/acmxvk") << QStringLiteral(
"/opt/homebrew/share/acmxvk") << QStringLiteral(
"/usr/share/acmxvk");
308 for (
const QString &candidate : candidates) {
309 if (QFileInfo(candidate +
"/data").isDir())
315 return applicationDir;
321 if (!error.isEmpty())
331 return std::any_of(entries.cbegin(), entries.cend(), [](
const QString &entry) { return entry.endsWith(
".frag", Qt::CaseInsensitive) || entry.endsWith(
".comp", Qt::CaseInsensitive); });
334 QString
acmxvk_build_directory(
const QString &sourceLibrary) {
return QDir(sourceLibrary).filePath(QStringLiteral(
".acmxvk-build")); }
336 QString
acmxvk_runtime_shader_name(
const QString &sourceName) {
return sourceName.endsWith(
".spv", Qt::CaseInsensitive) ? sourceName : sourceName + QStringLiteral(
".spv"); }
345 const QFileInfo source_file(QDir(source_library).filePath(source_name));
347 if (!runtime_file.isFile())
349 if (source_file.isFile() && runtime_file.lastModified() < source_file.lastModified()) {
356 QStringList source_entries;
357 QStringList runtime_entries;
362 QStringList expected_entries;
363 expected_entries.reserve(source_entries.size());
364 for (
const QString &entry : source_entries)
366 if (runtime_entries != expected_entries) {
367 error = QObject::tr(
"The ACMXVK runtime manifest does not match the source "
368 "shader list. Choose Playback > Build before running.");
372 bool uniformMetadataMatches =
false;
376 if (!uniformMetadataMatches) {
377 error = QObject::tr(
"The ACMXVK runtime custom-uniform metadata is out of date. "
378 "Choose Playback > Build before running.");
386 runtimeLibrary = selectedLibrary;
388 return error.isEmpty();
392 error = QObject::tr(
"The ACMXVK source library has not been built yet. "
393 "Choose Playback > Build first.\n\nExpected output: %1")
394 .arg(runtimeLibrary);
398 if (!error.isEmpty())
401 error = QObject::tr(
"Compiled output is not an ACMXVK runtime library: %1").arg(runtimeLibrary);
404 QStringList sourceEntries;
410 for (
const QString &sourceEntry : sourceEntries) {
411 const QFileInfo sourceFile(QDir(selectedLibrary).filePath(sourceEntry));
413 if (!runtimeFile.isFile() || runtimeFile.lastModified() < sourceFile.lastModified()) {
414 error = QObject::tr(
"The ACMXVK build is missing or older than %1. "
415 "Choose Playback > Build before running.")
424 QSettings settings(
"LostSideDead",
"acmx2");
425 return settings.value(
"interface/texture_cache_array",
false).toBool();
428 QSize
storedResolution(QSettings &settings,
const QString &key,
const QSize &fallback,
bool defaultIsEmpty) {
429 const QString text = settings.value(key).toString().trimmed();
430 if (text.compare(
"Default", Qt::CaseInsensitive) == 0) {
431 return defaultIsEmpty ? QSize(0, 0) : fallback;
434 static const QRegularExpression resolutionPattern(R
"(^\s*(\d+)\s*[xX]\s*(\d+)\s*$)");
435 const QRegularExpressionMatch match = resolutionPattern.match(text);
436 if (!match.hasMatch()) {
440 const int width = match.captured(1).toInt();
441 const int height = match.captured(2).toInt();
442 return width > 0 && height > 0 ? QSize(width, height) : fallback;
449 const std::filesystem::path libraryFsPath(libraryPath.toStdString());
450 const std::filesystem::path absoluteLibrary = std::filesystem::absolute(libraryFsPath, ec);
451 std::string key = ec ? libraryPath.toStdString() : absoluteLibrary.lexically_normal().string();
452 key +=
"|s=" + std::to_string(cacheSize);
453 key +=
"|a=" + std::to_string(useArray ? 1 : 0);
454 std::ostringstream nameStream;
455 nameStream <<
".shader_cache_" << std::hex << std::hash<std::string>{}(key);
456 return QString::fromStdString(nameStream.str());
466 const QString assetsCache = assets +
"/" + filename;
467 const QString libCache = libraryPath +
"/" + filename;
468 if (QFileInfo::exists(assetsCache))
470 if (QFileInfo::exists(libCache))
478 QHash<QString, bool> result;
480 if (!f.open(QIODevice::ReadOnly))
483 auto readU32 = [&](quint32 &v) ->
bool {
return f.read(
reinterpret_cast<char *
>(&v),
sizeof(v)) == qint64(
sizeof(v)); };
484 auto readU64 = [&](quint64 &v) ->
bool {
return f.read(
reinterpret_cast<char *
>(&v),
sizeof(v)) == qint64(
sizeof(v)); };
485 auto readU8 = [&](quint8 &v) ->
bool {
return f.read(
reinterpret_cast<char *
>(&v),
sizeof(v)) == qint64(
sizeof(v)); };
486 auto readStr = [&](QString &out) ->
bool {
490 QByteArray buf = f.read(len);
491 if (quint32(buf.size()) != len)
493 out = QString::fromUtf8(buf);
496 auto skipBytes = [&](quint32 n) ->
bool {
return f.skip(n) == qint64(n); };
498 constexpr quint32 CACHE_MAGIC = 0x53484452;
499 constexpr quint32 CACHE_VERSION = 4;
501 quint32 magic = 0, version = 0;
502 if (!readU32(magic) || !readU32(version))
504 if (magic != CACHE_MAGIC || version != CACHE_VERSION)
513 quint8 dual_mode = 0;
514 if (!readU8(dual_mode))
521 for (quint32 i = 0; i < count; ++i) {
525 quint8 shader_kind = 0;
526 if (!readU8(shader_kind) || shader_kind > 2)
528 quint8 failed_flag = 0;
529 if (!readU8(failed_flag))
531 quint64 source_hash = 0;
532 if (!readU64(source_hash))
534 quint32 fmt2d = 0, sz2d = 0, fmt3d = 0, sz3d = 0;
535 if (!readU32(fmt2d) || !readU32(sz2d) || !skipBytes(sz2d))
537 if (!readU32(fmt3d) || !readU32(sz3d) || !skipBytes(sz3d))
539 result.insert(name, failed_flag != 0);
546 return QStringLiteral(
"-");
547 return dt.toLocalTime().toString(QStringLiteral(
"yyyy-MM-dd HH:mm"));
555 auto updateShaderMenuState = [
this](QProcess::ProcessState state) {
556 const bool running = (state == QProcess::Running);
587 connect(
process, &QProcess::stateChanged,
this, updateShaderMenuState);
588 updateShaderMenuState(
process->state());
589 connect(
process, &QProcess::readyReadStandardOutput,
this, [
this]() {
597 this->
Write(line +
"<br>");
601 connect(
process, &QProcess::readyReadStandardError,
this, [
this]() {
602 auto writeStderrLine = [
this](
const QString &line) {
603 if (line.contains(
"GStreamer"))
608 if (line.contains(
"[ WARN:") || line.contains(
"[WARN "))
609 this->
Write(
"<b style='color:#ccaa00;'>Warning:</b> " + line +
"<br>");
611 this->
Write(
"<b style='color:red;'>Error:</b> " + line +
"<br>");
618 writeStderrLine(line);
627 connect(
process,
static_cast<void (QProcess::*)(
int, QProcess::ExitStatus)
>(&QProcess::finished),
this, [
this](
int exitCode, QProcess::ExitStatus exitStatus) {
642 QTextStream stream(&text);
648 if (exitStatus == QProcess::CrashExit) {
665 Log(tr(
"ACMXVK build ready: %1").arg(acmxvk_build_directory(
shader_path)));
667 Log(tr(
"<b style='color:red;'>ACMXVK build failed "
668 "with exit code %1.</b>")
674 if (!pruneLibraryPath.isEmpty() && exitCode == 0 && exitStatus == QProcess::NormalExit) {
675 QStringList sourceShaders;
676 QString manifestError;
678 Log(tr(
"<b style='color:red;'>Broken sources were "
679 "pruned, but the source manifest could not "
681 .arg(manifestError.toHtmlEscaped()));
682 QMessageBox::warning(
this,
683 tr(
"Remove Broken Shaders"),
684 tr(
"Broken source files were deleted, but the "
685 "source manifest could not be updated.\n\n%1")
686 .arg(manifestError));
688 QStringList retainedShaders;
689 int removedCount = 0;
690 const QDir sourceDirectory(pruneLibraryPath);
691 for (
const QString &shader : sourceShaders) {
692 const QString suffix = QFileInfo(shader).suffix().toLower();
693 const bool sourceEntry = suffix == QStringLiteral(
"frag") || suffix == QStringLiteral(
"comp");
694 if (sourceEntry && !QFileInfo(sourceDirectory.filePath(shader)).isFile()) {
697 retainedShaders.append(shader);
702 Log(tr(
"<b style='color:red;'>Broken sources "
703 "were pruned, but the source manifest "
704 "could not be updated: %1</b>")
705 .arg(manifestError.toHtmlEscaped()));
706 QMessageBox::warning(
this,
707 tr(
"Remove Broken Shaders"),
708 tr(
"%1 source file(s) were permanently "
709 "deleted, but library.json could not be "
712 .arg(manifestError));
716 Log(tr(
"Remove Broken completed: %1 source "
717 "shader(s) permanently deleted.")
719 QMessageBox::information(
this,
720 tr(
"Remove Broken Shaders"),
721 removedCount > 0 ? tr(
"Removed %1 broken source shader(s) "
722 "and updated library.json.\n\n"
723 "This deletion cannot be undone.")
725 : tr(
"The build completed and no broken "
726 "source shaders were found."));
731 Log(tr(
"ACMXVK build succeeded; resuming the requested "
733 QTimer::singleShot(0,
this, [
this, resume_action]() {
753 connect(
hdr10Process, &QProcess::readyReadStandardOutput,
this, [
this]() {
754 QString output = QString::fromUtf8(
hdr10Process->readAllStandardOutput());
755 output.replace(
"\n",
"<br>");
758 connect(
hdr10Process, &QProcess::readyReadStandardError,
this, [
this]() {
759 QString output = QString::fromUtf8(
hdr10Process->readAllStandardError());
760 output.replace(
"\n",
"<br>");
763 this->
Write(
"<span style='color:#88aaff;'>" + output +
"</span>");
765 connect(
hdr10Process,
static_cast<void (QProcess::*)(
int, QProcess::ExitStatus)
>(&QProcess::finished),
this, [
this](
int exitCode, QProcess::ExitStatus) {
767 QTextStream stream(&text);
768 stream <<
"ffmpeg (HDR10): Exited with Code: " << exitCode;
773 setStyleSheet(
" QMainWindow { background-color: rgb(0,0,0); }");
777 setGeometry(150, 150, 1280, 720);
778 setWindowTitle(
"ACMX2 - Interface");
779 QMenuBar *menuBarPtr = menuBar();
781 menuBar()->setNativeMenuBar(
false);
782 fileMenu = menuBarPtr->addMenu(tr(
"File"));
783 cameraMenu = menuBarPtr->addMenu(tr(
"Session"));
786 runMenu = menuBarPtr->addMenu(tr(
"Run"));
787 listMenu = menuBarPtr->addMenu(tr(
"List"));
788 viewMenu = menuBarPtr->addMenu(tr(
"View"));
789 helpMenu = menuBarPtr->addMenu(tr(
"Help"));
805 connect(
stayOnTopAction, &QAction::toggled,
this, [
this](
bool checked) {
807 setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
809 setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
812 if (checked && QGuiApplication::platformName() ==
"wayland") {
813 Log(
"Stay on Top may not work on Wayland. Launch with QT_QPA_PLATFORM=xcb for X11 support.");
817 QAction *metadataAction =
new QAction(tr(
"Media Metadata Viewer..."),
this);
818 metadataAction->setShortcut(QKeySequence(
"Ctrl+Alt+V"));
821 viewMenu->addAction(metadataAction);
827 loadRecentMenu->menuAction()->setShortcut(QKeySequence(
"Ctrl+Shift+O"));
840 cameraSet =
new QAction(tr(
"Session Properties"),
this);
841 cameraSet->setShortcut(QKeySequence(
"Ctrl+Shift+P"));
844 audioSet =
new QAction(tr(
"Audio Settings"),
this);
845 audioSet->setShortcut(QKeySequence(
"Ctrl+Shift+A"));
881 QAction *runMenu_clearLog =
new QAction(tr(
"Clear Log"),
this);
882 runMenu_clearLog->setShortcut(QKeySequence(
"Ctrl+L"));
883 connect(runMenu_clearLog, &QAction::triggered,
this, [
this]() {
bottomTextBox->clear(); });
884 runMenu->addAction(runMenu_clearLog);
895 normalizedTimeAction->setToolTip(tr(
"Advance shader time by a fixed amount per output frame."));
898 QSettings settings(
"LostSideDead",
"acmx2");
899 settings.setValue(
"interface/normalized_time", checked);
903 play_stop =
new QAction(tr(
"Stop"),
this);
904 play_stop->setShortcut(QKeySequence(
"Shift+F5"));
906 connect(
play_stop, &QAction::triggered,
this, [=]() {
907 if (
process->state() == QProcess::Running) {
921 playlistAction =
new QAction(tr(
"Shader Playlist Settings..."),
this);
932 fixBuildAction->setToolTip(tr(
"Build ACMXVK while omitting shaders that fail to compile."));
961 runFromCacheAction->setToolTip(tr(
"Shader binary caching is not supported on macOS."));
968 Log(
"Shader cache enabled - will use cached shaders if available");
970 Log(
"Shader cache disabled - shaders will be recompiled each run");
994 listMenu_new =
new QAction(tr(
"New Shader Library"),
this);
995 listMenu_new->setShortcut(QKeySequence(
"Ctrl+Shift+N"));
1021 listMenu_up =
new QAction(tr(
"Shift Shader Up"),
this);
1051 QMessageBox::information(
this, tr(
"Find in Files"), tr(
"Load a shader library before searching its files."));
1059 dialog->activateWindow();
1072 QMessageBox box(
this);
1073 box.setWindowTitle(
"About ACMX2");
1074 box.setWindowIcon(QIcon(
":/win-icon.png"));
1075 const QString info = QStringLiteral(
"<p><b>ACMX %1</b><br>"
1076 "(C) 2026 %2 Software<br>"
1077 "<a href=\"https://lostsidedead.biz\">"
1078 "http://lostsidedead.biz</a><br>"
1079 "This software is dedicated to all that have "
1080 "experienced mental health issues.</p>")
1082 box.setTextFormat(Qt::RichText);
1083 box.setTextInteractionFlags(Qt::TextBrowserInteraction);
1085 for (QLabel *label : box.findChildren<QLabel *>())
1086 label->setOpenExternalLinks(
true);
1087 QPixmap bigIcon(
":/win-icon.png");
1088 if (!bigIcon.isNull()) {
1089 QPixmap resizedIcon = bigIcon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1090 box.setIconPixmap(resizedIcon);
1100 if (!shaderName.isEmpty())
1105 list_view->setHeaderLabels({tr(
"#"), tr(
"Name"), tr(
"Last Modified"), tr(
"Compile Health"), tr(
"Type")});
1108 list_view->setAlternatingRowColors(
false);
1109 list_view->setSelectionMode(QAbstractItemView::SingleSelection);
1110 list_view->setSelectionBehavior(QAbstractItemView::SelectRows);
1111 list_view->setContextMenuPolicy(Qt::CustomContextMenu);
1113 list_view->setAllColumnsShowFocus(
true);
1114 list_view->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
1115 list_view->header()->setSectionResizeMode(1, QHeaderView::Stretch);
1116 list_view->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
1117 list_view->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
1118 list_view->header()->setSectionResizeMode(4, QHeaderView::ResizeToContents);
1123 list_view->setToolTip(tr(
"Right click while running to change the active shader."));
1125 bottomTextBox->setHtml(
"<b style='color:red;'>ACMX</b> - Interface: Loaded.");
1128 connect(
list_view, &QTreeWidget::customContextMenuRequested,
this, [
this](
const QPoint &pos) {
1131 if (QTreeWidgetItem *item =
list_view->itemAt(pos)) {
1142 QWidget *centralWidget =
new QWidget(
this);
1143 QVBoxLayout *layout =
new QVBoxLayout(centralWidget);
1146 centralWidget->setLayout(layout);
1147 setCentralWidget(centralWidget);
1148 QSettings appSettings(
"LostSideDead");
1156 path = path.trimmed();
1157 while (path.endsWith(
"/") || path.endsWith(
"\\")) {
1162 prefix_path = appSettings.value(
"prefix_path",
".").toString();
1165 bool useCustomStyle = appSettings.value(
"useCustomStyle",
false).toBool();
1167 midi_enabled = appSettings.value(
"midiEnabled",
false).toBool();
1169 midi_device = appSettings.value(
"midiDevice", -1).toInt();
1171 watermark_text = appSettings.value(
"watermarkText",
"").toString();
1172 watermark_r = appSettings.value(
"watermarkR", 255).toInt();
1173 watermark_g = appSettings.value(
"watermarkG", 0).toInt();
1174 watermark_b = appSettings.value(
"watermarkB", 150).toInt();
1176 autopilot_frames = appSettings.value(
"playlistAutopilotFrames", 4).toInt();
1180 autopilot_random = appSettings.value(
"playlistAutopilotRandom",
false).toBool();
1186 if (!path.isEmpty()) {
1187 QFileInfo pathInfo(path);
1189 QString backendError;
1191 if (!backendError.isEmpty()) {
1192 Log(
"Warning: Saved shader library backend metadata is invalid: " + backendError);
1193 }
else if (libraryBackend && *libraryBackend !=
active_backend) {
1194 Log(tr(
"Warning: Saved shader library targets %1 while the "
1195 "active backend is %2: %3")
1201 Log(
"Successfully loaded saved shader path");
1204 QString errorMsg =
"Warning: Saved shader path is invalid: " + path +
" - ";
1205 if (!pathInfo.exists()) {
1206 errorMsg +=
"directory does not exist";
1207 }
else if (!pathInfo.isDir()) {
1208 errorMsg +=
"path is not a directory";
1210 errorMsg +=
"library.json or index.txt not found in directory";
1217 customStyleSheet = appSettings.value(
"customStyleSheet", defaultCustomStyleSheet).toString();
1223 QSettings settings(
"LostSideDead",
"acmx2");
1225 const QString inputMode = settings.value(
"interface/input_mode",
"camera").toString();
1226 const bool videoMode = inputMode ==
"video";
1227 const bool graphicsMode = inputMode ==
"graphic";
1228 const bool cameraMode = !videoMode && !graphicsMode;
1230 camera_index =
static_cast<unsigned int>(std::max(0, settings.value(
"interface/camera_device", 0).toInt()));
1231 camera_res = storedResolution(settings,
"interface/camera_resolution", QSize(1280, 720),
false);
1232 screen_res = storedResolution(settings,
"interface/screen_resolution", QSize(0, 0),
true);
1234 output_fps = settings.value(
"interface/camera_fps", 30.0).toDouble();
1238 video_file = videoMode ? settings.value(
"interface/input_video",
"").toString() : QString();
1239 graphics_file = graphicsMode ? settings.value(
"interface/graphics_file",
"").toString() : QString();
1241 const bool saveOutput = settings.value(
"interface/save_output",
false).toBool();
1242 output_file = saveOutput ? settings.value(
"interface/output_video",
"").toString() : QString();
1243 save_output_log = saveOutput && settings.value(
"interface/save_output_log",
false).toBool();
1245 copy_audio = videoMode && saveOutput && settings.value(
"interface/copy_audio",
false).toBool();
1247 cache_enabled = !graphicsMode && settings.value(
"interface/texture_cache",
false).toBool();
1248 cache_delay = settings.value(
"interface/cache_delay", 1).toInt();
1249 cache_size = std::clamp(settings.value(
"interface/cache_size", 8).toInt(), 1, 64);
1250 use_yuv = cameraMode && settings.value(
"interface/use_yuv",
false).toBool();
1252 convert_to_hdr10 = videoMode && saveOutput && settings.value(
"interface/convert_to_hdr10",
false).toBool();
1253 enable_3d = settings.value(
"interface/enable_3d",
false).toBool();
1254 model_file = settings.value(
"interface/model_file",
"cube.mxmod.z").toString();
1256 onnx_model = settings.value(
"interface/onnx_model_file",
"").toString();
1258 deep_dream_model = settings.value(
"deep_dream/model_file", QString()).toString();
1259 deep_dream_layer = settings.value(
"deep_dream/layer",
"relu4_2").toString();
1261 deep_dream_strength = std::clamp(settings.value(
"deep_dream/strength", 0.05).toDouble(), 0.0001, 10.0);
1262 deep_dream_feedback = std::clamp(settings.value(
"deep_dream/feedback", 0.9).toDouble(), 0.0, 0.99);
1263 deep_dream_zoom = std::clamp(settings.value(
"deep_dream/zoom", 1.01).toDouble(), 0.9, 1.1);
1264 deep_dream_rotation = std::clamp(settings.value(
"deep_dream/rotation", 0.1).toDouble(), -5.0, 5.0);
1270 deep_dream_channel = std::clamp(settings.value(
"deep_dream/channel", -1).toInt(), -1, 65535);
1271 deep_dream_octaves = std::clamp(settings.value(
"deep_dream/octaves", 1).toInt(), 1, 8);
1273 deep_dream_jitter = std::clamp(settings.value(
"deep_dream/jitter", 0).toInt(), 0, 64);
1285 const QStringList stable_diffusion_lora_multiplier_values = settings.value(
"stable_diffusion/lora_multipliers").toStringList();
1287 bool multiplier_ok =
false;
1288 const double multiplier = index < stable_diffusion_lora_multiplier_values.size() ? stable_diffusion_lora_multiplier_values.at(index).toDouble(&multiplier_ok) : 1.0;
1297 stable_diffusion_server = settings.value(
"stable_diffusion/server", default_stable_diffusion_server()).toString();
1311 cuda_device = settings.value(
"interface/cuda_device", 0).toInt();
1312 time_speed = settings.value(
"interface/time_speed", 1.0).toFloat();
1313 normalized_time = settings.value(
"interface/normalized_time",
false).toBool();
1319 max_duration = settings.value(
"interface/duration_seconds", 60.0).toDouble();
1321 max_size_mb = settings.value(
"interface/max_size_mb", 500.0).toDouble();
1323 flip_enabled = settings.value(
"interface/flip",
false).toBool();
1324 rotate_enabled = settings.value(
"interface/rotate",
false).toBool();
1325 rotation_mode = settings.value(
"interface/rotation_mode",
"clockwise").toString();
1328 png_level = std::clamp(settings.value(
"interface/png_level", 6).toInt(), 1, 9);
1329 generate_enabled = settings.value(
"interface/generate_enabled",
false).toBool();
1332 encode_preset = settings.value(
"recording/preset",
"medium").toString();
1333 encode_tune = settings.value(
"recording/tune",
"").toString();
1334 encode_crf = settings.value(
"recording/crf", 18).toInt();
1336 encode_bitrate = settings.value(
"recording/bitrate",
"10M").toString();
1337 encode_codec = settings.value(
"recording/codec",
"auto").toString();
1339 encode_realtime = settings.value(
"recording/realtime",
false).toBool();
1340 encode_no_drop = !cameraMode && settings.value(
"recording/no_drop",
false).toBool();
1343 maximize_fps = settings.value(
"interface/acmxvk_maximize_fps",
false).toBool();
1344 use_source_fps = settings.value(
"interface/acmxvk_use_source_fps",
false).toBool();
1346 extra_arguments = settings.value(
"interface/extra_arguments", QString()).toString();
1351 QFont listFont(
"Courier New");
1352 listFont.setStyleHint(QFont::Monospace);
1353 listFont.setPointSize(12);
1356 if (customStyleEnabled) {
1359 list_view->setStyleSheet(
"QTreeWidget { background-color: black; color: white; font-size: 13px;"
1360 " font-family: 'Courier New', Courier, monospace; }"
1361 "QHeaderView::section { background-color: #110000; color: lime;"
1362 " font-family: 'Courier New', Courier, monospace; padding: 4px;"
1363 " border: 1px solid #330000; }");
1368 QFont logFont(
"Courier New");
1369 logFont.setStyleHint(QFont::Monospace);
1370 logFont.setPointSize(11);
1373 if (customStyleEnabled) {
1376 bottomTextBox->setStyleSheet(
"QTextEdit { background-color: black; color: lime; font-size: 13px;"
1377 " font-family: 'Courier New', Courier, monospace; }");
1383 QSettings appSettings(
"LostSideDead");
1384 appSettings.setValue(
"useCustomStyle", enable);
1402 QSettings appSettings(
"LostSideDead");
1403 const bool currentlyEnabled = appSettings.value(
"useCustomStyle",
false).toBool();
1404 const QString lastPresetName = appSettings.value(
"customStylePreset",
"Current Style").toString();
1406 auto makePalette = [](
const char *winBg,
const char *winFg,
const char *accent,
const char *fieldBg,
const char *fieldFg,
const char *fieldBorder,
const char *btnBg,
const char *btnHover,
const char *btnFg,
const char *menuBg,
const char *menuFg,
const char *menuSelBg,
const char *menuSelFg,
const char *selBg,
const char *border) {
1426 const std::array<QPair<QString, QString>, 26> presetStyles = {{{
"Current Style",
customStyleSheet},
1427 {
"Light: Blue & White", makePalette(
"#f6fbff",
"#143a5c",
"#2d7cc4",
"#ffffff",
"#123b61",
"#9cc6ea",
"#2d7cc4",
"#2368a6",
"#ffffff",
"#eaf5ff",
"#143a5c",
"#cfe6ff",
"#0b2e4d",
"#bcdcff",
"1px solid #9cc6ea")},
1428 {
"Light: Slate", makePalette(
"#f5f7fa",
"#1f2a37",
"#4b5563",
"#ffffff",
"#1f2937",
"#b6c3d4",
"#4b5563",
"#374151",
"#ffffff",
"#e8edf4",
"#1f2a37",
"#d2dbe7",
"#111827",
"#cdd5e0",
"1px solid #b6c3d4")},
1429 {
"Light: White & Red", makePalette(
"#fffdfd",
"#5b1515",
"#d63b3b",
"#ffffff",
"#5a1a1a",
"#e8bcbc",
"#d63b3b",
"#bc2f2f",
"#ffffff",
"#fff4f4",
"#5b1515",
"#ffdede",
"#4b0f0f",
"#ffd1d1",
"1px solid #e8bcbc")},
1430 {
"Light: White & Green", makePalette(
"#fcfffc",
"#164529",
"#2e9d57",
"#ffffff",
"#1a4f2f",
"#b8dfc7",
"#2e9d57",
"#25824a",
"#ffffff",
"#f1fbf4",
"#164529",
"#d6f3df",
"#11361f",
"#c9eecf",
"1px solid #b8dfc7")},
1431 {
"Light: White & Blue", makePalette(
"#fcfdff",
"#16395f",
"#2f6ed7",
"#ffffff",
"#1b446f",
"#b7d0f0",
"#2f6ed7",
"#285db7",
"#ffffff",
"#f1f6ff",
"#16395f",
"#d9e8ff",
"#102b49",
"#cddfff",
"1px solid #b7d0f0")},
1432 {
"Light: White & Cyan", makePalette(
"#fbfeff",
"#12404a",
"#1ea9bf",
"#ffffff",
"#14505d",
"#b8e2ea",
"#1ea9bf",
"#198da0",
"#ffffff",
"#effbfe",
"#12404a",
"#d5f3f8",
"#0e3138",
"#c7edf4",
"1px solid #b8e2ea")},
1433 {
"Light: White & Amber", makePalette(
"#fffefb",
"#5a3a12",
"#d18b1f",
"#ffffff",
"#644317",
"#ead7b6",
"#d18b1f",
"#b37518",
"#ffffff",
"#fff9ed",
"#5a3a12",
"#ffebcb",
"#4a2f0f",
"#ffe2b5",
"1px solid #ead7b6")},
1434 {
"Dark: Crimson", makePalette(
"#0f0608",
"#ff637d",
"#a02949",
"#1b0b10",
"#ff8fa3",
"#7f2036",
"#6f1630",
"#8a1f3d",
"#ffdfe6",
"#16090d",
"#ff637d",
"#52111f",
"#ffd5dc",
"#52111f",
"2px solid #a02949")},
1435 {
"Dark: Emerald", makePalette(
"#06110c",
"#7af7c2",
"#2c8e68",
"#0d1e16",
"#95ffd0",
"#2c8e68",
"#1c6a4d",
"#258961",
"#dcfff2",
"#08160f",
"#7af7c2",
"#12402d",
"#d9fff0",
"#12402d",
"2px solid #2c8e68")},
1436 {
"Dark: Indigo", makePalette(
"#070713",
"#c6c8ff",
"#5362ba",
"#121634",
"#d8daff",
"#4956a5",
"#36439a",
"#4453b4",
"#eef0ff",
"#0d1022",
"#c6c8ff",
"#232a5a",
"#eef0ff",
"#232a5a",
"2px solid #5362ba")},
1437 {
"Dark: Black & Red", makePalette(
"#050505",
"#ff4d4d",
"#d90000",
"#120808",
"#ff7b7b",
"#b50000",
"#2a0c0c",
"#3a1010",
"#ffd6d6",
"#0b0707",
"#ff5a5a",
"#6b1111",
"#ffe9e9",
"#5a0c0c",
"2px solid #d90000")},
1438 {
"Dark: Black & Green", makePalette(
"#040704",
"#6dfb88",
"#22b44a",
"#0a140b",
"#a8ffbe",
"#1d9a3e",
"#12331b",
"#164425",
"#e1ffe8",
"#08100a",
"#74ff95",
"#12331b",
"#e7ffed",
"#10381d",
"2px solid #22b44a")},
1439 {
"Dark: Black & Blue", makePalette(
"#04060a",
"#81b9ff",
"#2f6ed7",
"#0a1222",
"#b4d4ff",
"#2a5eb7",
"#132749",
"#1a3260",
"#e7f1ff",
"#070d1a",
"#8cc0ff",
"#1a3260",
"#eef5ff",
"#17335f",
"2px solid #2f6ed7")},
1440 {
"Dark: Black & Cyan", makePalette(
"#030809",
"#7defff",
"#1ba8c3",
"#09161a",
"#b8f7ff",
"#1990a7",
"#10323a",
"#14414b",
"#e7fbff",
"#071015",
"#89f3ff",
"#0f3943",
"#e8fcff",
"#0f3943",
"2px solid #1ba8c3")},
1441 {
"Dark: Black & Amber", makePalette(
"#090704",
"#ffd77a",
"#d88c1d",
"#1a1308",
"#ffe7b4",
"#bf7a19",
"#3d2810",
"#523618",
"#fff3db",
"#130e07",
"#ffdf8a",
"#5a3a16",
"#fff4df",
"#5a3a16",
"2px solid #d88c1d")},
1442 {
"Light: Lavender Mist", makePalette(
"#f8f6ff",
"#302653",
"#7157c8",
"#ffffff",
"#34295b",
"#c9bdea",
"#7157c8",
"#5d45ae",
"#ffffff",
"#eee9ff",
"#302653",
"#ded5ff",
"#241a48",
"#d9d0ff",
"1px solid #c9bdea")},
1443 {
"Light: Rose Quartz", makePalette(
"#fff8fa",
"#532535",
"#c25578",
"#ffffff",
"#5b293c",
"#e8c1cf",
"#c25578",
"#a94465",
"#ffffff",
"#fff0f4",
"#532535",
"#f6d7e1",
"#411a28",
"#f1ccd8",
"1px solid #e8c1cf")},
1444 {
"Light: Sandstone", makePalette(
"#fbf7ef",
"#493728",
"#a66a3f",
"#fffdf8",
"#4f3929",
"#d9c3aa",
"#a66a3f",
"#895431",
"#ffffff",
"#f3eadc",
"#493728",
"#ead8c1",
"#35251a",
"#e5d1b7",
"1px solid #d9c3aa")},
1445 {
"Light: Mint & Navy", makePalette(
"#f3fbf8",
"#173a3c",
"#2b8c7f",
"#ffffff",
"#173a3c",
"#addbd2",
"#1d5962",
"#287681",
"#ffffff",
"#e5f6f1",
"#173a3c",
"#c8eee5",
"#102f34",
"#bde5dc",
"1px solid #addbd2")},
1446 {
"Light: High Contrast", makePalette(
"#ffffff",
"#111111",
"#005fcc",
"#ffffff",
"#000000",
"#4d4d4d",
"#111111",
"#005fcc",
"#ffffff",
"#f0f0f0",
"#000000",
"#005fcc",
"#ffffff",
"#9dccff",
"2px solid #111111")},
1447 {
"Dark: Cyberpunk Neon", makePalette(
"#070513",
"#f3e7ff",
"#ff2bd6",
"#100c24",
"#5ffbf1",
"#6e4cff",
"#2a145c",
"#ff2bd6",
"#ffffff",
"#0c081c",
"#5ffbf1",
"#381b72",
"#ffffff",
"#381b72",
"2px solid #ff2bd6")},
1448 {
"Dark: Dracula", makePalette(
"#282a36",
"#f8f8f2",
"#bd93f9",
"#21222c",
"#f8f8f2",
"#6272a4",
"#44475a",
"#6272a4",
"#f8f8f2",
"#21222c",
"#f8f8f2",
"#44475a",
"#f8f8f2",
"#44475a",
"1px solid #6272a4")},
1449 {
"Dark: Nord Frost", makePalette(
"#2e3440",
"#eceff4",
"#88c0d0",
"#3b4252",
"#eceff4",
"#4c566a",
"#4c566a",
"#5e81ac",
"#eceff4",
"#242933",
"#d8dee9",
"#434c5e",
"#eceff4",
"#434c5e",
"1px solid #88c0d0")},
1450 {
"Dark: Solarized", makePalette(
"#002b36",
"#93a1a1",
"#b58900",
"#073642",
"#eee8d5",
"#586e75",
"#07576b",
"#268bd2",
"#fdf6e3",
"#00242d",
"#93a1a1",
"#07576b",
"#fdf6e3",
"#07576b",
"1px solid #586e75")},
1451 {
"Dark: Graphite Orange", makePalette(
"#171717",
"#f2f2f2",
"#ff8a3d",
"#242424",
"#f7f7f7",
"#5f5f5f",
"#3a3a3a",
"#ff8a3d",
"#ffffff",
"#202020",
"#f2f2f2",
"#59311c",
"#ffffff",
"#59311c",
"2px solid #ff8a3d")}}};
1458 QDialog dialog(
this);
1459 dialog.setWindowTitle(tr(
"Custom Style Editor"));
1460 dialog.resize(900, 640);
1462 dialog.setStyleSheet(
"");
1464 auto *layout =
new QVBoxLayout(&dialog);
1465 auto *topRow =
new QHBoxLayout();
1466 auto *enableCheck =
new QCheckBox(tr(
"Use custom style"), &dialog);
1467 enableCheck->setChecked(currentlyEnabled);
1468 auto *presetLabel =
new QLabel(tr(
"Preset:"), &dialog);
1469 auto *presetCombo =
new QComboBox(&dialog);
1470 for (
const auto &preset : presetStyles) {
1471 presetCombo->addItem(preset.first);
1473 int presetIndex = 0;
1474 for (
int i = 0; i < static_cast<int>(presetStyles.size()); ++i) {
1475 if (presetStyles[
static_cast<std::size_t
>(i)].first == lastPresetName) {
1480 presetCombo->setCurrentIndex(presetIndex);
1482 auto *editor =
new QPlainTextEdit(&dialog);
1484 editor->setLineWrapMode(QPlainTextEdit::NoWrap);
1485 editor->setPlaceholderText(tr(
"Enter a Qt stylesheet (QSS) for ACMX2 interface..."));
1487 QFont qssFont(
"Courier New");
1488 qssFont.setStyleHint(QFont::Monospace);
1489 qssFont.setPointSize(10);
1490 editor->setFont(qssFont);
1493 auto *buttonBox =
new QDialogButtonBox(&dialog);
1494 auto *applyButton = buttonBox->addButton(tr(
"Apply"), QDialogButtonBox::ApplyRole);
1495 auto *saveButton = buttonBox->addButton(tr(
"Save"), QDialogButtonBox::ActionRole);
1496 auto *closeButton = buttonBox->addButton(QDialogButtonBox::Close);
1498 topRow->addWidget(enableCheck);
1499 topRow->addSpacing(12);
1500 topRow->addWidget(presetLabel);
1501 topRow->addWidget(presetCombo, 1);
1502 layout->addLayout(topRow);
1503 layout->addWidget(editor, 1);
1504 layout->addWidget(buttonBox);
1506 connect(presetCombo, &QComboBox::currentTextChanged, &dialog, [editor, &presetStyles, &appSettings](
const QString &name) {
1507 for (
const auto &preset : presetStyles) {
1508 if (preset.first == name) {
1509 editor->setPlainText(preset.second);
1510 appSettings.setValue(
"customStylePreset", name);
1516 auto applyEditorStyle = [
this, &dialog, enableCheck, editor, presetCombo]() {
1518 QSettings styleSettings(
"LostSideDead");
1520 styleSettings.setValue(
"customStylePreset", presetCombo->currentText());
1521 styleSettings.setValue(
"useCustomStyle", enableCheck->isChecked());
1524 dialog.setStyleSheet(
"");
1531 connect(applyButton, &QPushButton::clicked, &dialog, applyEditorStyle);
1532 connect(saveButton, &QPushButton::clicked, &dialog, applyEditorStyle);
1533 connect(closeButton, &QPushButton::clicked, &dialog, &QDialog::accept);
1541 if (library.exec() == QDialog::Accepted) {
1571 QString searchText = QInputDialog::getText(
this, tr(
"Search Shaders"), tr(
"Enter shader name to search:"), QLineEdit::Normal,
lastSearchText, &ok);
1573 if (!ok || searchText.isEmpty()) {
1579 if (
items.isEmpty()) {
1580 QMessageBox::information(
this, tr(
"Search Shaders"), tr(
"No shaders are loaded."));
1583 int foundIndex = -1;
1585 for (
int i = 0; i <
items.size(); ++i) {
1586 if (
items[i].compare(searchText, Qt::CaseInsensitive) == 0) {
1592 if (foundIndex == -1) {
1593 for (
int i = 0; i <
items.size(); ++i) {
1594 if (
items[i].contains(searchText, Qt::CaseInsensitive)) {
1601 if (foundIndex != -1) {
1604 Log(
"Found shader: " +
items[foundIndex] +
" at index " + QString::number(foundIndex));
1606 QMessageBox::information(
this, tr(
"Not Found"), tr(
"Shader \"") + searchText + tr(
"\" not found in the list."));
1607 Log(
"Shader not found: " + searchText);
1613 QMessageBox::information(
this, tr(
"No Search"), tr(
"Please perform a search first (Ctrl+F)."));
1617 if (
items.isEmpty()) {
1621 int foundIndex = -1;
1624 for (
int i = startIndex; i <
items.size(); ++i) {
1631 if (foundIndex == -1 && startIndex > 0) {
1632 for (
int i = 0; i < startIndex; ++i) {
1640 if (foundIndex != -1) {
1643 Log(
"Found next: " +
items[foundIndex] +
" at index " + QString::number(foundIndex));
1645 QMessageBox::information(
this, tr(
"No More Results"), tr(
"No more matches for \"") +
lastSearchText + tr(
"\"."));
1652 QMessageBox::information(
this, tr(
"New Shader File"), tr(
"Create or load a shader library first."));
1658 if (!typeError.isEmpty()) {
1659 QMessageBox::warning(
this, tr(
"New Shader File"), typeError);
1663 QMessageBox::information(
this,
1664 tr(
"New Shader File"),
1665 tr(
"New ACMXVK shaders must be added to a source library, not "
1666 "a compiled SPIR-V runtime library."));
1672 if (new_shader.exec() == QDialog::Accepted) {
1673 QSettings appSettings(
"LostSideDead");
1684 if (row < 0 || row >=
items.size())
1686 const QString shaderName =
items.at(row);
1687 QString manifestError;
1689 QMessageBox::warning(
this, tr(
"Could Not Remove Shader"), manifestError);
1690 Log(tr(
"Could not remove %1 from the library manifest: %2").arg(shaderName, manifestError));
1693 items.removeAt(row);
1697 Log(tr(
"Removed shader from library manifest: %1").arg(shaderName));
1705 if (row < 0 || row >=
items.size()) {
1706 Log(
"No shader selected.");
1713 QStringList writtenItems;
1714 const int rowCount =
items.size();
1716 for (
int row = 0; row < rowCount; ++row) {
1717 const QString shaderName =
items.at(row).trimmed();
1718 if (shaderName.isEmpty() || writtenItems.contains(shaderName, Qt::CaseInsensitive)) {
1722 QString fullPath =
shader_path +
"/" + shaderName;
1723 QFileInfo fileInfo(fullPath);
1724 if (fileInfo.exists() && fileInfo.isFile()) {
1725 writtenItems.append(shaderName);
1727 Log(
"Warning: File no longer exists, removing from list: " + shaderName);
1730 QString manifestError;
1731 QStringList existingItems;
1737 manifestError.clear();
1739 Log(
"Failed to update shader manifest: " + manifestError);
1745 if (writtenItems.size() != rowCount) {
1746 items = writtenItems;
1748 Log(
"Updated shader list, removed " + QString::number(rowCount - writtenItems.size()) +
" non-existent files");
1754 if (row <= 0 || row >=
items.size())
1756 items.swapItemsAt(row, row - 1);
1764 if (row < 0 || row >=
items.size() - 1)
1766 items.swapItemsAt(row, row + 1);
1773 QFile file(filePath);
1774 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
1775 Log(
"Failed to open file: " + filePath);
1779 QTextStream in(&file);
1780 QString contents = in.readAll();
1788 const int row = i.row();
1789 if (row < 0 || row >=
items.size())
1792 if (itemText.isEmpty()) {
1793 Log(
"Invalid shader name");
1801 const QFileInfo requestedFile(filePath);
1802 if (!requestedFile.exists() || !requestedFile.isFile()) {
1803 QMessageBox::warning(
this, tr(
"Open Shader"), tr(
"Shader file no longer exists:\n%1").arg(filePath));
1808 const QString canonicalPath = requestedFile.canonicalFilePath();
1809 for (
const QPointer<TextEditor> &openEditor :
open_files) {
1812 const QString openPath = QFileInfo(openEditor->fileName()).canonicalFilePath();
1813 if (!canonicalPath.isEmpty() && openPath == canonicalPath) {
1819 openEditor->revealLocation(lineNumber, columnNumber, matchLength);
1826 editor->setWindowFlags(Qt::Widget);
1836 for (
const QPointer<TextEditor> &openEditor :
open_files) {
1838 openEditor->setUniformValue(name, value);
1843 connect(editor, &QWidget::windowTitleChanged,
this, [
this, editor](
const QString &title) {
1849 QString tabTitle = title;
1850 const int separator = tabTitle.indexOf(QStringLiteral(
" - "));
1852 tabTitle = tabTitle.mid(separator + 3);
1871 layout->setContentsMargins(4, 4, 4, 4);
1877 connect(
shaderEditorTabs, &QTabWidget::tabCloseRequested,
this, [
this](
int index) {
1878 auto *editor = qobject_cast<TextEditor *>(
shaderEditorTabs->widget(index));
1879 if (editor && editor->close())
1882 QSettings settings(
"LostSideDead");
1883 if (!
shaderEditorWorkspace->restoreGeometry(settings.value(
"editor/workspaceGeometry").toByteArray())) {
1888 QSettings(
"LostSideDead").setValue(
"editor/workspaceGeometry",
shaderEditorWorkspace->saveGeometry());
1894 const QFileInfo sourceInfo(sourcePath);
1895 const QString sourceCanonical = sourceInfo.canonicalFilePath();
1896 for (
const QPointer<TextEditor> &editor :
open_files) {
1899 const QFileInfo editorInfo(editor->fileName());
1900 const QString editorCanonical = editorInfo.canonicalFilePath();
1901 const bool sameFile = (!sourceCanonical.isEmpty() && !editorCanonical.isEmpty() && sourceCanonical == editorCanonical) || sourceInfo.absoluteFilePath() == editorInfo.absoluteFilePath();
1905 editor->setCompilePending();
1907 editor->setCompileResult(success, diagnostics);
1913 QList<acmx2::CustomUniformDefinition> definitions;
1916 definitions.clear();
1919 QVector<ShaderEditorUniform> uniforms;
1920 uniforms.reserve(definitions.size());
1922 uniforms.append({definition.name, definition.slot, definition.minimum, definition.maximum, definition.step, definition.value});
1924 const QString libraryRoot = QFileInfo(
shader_path).canonicalFilePath();
1925 for (
const QPointer<TextEditor> &editor :
open_files) {
1928 const QString editorPath = QFileInfo(editor->fileName()).canonicalFilePath();
1929 const QString relative = libraryRoot.isEmpty() || editorPath.isEmpty() ? QStringLiteral(
"..") : QDir(libraryRoot).relativeFilePath(editorPath);
1930 const bool inActiveLibrary = relative != QStringLiteral(
"..") && !relative.startsWith(QStringLiteral(
"../"));
1931 if (inActiveLibrary)
1932 editor->setShaderContext(
acmxvk, uniforms, libraryRoot);
1940 const int row =
list_view->indexOfTopLevelItem(it);
1941 if (row < 0 || row >=
items.size())
1943 return items.at(row);
1949 QTreeWidgetItem *it =
list_view->currentItem();
1952 return list_view->indexOfTopLevelItem(it);
1956#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
1957#if defined(__linux__) || defined(__APPLE__)
1961 if (shaderSelectionSemaphore != SEM_FAILED) {
1963 if (publishedSemaphore != SEM_FAILED) {
1964 ::sem_close(publishedSemaphore);
1966 ::sem_close(shaderSelectionSemaphore);
1967 shaderSelectionSemaphore = SEM_FAILED;
1970 if (shaderSelectionSemaphore == SEM_FAILED) {
1973 if (shaderSelectionSemaphore == SEM_FAILED) {
1978 if (shaderSelectionShm)
1982 if (shaderSelectionShmFd < 0) {
1983 const int openError = errno;
1984 Log(tr(
"Shared interface control unavailable: shm_open(%1) failed: "
1992 struct stat shmStat{};
1993 if (::fstat(shaderSelectionShmFd, &shmStat) != 0) {
1994 const int statError = errno;
2000 if (shmStat.st_size == 0) {
2001 if (::ftruncate(shaderSelectionShmFd,
static_cast<off_t
>(SHARED_MEMORY_SIZE)) != 0) {
2002 const int truncateError = errno;
2003 Log(tr(
"Shared interface control unavailable: ftruncate(%1, %2) "
2006 .arg(
static_cast<qulonglong
>(SHARED_MEMORY_SIZE))
2007 .arg(QString::fromLocal8Bit(std::strerror(truncateError))));
2011 }
else if (shmStat.st_size <
static_cast<off_t
>(SHARED_MEMORY_SIZE)) {
2012 Log(tr(
"Shared interface control unavailable: %1 has size %2 bytes; "
2013 "expected %3. Refusing to resize an active or stale shared "
2016 .arg(
static_cast<qlonglong
>(shmStat.st_size))
2017 .arg(
static_cast<qulonglong
>(SHARED_MEMORY_SIZE)));
2022 void *mapped = ::mmap(
nullptr, SHARED_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shaderSelectionShmFd, 0);
2023 if (mapped == MAP_FAILED) {
2024 const int mapError = errno;
2025 Log(tr(
"Shared interface control unavailable: mmap(%1, %2) failed: "
2028 .arg(
static_cast<qulonglong
>(SHARED_MEMORY_SIZE))
2029 .arg(QString::fromLocal8Bit(std::strerror(mapError))));
2036 if (shaderSelectionSemaphore ==
nullptr) {
2037 shaderSelectionSemaphore = ::CreateMutexW(
nullptr, FALSE, acmx2::ipc::kShaderSelectionMutexNameWindows);
2039 if (shaderSelectionSemaphore ==
nullptr) {
2040 Log(tr(
"Shared interface control unavailable: CreateMutexW failed "
2041 "with Windows error %1")
2042 .arg(
static_cast<qulonglong
>(::GetLastError())));
2046 if (shaderSelectionShm)
2050 shaderSelectionMapping = ::CreateFileMappingW(INVALID_HANDLE_VALUE,
nullptr, PAGE_READWRITE, 0,
static_cast<DWORD
>(SHARED_MEMORY_SIZE), acmx2::ipc::kShaderSelectionMappingNameWindows);
2051 if (shaderSelectionMapping ==
nullptr) {
2052 Log(tr(
"Shared interface control unavailable: CreateFileMappingW "
2053 "failed with Windows error %1")
2054 .arg(
static_cast<qulonglong
>(::GetLastError())));
2059 void *mapped = ::MapViewOfFile(shaderSelectionMapping, FILE_MAP_ALL_ACCESS, 0, 0, SHARED_MEMORY_SIZE);
2060 if (mapped ==
nullptr) {
2061 Log(tr(
"Shared interface control unavailable: MapViewOfFile failed "
2062 "with Windows error %1")
2063 .arg(
static_cast<qulonglong
>(::GetLastError())));
2070 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2072#if defined(__linux__) || defined(__APPLE__)
2075 Log(tr(
"Shared interface control unavailable: could not lock the "
2076 "Windows control mutex (error %1)")
2077 .arg(
static_cast<qulonglong
>(::GetLastError())));
2086 shaderSelectionShm->selected_index = -1;
2087 shaderSelectionShm->shader_pass_count = 0;
2088 shaderSelectionShm->shader_pass_enabled = 0;
2089 shaderSelectionShm->repeat_enabled = 0;
2090 shaderSelectionShm->display_filter_enabled = 0;
2091 shaderSelectionShm->watermark_enabled = 0;
2092 shaderSelectionShm->normalized_time_enabled = 0;
2093 std::fill(std::begin(shaderSelectionShm->reserved_flags), std::end(shaderSelectionShm->reserved_flags), 0);
2094 std::fill(std::begin(shaderSelectionShm->shader_pass_indices), std::end(shaderSelectionShm->shader_pass_indices), -1);
2096 shaderSelectionShm->gpu_filter_count = 0;
2097 shaderSelectionShm->gpu_filter_enabled = 0;
2098 shaderSelectionShm->gpu_buffer_size = 8;
2099 shaderSelectionShm->watermark_r = 255;
2100 shaderSelectionShm->watermark_g = 0;
2101 shaderSelectionShm->watermark_b = 150;
2102 std::fill(std::begin(shaderSelectionShm->reserved), std::end(shaderSelectionShm->reserved), 0);
2103 std::fill(std::begin(shaderSelectionShm->gpu_filter_indices), std::end(shaderSelectionShm->gpu_filter_indices), -1);
2104 std::fill(std::begin(shaderSelectionShm->watermark_text), std::end(shaderSelectionShm->watermark_text),
'\0');
2105 shaderSelectionShm->reload_shader_index = -1;
2106 std::fill(std::begin(shaderSelectionShm->reload_shader_path), std::end(shaderSelectionShm->reload_shader_path),
'\0');
2107 shaderSelectionShm->reload_sequence = 0;
2108 shaderSelectionShm->custom_uniform_count = 0;
2110 std::fill(std::begin(shaderSelectionShm->custom_uniform_values), std::end(shaderSelectionShm->custom_uniform_values), 0.0f);
2111 std::fill(std::begin(shaderSelectionShm->audio_file_path), std::end(shaderSelectionShm->audio_file_path),
'\0');
2112 shaderSelectionShm->audio_output_device = -1;
2113 shaderSelectionShm->audio_pass_through = 0;
2114 shaderSelectionShm->audio_trunc = 0;
2115 shaderSelectionShm->audio_repeat = 0;
2116 shaderSelectionShm->audio_reserved = 0;
2117 shaderSelectionShm->audio_file_sequence = 0;
2118 shaderSelectionShm->dream_enabled = 0;
2119 shaderSelectionShm->dream_fp16 = 0;
2120 shaderSelectionShm->dream_gpu_filter_first = 0;
2121 shaderSelectionShm->dream_reserved = 0;
2122 shaderSelectionShm->dream_iterations = 1;
2123 shaderSelectionShm->dream_maximum_dimension = 512;
2124 shaderSelectionShm->dream_channel = -1;
2125 shaderSelectionShm->dream_octaves = 1;
2126 shaderSelectionShm->dream_jitter = 0;
2127 shaderSelectionShm->dream_smoothing = 0;
2128 shaderSelectionShm->dream_strength = 0.05F;
2129 shaderSelectionShm->dream_feedback = 0.9F;
2130 shaderSelectionShm->dream_zoom = 1.01F;
2131 shaderSelectionShm->dream_rotation = 0.1F;
2132 shaderSelectionShm->dream_octave_scale = 1.4F;
2133 std::fill(std::begin(shaderSelectionShm->dream_model_path), std::end(shaderSelectionShm->dream_model_path),
'\0');
2134 std::fill(std::begin(shaderSelectionShm->dream_layer), std::end(shaderSelectionShm->dream_layer),
'\0');
2135 std::fill(std::begin(shaderSelectionShm->selected_shader_name), std::end(shaderSelectionShm->selected_shader_name),
'\0');
2136 shaderSelectionShm->sequence = 0;
2142#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2143 if (!shaderSelectionShm)
2146 if (row < 0 || row >=
items.size())
2148 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2150 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
2153 shaderSelectionShm->selected_index = row;
2154 const QByteArray shaderName =
items.at(row).toUtf8();
2156 std::fill(std::begin(shaderSelectionShm->selected_shader_name), std::end(shaderSelectionShm->selected_shader_name),
'\0');
2157 std::copy_n(shaderName.constData(), copyLength, shaderSelectionShm->selected_shader_name);
2158 ++shaderSelectionShm->sequence;
2163#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2168 const QFileInfo savedFile(filePath);
2169 const QString shaderName = QDir(
shader_path).relativeFilePath(savedFile.absoluteFilePath());
2170 const int shaderIndex =
items.indexOf(shaderName, 0, Qt::CaseInsensitive);
2171 if (shaderIndex < 0) {
2172 Log(
"Saved shader is not in the active library; live reload was skipped: " + filePath);
2176 const QByteArray reloadPath = savedFile.canonicalFilePath().toUtf8();
2178 Log(
"Shader path is too long for live reload: " + filePath);
2182 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2184 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
2187 shaderSelectionShm->reload_shader_index = shaderIndex;
2188 std::fill(std::begin(shaderSelectionShm->reload_shader_path), std::end(shaderSelectionShm->reload_shader_path),
'\0');
2189 std::copy(reloadPath.cbegin(), reloadPath.cend(), shaderSelectionShm->reload_shader_path);
2190 ++shaderSelectionShm->reload_sequence;
2191 ++shaderSelectionShm->sequence;
2192 Log(
"Requested live shader reload: " + shaderName +
"<br>");
2208#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2210 if (!is_acmxvk_source_library(
shader_path, typeError)) {
2211 const QString diagnostic = typeError.isEmpty() ? tr(
"ACMXVK live compile requires a source library.") : typeError;
2217 const QFileInfo sourceInfo(filePath);
2218 const QString sourcePath = sourceInfo.canonicalFilePath();
2219 const QString sourceRoot = QFileInfo(
shader_path).canonicalFilePath();
2220 if (sourcePath.isEmpty() || sourceRoot.isEmpty()) {
2221 const QString diagnostic = tr(
"Could not resolve saved ACMXVK shader: %1").arg(filePath);
2226 const QString sourceName =
sanitizeShaderName(QDir(sourceRoot).relativeFilePath(sourcePath));
2227 if (sourceName.isEmpty() || (!sourceName.endsWith(QStringLiteral(
".frag"), Qt::CaseInsensitive) && !sourceName.endsWith(QStringLiteral(
".comp"), Qt::CaseInsensitive)) || !
items.contains(sourceName, Qt::CaseInsensitive)) {
2228 const QString diagnostic = tr(
"Saved file is not a fragment or compute source in the active "
2229 "ACMXVK library: %1")
2246#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2262 Write(output.toHtmlEscaped().replace(
'\n', QStringLiteral(
"<br>")));
2269 Write(QStringLiteral(
"<b style='color:red;'>") + output.toHtmlEscaped().replace(
'\n', QStringLiteral(
"<br>")) + QStringLiteral(
"</b>"));
2271 connect(
liveShaderCompileProcess,
static_cast<void (QProcess::*)(
int, QProcess::ExitStatus)
>(&QProcess::finished),
this, [
this](
int exitCode, QProcess::ExitStatus exitStatus) {
2274 bool installed =
false;
2275 QString editorDiagnostics;
2278 if (!compilerOutput.isEmpty())
2279 compilerOutput += QLatin1Char(
'\n');
2282 if (exitStatus == QProcess::NormalExit && exitCode == 0) {
2285 if (compiled.open(QIODevice::ReadOnly)) {
2286 QDataStream stream(&compiled);
2287 stream.setByteOrder(QDataStream::LittleEndian);
2291 constexpr quint32 SPIRV_MAGIC = 0x07230203U;
2292 if (magic != SPIRV_MAGIC) {
2293 editorDiagnostics = tr(
"Compiler did not produce valid SPIR-V.");
2294 Log(tr(
"<b style='color:red;'>Live ACMXVK compile did "
2295 "not produce valid SPIR-V for %1.</b>")
2298 editorDiagnostics = tr(
"The compiled output is a symbolic link and cannot "
2299 "be replaced safely.");
2300 Log(tr(
"<b style='color:red;'>Refusing to replace "
2301 "symbolic-link shader output: %1</b>")
2304 std::error_code error;
2307 editorDiagnostics = tr(
"Could not install compiled shader: %1").arg(QString::fromStdString(error.message()));
2308 Log(tr(
"<b style='color:red;'>Could not install "
2309 "live ACMXVK shader: %1</b>")
2310 .arg(QString::fromStdString(error.message())));
2316 Log(tr(
"<b style='color:red;'>Live ACMXVK compile failed "
2317 "for %1 (%2, exit code %3).</b>")
2319 .arg(exitStatus == QProcess::CrashExit ? tr(
"compiler crashed") : tr(
"compiler error"))
2321 if (compilerOutput.isEmpty())
2323 editorDiagnostics = compilerOutput;
2324 Log(tr(
"<b style='color:red;'>Compiler message:</b>"
2325 "<pre style='white-space:pre-wrap;'>%1</pre>")
2326 .arg(compilerOutput.toHtmlEscaped()));
2350 QString compilerError;
2351 const QString glslc = resolve_acmxvk_shader_compiler(compilerError);
2352 if (glslc.isEmpty()) {
2353 Log(tr(
"<b style='color:red;'>Cannot live compile ACMXVK shader: "
2355 .arg(compilerError.toHtmlEscaped()));
2365 const QString sourceRoot = QFileInfo(
shader_path).canonicalFilePath();
2367 liveShaderCompileOutput = QDir(acmxvk_build_directory(sourceRoot)).filePath(acmxvk_runtime_shader_name(sourceName));
2369 const QString diagnostic = tr(
"Could not create live shader output directory for %1.").arg(
liveShaderCompileOutput);
2370 Log(QStringLiteral(
"<b style='color:red;'>%1</b>").arg(diagnostic.toHtmlEscaped()));
2382 if (!sourceFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
2383 const QString diagnostic = tr(
"Could not read shader source for live safety processing: %1").arg(sourceFile.errorString());
2384 Log(QStringLiteral(
"<b style='color:red;'>%1</b>").arg(diagnostic.toHtmlEscaped()));
2392 const QString source = QString::fromUtf8(sourceFile.readAll());
2393 const QSettings editorSettings(
"LostSideDead");
2394 const QString guardedSource = editorSettings.value(
"editor/guardLoops",
true).toBool() ?
inject_safety_counters(source) : source;
2397 const QByteArray guardedBytes = guardedSource.toUtf8();
2398 if (!guardedFile.open(QIODevice::WriteOnly | QIODevice::Text) || guardedFile.write(guardedBytes) != guardedBytes.size() || !guardedFile.commit()) {
2399 const QString diagnostic = tr(
"Could not create temporary shader source for live safety processing.");
2400 Log(QStringLiteral(
"<b style='color:red;'>%1</b>").arg(diagnostic.toHtmlEscaped()));
2410 Log(tr(
"Live compiling ACMXVK shader: %1").arg(sourceName));
2411 Log(tr(
"Command: %1 %2<br>").arg(glslc,
concatList(arguments)));
2416 const QString diagnostic = tr(
"Failed to start the ACMXVK shader compiler: %1").arg(
liveShaderCompileProcess->errorString());
2417 Log(QStringLiteral(
"<b style='color:red;'>%1</b>").arg(diagnostic.toHtmlEscaped()));
2445#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2446 if (!shaderSelectionShm || !
process ||
process->state() != QProcess::Running) {
2447 const QString error = tr(
"Start ACMX2 before using shader live preview.");
2453 const QFileInfo sourceInfo(filePath);
2454 const QString sourcePath = sourceInfo.canonicalFilePath();
2455 const QString sourceRoot = QFileInfo(
shader_path).canonicalFilePath();
2456 if (sourcePath.isEmpty() || sourceRoot.isEmpty()) {
2457 const QString error = tr(
"Could not resolve the ACMX2 shader preview path: %1").arg(filePath);
2463 const QString shaderName =
sanitizeShaderName(QDir(sourceRoot).relativeFilePath(sourcePath));
2464 const int shaderIndex =
items.indexOf(shaderName, 0, Qt::CaseInsensitive);
2465 const QString suffix = sourceInfo.suffix().toLower();
2466 if (shaderIndex < 0 || (suffix != QStringLiteral(
"glsl") && suffix != QStringLiteral(
"frag") && suffix != QStringLiteral(
"comp"))) {
2467 const QString error = tr(
"The editor file is not an active ACMX2 fragment or compute "
2475 const QString previewDirectory = QDir(sourceRoot).filePath(QStringLiteral(
".acmx2-editor-preview"));
2476 if (!QDir().mkpath(previewDirectory)) {
2477 const QString error = tr(
"Could not create the ACMX2 editor preview directory.");
2483 const QString previewPath = QDir(previewDirectory).filePath(QStringLiteral(
"preview-%1-%2.%3").arg(QCoreApplication::applicationPid()).arg(++
editorPreviewSequence).arg(suffix));
2484 QSaveFile previewFile(previewPath);
2485 const QByteArray sourceBytes = source.toUtf8();
2486 if (!previewFile.open(QIODevice::WriteOnly | QIODevice::Text) || previewFile.write(sourceBytes) != sourceBytes.size() || !previewFile.commit()) {
2487 const QString error = tr(
"Could not write the temporary ACMX2 shader preview.");
2493 const QByteArray reloadPath = QFileInfo(previewPath).canonicalFilePath().toUtf8();
2495 QFile::remove(previewPath);
2496 const QString error = tr(
"The ACMX2 shader preview path is too long.");
2502 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2504 QFile::remove(previewPath);
2505 const QString error = tr(
"Could not lock ACMX2 interface control.");
2510 shaderSelectionShm->reload_shader_index = shaderIndex;
2511 std::fill(std::begin(shaderSelectionShm->reload_shader_path), std::end(shaderSelectionShm->reload_shader_path),
'\0');
2512 std::copy(reloadPath.cbegin(), reloadPath.cend(), shaderSelectionShm->reload_shader_path);
2513 ++shaderSelectionShm->reload_sequence;
2514 ++shaderSelectionShm->sequence;
2520 Log(tr(
"Requested ACMX2 editor preview: %1").arg(shaderName));
2541 connect(
editorPreviewProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
this, [
this](
int exitCode, QProcess::ExitStatus exitStatus) {
2546 if (!diagnostics.isEmpty())
2547 diagnostics += QLatin1Char(
'\n');
2561 Log(tr(
"<b style='color:red;'>ACMXVK editor preview failed: "
2562 "%1</b><pre style='white-space:pre-wrap;'>%2</pre>")
2575 QString compilerError;
2576 const QString glslc = resolve_acmxvk_shader_compiler(compilerError);
2581 if (glslc.isEmpty()) {
2588 const QString sourceRoot = QFileInfo(
shader_path).canonicalFilePath();
2589 const QString previewDirectory = QDir(acmxvk_build_directory(sourceRoot)).filePath(QStringLiteral(
".editor-preview"));
2590 if (!QDir().mkpath(previewDirectory)) {
2591 const QString error = tr(
"Could not create the editor preview directory.");
2596 const QString suffix = sourceInfo.suffix().toLower();
2597 const QString baseName = QStringLiteral(
"preview-%1-%2.%3").arg(QCoreApplication::applicationPid()).arg(++
editorPreviewSequence).arg(suffix == QStringLiteral(
"comp") ? QStringLiteral(
"comp") : QStringLiteral(
"frag"));
2601 const QByteArray sourceBytes = source.toUtf8();
2602 if (!inputFile.open(QIODevice::WriteOnly | QIODevice::Text) || inputFile.write(sourceBytes) != sourceBytes.size() || !inputFile.commit()) {
2603 const QString error = tr(
"Could not write the temporary preview source.");
2612 QStringList arguments{QStringLiteral(
"-I"), sourceInfo.absolutePath()};
2613 if (!sourceRoot.isEmpty() && sourceRoot != sourceInfo.absolutePath())
2614 arguments << QStringLiteral(
"-I") << sourceRoot;
2620 const QString error = tr(
"Failed to start the shader compiler: %1").arg(
editorPreviewProcess->errorString());
2630#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2635 const QString sourceRoot = QFileInfo(
shader_path).canonicalFilePath();
2636 const QString resolvedSourcePath = QFileInfo(sourcePath).canonicalFilePath();
2637 const QString sourceName = sourceRoot.isEmpty() || resolvedSourcePath.isEmpty() ? QString() :
sanitizeShaderName(QDir(sourceRoot).relativeFilePath(resolvedSourcePath));
2638 if (sourceName.isEmpty()) {
2639 Log(tr(
"Compiled shader source cannot be resolved inside the active "
2645 const int shaderIndex =
items.indexOf(sourceName, 0, Qt::CaseInsensitive);
2646 if (shaderIndex < 0) {
2647 Log(tr(
"Compiled shader is not present in the active library "
2653 const QByteArray reloadPath = QFileInfo(runtimePath).canonicalFilePath().toUtf8();
2654 if (reloadPath.isEmpty()) {
2655 Log(tr(
"Compiled shader output cannot be resolved for live reload: %1").arg(runtimePath));
2659 Log(tr(
"Compiled shader path is too long for live reload: %1").arg(runtimePath));
2663 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2665 Log(tr(
"Could not lock the live shader reload channel."));
2668 shaderSelectionShm->reload_shader_index = shaderIndex;
2669 std::fill(std::begin(shaderSelectionShm->reload_shader_path), std::end(shaderSelectionShm->reload_shader_path),
'\0');
2670 std::copy(reloadPath.cbegin(), reloadPath.cend(), shaderSelectionShm->reload_shader_path);
2671 ++shaderSelectionShm->reload_sequence;
2672 ++shaderSelectionShm->sequence;
2673 Log(tr(
"Requested live ACMXVK pipeline reload: %1<br>").arg(sourceName));
2675 Q_UNUSED(sourcePath);
2676 Q_UNUSED(runtimePath);
2681#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2682 if (!shaderSelectionShm)
2685 std::array<qint32, acmx2::ipc::kShaderSelectionMaxPassCount> passIndices;
2686 passIndices.fill(-1);
2689 quint32 passCount = 0;
2695 const int idx =
items.indexOf(name);
2698 passIndices[passCount] = idx;
2699 const QByteArray shaderName = name.toUtf8();
2701 std::copy_n(shaderName.constData(), copyLength, passNames[passCount].begin());
2706 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2708 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
2712 shaderSelectionShm->shader_pass_count = passCount;
2713 std::copy(passIndices.begin(), passIndices.end(), std::begin(shaderSelectionShm->shader_pass_indices));
2714 for (std::size_t i = 0; i < passNames.size(); ++i) {
2715 std::copy(passNames[i].begin(), passNames[i].end(), shaderSelectionShm->shader_pass_names[i]);
2717 ++shaderSelectionShm->sequence;
2722#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2723 if (!shaderSelectionShm)
2725 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2727 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
2731 ++shaderSelectionShm->sequence;
2736#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2737 if (!shaderSelectionShm)
2740 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2742 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
2746 shaderSelectionShm->normalized_time_enabled =
normalized_time ? 1 : 0;
2749 shaderSelectionShm->watermark_enabled = watermarkActive ? 1 : 0;
2750 shaderSelectionShm->watermark_r =
static_cast<uint8_t
>(std::clamp(
watermark_r, 0, 255));
2751 shaderSelectionShm->watermark_g =
static_cast<uint8_t
>(std::clamp(
watermark_g, 0, 255));
2752 shaderSelectionShm->watermark_b =
static_cast<uint8_t
>(std::clamp(
watermark_b, 0, 255));
2753 std::fill(std::begin(shaderSelectionShm->watermark_text), std::end(shaderSelectionShm->watermark_text),
'\0');
2756 const std::size_t wmLen = std::min<std::size_t>(wmCap,
static_cast<std::size_t
>(wmUtf8.size()));
2757 std::copy_n(wmUtf8.constData(),
static_cast<int>(wmLen), shaderSelectionShm->watermark_text);
2759 std::array<qint32, acmx2::ipc::kShaderSelectionMaxGpuFilterCount> gpuIndices;
2760 gpuIndices.fill(-1);
2761 quint32 gpuCount = 0;
2764 for (
const QString &part : parts) {
2768 const int idx = part.trimmed().toInt(&ok);
2771 gpuIndices[gpuCount++] = idx;
2775 shaderSelectionShm->gpu_filter_enabled = (gpuCount > 0) ? 1 : 0;
2776 shaderSelectionShm->gpu_filter_count = gpuCount;
2777 shaderSelectionShm->gpu_buffer_size =
static_cast<uint8_t
>(std::clamp(
gpu_buffer_size, 4, 32));
2778 std::copy(gpuIndices.begin(), gpuIndices.end(), std::begin(shaderSelectionShm->gpu_filter_indices));
2784 shaderSelectionShm->dream_enabled = dreamActive ? 1 : 0;
2798 std::fill(std::begin(shaderSelectionShm->dream_model_path), std::end(shaderSelectionShm->dream_model_path),
'\0');
2799 std::fill(std::begin(shaderSelectionShm->dream_layer), std::end(shaderSelectionShm->dream_layer),
'\0');
2800 if (dreamStringsFit) {
2801 std::copy(dreamModel.cbegin(), dreamModel.cend(), shaderSelectionShm->dream_model_path);
2802 std::copy(dreamLayer.cbegin(), dreamLayer.cend(), shaderSelectionShm->dream_layer);
2804 Log(
"Deep Dream settings were not published because the model path "
2805 "or layer name is too long");
2808 ++shaderSelectionShm->sequence;
2813#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2817 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2819 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
2823 std::fill(std::begin(shaderSelectionShm->custom_uniform_values), std::end(shaderSelectionShm->custom_uniform_values), 0.0f);
2829 const QByteArray name = uniform.name.toUtf8();
2833 std::copy(name.cbegin(), name.cend(), shaderSelectionShm->custom_uniform_names[count]);
2834 shaderSelectionShm->custom_uniform_values[count] =
static_cast<float>(uniform.value);
2837 shaderSelectionShm->custom_uniform_count = count;
2838 ++shaderSelectionShm->sequence;
2843#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2844 if (shaderSelectionShm) {
2845#if defined(__linux__) || defined(__APPLE__)
2848 ::UnmapViewOfFile(shaderSelectionShm);
2850 shaderSelectionShm =
nullptr;
2852#if defined(__linux__) || defined(__APPLE__)
2853 if (shaderSelectionShmFd >= 0) {
2854 ::close(shaderSelectionShmFd);
2855 shaderSelectionShmFd = -1;
2858 if (shaderSelectionMapping !=
nullptr) {
2859 ::CloseHandle(shaderSelectionMapping);
2860 shaderSelectionMapping =
nullptr;
2868#if defined(__linux__) || defined(__APPLE__)
2869 if (shaderSelectionSemaphore == SEM_FAILED)
2873 ::sem_close(shaderSelectionSemaphore);
2874 shaderSelectionSemaphore = SEM_FAILED;
2875#elif defined(_WIN32)
2876 if (shaderSelectionSemaphore ==
nullptr)
2879 ::CloseHandle(shaderSelectionSemaphore);
2880 shaderSelectionSemaphore =
nullptr;
2887 QTreeWidgetItem *it =
list_view->topLevelItem(row);
2891 list_view->scrollToItem(it, QAbstractItemView::PositionAtCenter);
2907 QFileInfo cacheInfo(cachePath);
2908 if (!cacheInfo.exists() || !cacheInfo.isFile()) {
2909 Log(
"Shader cache not found at: " + cachePath);
2927 const QSignalBlocker blocker(
list_view);
2930 QString acmxvk_type_error;
2932 const int width = QString::number(
items.size()).size();
2933 for (
int i = 0; i <
items.size(); ++i) {
2934 const QString &name =
items.at(i);
2936 const QString stem = QFileInfo(name).completeBaseName();
2937 const bool isCompute = name.endsWith(QStringLiteral(
".comp"), Qt::CaseInsensitive) || name.endsWith(QStringLiteral(
".comp.spv"), Qt::CaseInsensitive);
2938 const QString shaderType = isCompute ? tr(
"Compute") : tr(
"Fragment");
2945 health = tr(
"Not Built");
2946 healthColor = QColor(
"#888888");
2948 health = tr(
"Stale");
2949 healthColor = QColor(
"#ffaa00");
2951 health = tr(
"Up to Date");
2952 healthColor = QColor(
"#55ff55");
2955 health = tr(
"No cache");
2956 healthColor = QColor(
"#888888");
2958 health = tr(
"Uncached");
2959 healthColor = QColor(
"#cccc00");
2961 health = tr(
"Failed");
2962 healthColor = QColor(
"#ff5555");
2964 health = tr(
"Stale");
2965 healthColor = QColor(
"#ffaa00");
2967 health = tr(
"Cached");
2968 healthColor = QColor(
"#55ff55");
2972 cols << QString(
"%1").arg(i, width, 10, QLatin1Char(
' ')) << name << (fi.exists() ? formatLastModified(fi.lastModified()) : tr(
"missing")) << health << shaderType;
2973 auto *item =
new QTreeWidgetItem(
list_view, cols);
2974 item->setTextAlignment(0, Qt::AlignRight | Qt::AlignVCenter);
2975 item->setForeground(3, QBrush(healthColor));
2977 item->setForeground(2, QBrush(QColor(
"#ff5555")));
2981 if (previousRow >= 0 && previousRow < list_view->topLevelItemCount()) {
2982 QTreeWidgetItem *it =
list_view->topLevelItem(previousRow);
2985 list_view->scrollToItem(it, QAbstractItemView::PositionAtCenter);
2991 QString normalized = message;
2992 while (normalized.endsWith(
'\n') || normalized.endsWith(
'\r')) {
2998 cursor.movePosition(QTextCursor::End);
3004 cursor.movePosition(QTextCursor::End);
3005 cursor.insertHtml(message);
3008 constexpr int MAX_BLOCKS = 5000;
3010 int excess = doc->blockCount() - MAX_BLOCKS;
3012 QTextCursor trim(doc);
3013 trim.movePosition(QTextCursor::Start);
3014 trim.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor, excess);
3015 trim.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
3016 trim.removeSelectedText();
3030 if (!
output_run_log.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
3031 Log(tr(
"<b style='color:red;'>Unable to save render log: %1</b>").arg(
output_run_log.errorString()));
3036 appendOutputRunLog(tr(
"Started: %1").arg(QDateTime::currentDateTime().toString(Qt::ISODate)));
3047 if (!message.endsWith(QLatin1Char(
'\n'))) {
3059 appendOutputRunLog(tr(
"Finished: %1").arg(QDateTime::currentDateTime().toString(Qt::ISODate)));
3060 appendOutputRunLog(tr(
"Exit: %1 (%2)").arg(exitCode).arg(exitStatus == QProcess::NormalExit ? tr(
"normal") : tr(
"crashed")));
3065 QSettings settings(
"LostSideDead");
3066 QString startDirectory = settings.value(
"lastShaderDir").toString();
3067 if (startDirectory.isEmpty())
3069 if (startDirectory.isEmpty())
3070 startDirectory = QDir::homePath();
3072 const QString directory = QFileDialog::getExistingDirectory(
this, tr(
"Load Shader Library"), startDirectory, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
3073 if (directory.isEmpty())
3076 settings.setValue(
"lastShaderDir", directory);
3084 if (!is_acmxvk_source_library(
shader_path, type_error) || !type_error.isEmpty()) {
3085 QMessageBox::warning(
this, tr(
"Build ACMXVK Library"), reason);
3089 const QMessageBox::StandardButton answer = QMessageBox::question(
this,
3090 tr(
"Build ACMXVK Library"),
3091 tr(
"The ACMXVK build is out of date or incomplete.\n\n%1\n\n"
3092 "Do you wish to rebuild it now?")
3094 QMessageBox::Yes | QMessageBox::No,
3096 if (answer != QMessageBox::Yes)
3100 Log(tr(
"ACMXVK rebuild requested before launch."));
3106 setWindowTitle(tr(
"%1 - Interface").arg(name));
3117 QString sourceTypeError;
3126 buildCacheAction->setText(acmxvkSource ? tr(
"Build") : tr(
"Rebuild Shader Cache"));
3129 buildCacheAction->setToolTip(acmxvkSource ? tr(
"Compile changed GLSL sources into %1").arg(acmxvk_build_directory(
shader_path)) : QString());
3134 fixBuildAction->setToolTip(acmxvkSource ? tr(
"Build into %1 and omit shaders that fail to compile").arg(acmxvk_build_directory(
shader_path)) : QString());
3143 removeBrokenAction->setToolTip(acmxvkSource ? tr(
"Permanently delete .frag and .comp sources that fail "
3144 "the ACMXVK Fix Build")
3161 const bool processIdle = !
process ||
process->state() == QProcess::NotRunning;
3170 list_view->setColumnHidden(3, acmx2Tools);
3174 list_view->headerItem()->setText(3, acmx2Tools ? tr(
"Compile Health") : tr(
"Build Status"));
3183 list_view->setToolTip(tr(
"Right click while running to change the active shader."));
3189 QMessageBox::information(
this, tr(
"Process Running"), tr(
"Stop the running process before changing backends."));
3207 QSettings settings(
"LostSideDead");
3225 QString backendError;
3227 if (!backendError.isEmpty()) {
3228 Log(tr(
"Warning: Could not read backend metadata for %1: %2").arg(nextLibrary, backendError));
3229 }
else if (libraryBackend && *libraryBackend !=
active_backend) {
3251 Log(tr(
"ACMXVK launching and live shader selection are enabled."));
3255 const QString trimmedPath = path.trimmed();
3256 if (trimmedPath.isEmpty())
3258 const QString libraryPath = QDir::cleanPath(trimmedPath);
3259 const QFileInfo libraryInfo(libraryPath);
3260 if (!libraryInfo.exists()) {
3261 QMessageBox::warning(
this, tr(
"Invalid Shader Path"), tr(
"Shader directory does not exist:\n%1").arg(libraryPath));
3264 if (!libraryInfo.isDir()) {
3265 QMessageBox::warning(
this, tr(
"Invalid Shader Path"), tr(
"Shader path is not a directory:\n%1").arg(libraryPath));
3269 QMessageBox::warning(
this, tr(
"Missing Shader Manifest"), tr(
"Shader directory does not contain library.json or index.txt:\n%1").arg(libraryPath));
3272 QString backendError;
3274 if (!backendError.isEmpty()) {
3275 QMessageBox::warning(
this, tr(
"Invalid Backend Metadata"), backendError);
3279 const QMessageBox::StandardButton reply = QMessageBox::question(
this,
3280 tr(
"Switch Backend"),
3281 tr(
"This library targets %1, but the active backend is %2.\n\n"
3282 "Switch to %1 and load it?")
3284 QMessageBox::Yes | QMessageBox::No,
3286 if (reply != QMessageBox::Yes)
3291 QString libraryTypeError;
3293 if (!libraryTypeError.isEmpty()) {
3294 QMessageBox::warning(
this, tr(
"Invalid Library Type"), libraryTypeError);
3299 Log(tr(
"Warning: Could not load shaders from directory: %1").arg(libraryPath));
3303 QSettings settings(
"LostSideDead");
3306 settings.setValue(
"shaders", libraryPath);
3309 Log(tr(
"Successfully loaded shader library: %1").arg(libraryPath));
3315 const QString trimmedPath = path.trimmed();
3316 if (trimmedPath.isEmpty())
3318 const QString libraryPath = QDir::cleanPath(trimmedPath);
3320 QSettings settings(
"LostSideDead");
3323 QStringList recentLibraries = settings.value(recentKey, legacyRecent).toStringList();
3324 for (
auto it = recentLibraries.begin(); it != recentLibraries.end();) {
3325 if (QDir::cleanPath(*it).compare(libraryPath, Qt::CaseInsensitive) == 0)
3326 it = recentLibraries.erase(it);
3330 recentLibraries.prepend(libraryPath);
3331 while (recentLibraries.size() > RECENT_LIBRARY_LIMIT)
3332 recentLibraries.removeLast();
3333 settings.setValue(recentKey, recentLibraries);
3335 settings.setValue(
"recentLibraries", recentLibraries);
3345 QSettings settings(
"LostSideDead");
3348 const QStringList recentLibraries = settings.value(recentKey, legacyRecent).toStringList();
3349 if (recentLibraries.isEmpty()) {
3350 QAction *emptyAction =
loadRecentMenu->addAction(tr(
"No Recent Libraries"));
3351 emptyAction->setEnabled(
false);
3355 for (
const QString &path : recentLibraries) {
3357 connect(action, &QAction::triggered,
this, [
this, path]() {
loadLibraryPath(path); });
3363 PropWindow propWindow(propertiesBackend,
this);
3364 if (propWindow.exec() == QDialog::Accepted) {
3368 QString compilerMode;
3369 QString compilerPath;
3373 if (compilerMode == QStringLiteral(
"custom") && compilerPath.isEmpty()) {
3374 QMessageBox::information(
this, tr(
"Shader Compiler"), tr(
"Select a custom glslc-compatible compiler path."));
3378 if (exePath.length() == 0) {
3379 QMessageBox::information(
this,
"No Path",
"Requires Executable path");
3382 if (shaderDir.length() == 0) {
3383 QMessageBox::information(
this,
"Shader Path",
"Requires Shader Path");
3390 QSettings appSettings(
"LostSideDead");
3394 appSettings.setValue(
"exePath", exePath);
3401 Log(tr(
"Backend changed while loading the library; retained the "
3402 "%1 executable setting.")
3405 appSettings.setValue(
"prefix_path", prefix);
3411 Log(
"Prefix Path: " + prefix);
3412 Log(
"Shader Directory: " + shaderDir);
3421 QMessageBox::information(
this, tr(
"Custom Uniforms"), tr(
"Load a shader library first."));
3424 const QString jsonPath = QDir(
shader_path).filePath(
"library.json");
3425 if (!QFileInfo(jsonPath).isFile()) {
3426 QMessageBox::warning(
this, tr(
"Custom Uniforms"), tr(
"Custom uniforms require a library.json manifest."));
3432 QMessageBox::warning(
this, tr(
"Could Not Load Custom Uniforms"), error);
3453 if (manifestPath.isEmpty()) {
3454 QMessageBox::warning(
this,
"Could not open shader manifest",
"No library.json or index.txt found in: " + path);
3459 bool generated =
false;
3460 QString migrationError;
3462 Log(
"Could not generate library.json from index.txt: " + migrationError);
3463 }
else if (generated) {
3465 Log(
"Generated library.json from index.txt");
3469 QDateTime modified = QFileInfo(manifestPath).lastModified();
3473 QStringList manifestEntries;
3474 QString manifestError;
3476 QMessageBox::warning(
this,
"Could not open shader manifest", manifestError);
3483 if (
customUniformDialog && QFileInfo(manifestPath).fileName().compare(
"library.json", Qt::CaseInsensitive) == 0) {
3484 QString uniformError;
3486 Log(
"Could not load custom uniforms: " + uniformError);
3492 QStringList uniqueItems;
3493 for (
const QString &rawEntry : manifestEntries) {
3494 const QString line = rawEntry.trimmed();
3496 if (line.isEmpty()) {
3500 if (shaderEntry.isEmpty()) {
3501 Log(
"Skipping invalid shader path in " + QFileInfo(manifestPath).fileName() +
": " + line);
3504 QString fullPath = path +
"/" + shaderEntry;
3505 QFileInfo fileInfo(fullPath);
3506 if (!fileInfo.exists() || !fileInfo.isFile()) {
3507 Log(
"Skipping non-existent file: " + shaderEntry);
3510 if (!uniqueItems.contains(shaderEntry, Qt::CaseInsensitive)) {
3511 uniqueItems.append(shaderEntry);
3513 Log(
"Skipping duplicate shader: " + shaderEntry);
3516 items = uniqueItems;
3518 Log(
"Loaded " + QString::number(
items.size()) +
" unique shader files");
3522 if (!
items.isEmpty()) {
3523 int restoredRow = previousRow;
3524 if (restoredRow < 0 || restoredRow >=
items.size()) {
3525 if (!previouslySelected.isEmpty() &&
items.contains(previouslySelected)) {
3526 restoredRow =
items.indexOf(previouslySelected);
3541 QMessageBox::information(
this, tr(
"Audio Settings"), tr(
"Audio support is unavailable: acmx2 was built without audio support."));
3544 const QString previousAudioFile =
audio_file;
3550 if (audio_set.exec() == QDialog::Accepted) {
3569 Log(
"Audio Settings Saved");
3570#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
3572 if (shaderSelectionShm &&
process &&
process->state() == QProcess::Running && !
audio_file.isEmpty() && liveAudioSettingsChanged) {
3573 const QByteArray path = QFileInfo(
audio_file).absoluteFilePath().toUtf8();
3575 Log(
"Audio file path is too long for live playback: " +
audio_file);
3577 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
3579 Log(
"Could not lock the live playback control channel");
3582 std::fill(std::begin(shaderSelectionShm->audio_file_path), std::end(shaderSelectionShm->audio_file_path),
'\0');
3583 std::copy(path.cbegin(), path.cend(), shaderSelectionShm->audio_file_path);
3584 shaderSelectionShm->audio_output_device =
audio_output;
3586 shaderSelectionShm->audio_trunc =
audio_trunc ? 1 : 0;
3587 shaderSelectionShm->audio_repeat =
audio_repeat ? 1 : 0;
3588 ++shaderSelectionShm->audio_file_sequence;
3589 ++shaderSelectionShm->sequence;
3590 Log(
"Requested live audio-file change: " +
audio_file +
"<br>");
3599 QMessageBox::information(
this, tr(
"GPU Filter Settings"), tr(
"GPU filters are unavailable: acmx2 was built without CUDA support."));
3614 auto applyGpuDialogSettings = [
this](
bool enabled,
const QString &filters,
int bufferSize) {
3620 QSettings(
"LostSideDead",
"acmx2").setValue(
"deep_dream/gpu_filter_first",
false);
3621 Log(
"Deep Dream pipeline order reset because GPU filtering was disabled");
3626 Log(
"GPU Filtering Disabled");
3631 connect(dialog, &
GPUFilterDialog::settingsApplied,
this, [applyGpuDialogSettings](
bool enabled,
const QString &filterArgument,
int bufferSize) { applyGpuDialogSettings(enabled, filterArgument, bufferSize); });
3636 dialog->activateWindow();
3641 QMessageBox::information(
this,
3642 tr(
"Deep Dream Settings"),
3643 tr(
"Deep Dream is unavailable: ACMXVK must be built with "
3644 "-DWITH_DEEP_DREAM=ON and CUDA-enabled LibTorch."));
3681 Log(tr(
"Deep Dream Settings Applied: %1/%2, %3 "
3682 "iteration(s), rotation %4 degrees, %5, %6")
3689 Log(
"Deep Dream Disabled");
3694 dialog->activateWindow();
3703 error = tr(
"The selected ACMXVK executable does not provide Deep "
3708 error = tr(
"The configured Deep Dream model does not exist:\n%1").arg(
deep_dream_model);
3712 error = tr(
"Select a Deep Dream feature layer.");
3716 error = tr(
"Independent-frame preview requires video input and an "
3717 "enabled video output file.");
3724 error = tr(
"Running acidcam-gpu before Deep Dream requires an enabled "
3725 "GPU filter chain.");
3729 error = tr(
"Running acidcam-gpu before Deep Dream currently supports "
3730 "camera and video input, not still graphics.");
3734 error = tr(
"Running acidcam-gpu before Deep Dream cannot be combined "
3735 "with Maximize FPS.");
3739 error = tr(
"Running acidcam-gpu before Deep Dream cannot be combined "
3740 "with an ONNX input effect.");
3756 arguments <<
"--dream-zoom" << QString::number(
deep_dream_zoom,
'g', 12);
3760 arguments <<
"--dream-fp16";
3768 arguments <<
"--gpu-filter-before-dream";
3771 arguments <<
"--deep-orig";
3777 QMessageBox::information(
this,
3778 tr(
"Stable Diffusion Settings"),
3779 tr(
"Stable Diffusion is unavailable: ACMXVK must be built with "
3780 "-DWITH_STABLE_DIFFUSION=ON."));
3820 Log(tr(
"Stable Diffusion Settings Applied: %1, %2x%3, "
3821 "%4 step(s)%5%6; changes apply on the next launch")
3832 Log(
"Stable Diffusion Disabled");
3837 dialog->activateWindow();
3846 error = tr(
"The selected ACMXVK executable does not provide Stable "
3847 "Diffusion support.");
3851 error = tr(
"Stable Diffusion requires an existing video input file.");
3855 error = tr(
"Stable Diffusion cannot be used with still-image input.");
3863 error = tr(
"The saved Stable Diffusion LoRA settings are incomplete. "
3864 "Open Stable Diffusion Settings and apply them again.");
3867 QString lora_directory;
3870 const QFileInfo file_info(filename);
3871 if (!file_info.isFile()) {
3872 error = tr(
"The configured LoRA model does not exist:\n%1").arg(filename);
3875 if (lora_directory.isEmpty()) {
3876 lora_directory = file_info.absolutePath();
3877 }
else if (lora_directory != file_info.absolutePath()) {
3878 error = tr(
"All configured LoRA models must be in the same folder. "
3879 "sd-server scans one LoRA model directory per launch.");
3883 if (multiplier < -10.0 || multiplier > 10.0) {
3884 error = tr(
"LoRA multipliers must be between -10 and 10.");
3889 error = tr(
"The configured Stable Diffusion upscale model does not "
3895 error = tr(
"ESRGAN upscale-only mode requires an existing upscale model.");
3899 error = tr(
"Enter a Stable Diffusion image-to-image prompt.");
3903 error = tr(
"Enter the sd-server executable name or path.");
3907 error = tr(
"Stable Diffusion dimensions must be multiples of 64.");
3911 error = tr(
"Stable Diffusion cannot be combined with Fill PTS Gaps.");
3922 arguments <<
"--sd-upscale-only";
3936 for (
const QString &argument : server_arguments) {
3937 arguments <<
"--sd-server-arg" << argument;
3952 arguments <<
"--sd-quiet";
3954 arguments <<
"--sd-upscale";
3962 QMessageBox::information(
this, tr(
"MIDI Settings"), tr(
"MIDI support is unavailable: acmx2 was built without MIDI support."));
3966 if (midiDialog.exec() == QDialog::Accepted) {
3970 QSettings appSettings(
"LostSideDead");
3977 Log(
"MIDI Disabled");
3984 QSettings appSettings(
"LostSideDead");
3992 dlg.setWindowTitle(tr(
"Watermark Settings"));
3995 auto *enableCheck =
new QCheckBox(tr(
"Enable watermark in recorded video"), &dlg);
3999 textEdit->setPlaceholderText(tr(
"Watermark text (shown upper-left of recorded video)"));
4001 auto *colorPreview =
new QLabel(&dlg);
4002 colorPreview->setAutoFillBackground(
true);
4003 colorPreview->setMinimumSize(80, 24);
4004 colorPreview->setFrameStyle(QFrame::Box | QFrame::Plain);
4005 colorPreview->setAlignment(Qt::AlignCenter);
4008 auto applyPreview = [colorPreview, &curR, &curG, &curB]() {
4009 QPalette pal = colorPreview->palette();
4010 pal.setColor(QPalette::Window, QColor(curR, curG, curB));
4011 QColor fg = (curR * 0.299 + curG * 0.587 + curB * 0.114) > 140 ? Qt::black : Qt::white;
4012 pal.setColor(QPalette::WindowText, fg);
4013 colorPreview->setPalette(pal);
4014 colorPreview->setText(QString(
" %1, %2, %3 ").arg(curR).arg(curG).arg(curB));
4018 auto *colorBtn =
new QPushButton(tr(
"Choose Color..."), &dlg);
4019 QObject::connect(colorBtn, &QPushButton::clicked, &dlg, [&]() {
4020 QColor chosen = QColorDialog::getColor(QColor(curR, curG, curB), &dlg, tr(
"Watermark Color"));
4021 if (chosen.isValid()) {
4022 curR = chosen.red();
4023 curG = chosen.green();
4024 curB = chosen.blue();
4029 auto *form =
new QFormLayout();
4030 form->addRow(enableCheck);
4031 form->addRow(tr(
"Text:"), textEdit);
4032 auto *colorRow =
new QHBoxLayout();
4033 colorRow->addWidget(colorPreview, 1);
4034 colorRow->addWidget(colorBtn);
4035 form->addRow(tr(
"Color:"), colorRow);
4037 auto *buttons =
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
4038 QObject::connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
4039 QObject::connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
4041 auto *layout =
new QVBoxLayout(&dlg);
4042 layout->addLayout(form);
4043 layout->addWidget(buttons);
4045 if (dlg.exec() != QDialog::Accepted) {
4055 QSettings appSettings(
"LostSideDead");
4068 QMessageBox::information(
this,
"Load Shaders First",
"Please load a shader library before configuring multi-pass shaders.");
4074 if (
items.isEmpty()) {
4075 QMessageBox::information(
this,
"Load Shaders First",
"Please load a shader library before configuring multi-pass shaders.");
4095 auto applyMultipassSettings = [
this, dialog]() {
4100 Log(
"Multi-Pass Shader Settings Saved: " + QString::number(
shader_pass_names.size()) +
" passes");
4102 Log(
"Multi-Pass Shader Disabled");
4111 Log(
"Multi-Pass Shader Settings Saved: " + QString::number(
shader_pass_names.size()) +
" passes");
4113 Log(
"Multi-Pass Shader Disabled");
4118 if (!safeName.isEmpty())
4121 connect(dialog, &QDialog::accepted,
this, applyMultipassSettings);
4125 dialog->activateWindow();
4130 QMessageBox::information(
this,
"Load Shaders First",
"Please load a shader library before configuring playlist.");
4136 if (
items.isEmpty()) {
4137 QMessageBox::information(
this,
"Load Shaders First",
"Please load a shader library before configuring playlist.");
4164 connect(dialog, &QDialog::accepted,
this, [
this, dialog]() {
4171 QSettings appSettings(
"LostSideDead");
4175 Log(
"Playlist Settings Saved: " + QString::number(
playlist_names.size()) +
" shaders");
4183 Log(
"Playlist Disabled");
4189 dialog->activateWindow();
4196 if (settingsWindow.exec() == QDialog::Accepted) {
4282 if (
process->state() == QProcess::Running) {
4283 QMessageBox::information(
this,
"Process Running",
"A process is already running. Please stop it first.");
4287 QString deep_dream_error;
4289 QMessageBox::warning(
this, tr(
"Deep Dream Settings"), deep_dream_error);
4292 QString stable_diffusion_error;
4294 QMessageBox::warning(
this, tr(
"Stable Diffusion Settings"), stable_diffusion_error);
4299 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
4300 for (
const QString &entry : defaultLinuxRunEnvAssignments()) {
4301 const int equals = entry.indexOf(
'=');
4303 env.insert(entry.left(equals), entry.mid(equals + 1));
4306 process->setProcessEnvironment(env);
4310 QMessageBox::information(
this,
"Select Shaders",
"Select Shader Path");
4318 if (data.isEmpty()) {
4319 Log(
"<b>No item selected.</b>");
4323 QString launchShaderName = data;
4325 QString runtimeError;
4326 if (!resolve_acmxvk_runtime_library(
shader_path, launchShaderPath, runtimeError)) {
4331 launchShaderName = acmxvk_runtime_shader_name(data);
4332 if (!QFileInfo(QDir(launchShaderPath).filePath(launchShaderName)).isFile()) {
4333 QMessageBox::warning(
this,
4334 tr(
"Build ACMXVK Library"),
4335 tr(
"The compiled shader is missing. Choose Playback > Build "
4336 "and try again.\n\n%1")
4337 .arg(QDir(launchShaderPath).filePath(launchShaderName)));
4341 QStringList arguments;
4342 QString dirPath = QCoreApplication::applicationDirPath();
4348 if (selectedIndex < 0 || selectedIndex >=
items.size()) {
4349 Log(
"<b>No valid shader selection.</b>");
4353 arguments <<
"--unbuffered";
4354 arguments <<
"--path" << dirPath;
4358 arguments <<
"--shaders" << launchShaderPath <<
"--shader-file" << launchShaderName <<
"--interface-shm";
4362 arguments <<
"--fragment" << (
shader_path +
"/" + data) <<
"--interface-shm";
4368 arguments <<
"--texture-cache-array";
4369 const QSize effectiveCameraResolution = hasPositiveResolution(
camera_res) ?
camera_res : QSize(1280, 720);
4371 QTextStream stream(&res);
4372 stream << effectiveCameraResolution.width() <<
"x" << effectiveCameraResolution.height();
4375 QTextStream stream_r(&scr_res);
4379 arguments <<
"--fullscreen";
4384 arguments <<
"--resolution" << scr_res;
4385 arguments <<
"--fps" << QString::number(
output_fps);
4387 arguments <<
"--camera-res" << res;
4389 arguments <<
"--resolution" << scr_res;
4390 arguments <<
"--device" << QString::number(
camera_index);
4391 arguments <<
"--fps" << QString::number(
output_fps);
4393 arguments <<
"--maximize-fps";
4395 arguments <<
"--use-yuv";
4397 arguments <<
"--texture-cache";
4398 arguments <<
"--cache-delay" << QString::number(
cache_delay);
4403 arguments <<
"--use-source-fps";
4405 arguments <<
"--use-source-audio";
4408 arguments <<
"--resolution" << scr_res;
4410 arguments <<
"--repeat";
4412 arguments <<
"--texture-cache";
4413 arguments <<
"--cache-delay" << QString::number(
cache_delay);
4416 arguments <<
"--copy-audio";
4420 arguments <<
"--png-level" << QString::number(
png_level);
4428 arguments <<
"--encode-crf" << QString::number(
encode_crf);
4438 arguments <<
"--encode-realtime";
4440 arguments <<
"--no-drop";
4442 arguments <<
"--constant-frame-rate";
4444 arguments <<
"--fill-pts-gaps";
4448 arguments <<
"--enable-audio";
4452 arguments <<
"--audio-input" <<
"default";
4454 arguments <<
"--audio-input" << QString::number(
audio_input);
4460 wavPath = fi.absolutePath() +
"/" + fi.completeBaseName() +
".wav";
4464 arguments <<
"--record-audio" << wavPath;
4465 arguments <<
"--record-gain" << QString::number(
record_volume,
'f', 2);
4470 arguments <<
"--mute-output";
4474 arguments <<
"--sense" << QString::number(
audio_sense);
4476 arguments <<
"--pass-through";
4478 arguments <<
"--audio-output" <<
"default";
4480 arguments <<
"--audio-output" << QString::number(
audio_output);
4487 arguments <<
"--audio-trunc";
4490 arguments <<
"--audio-repeat";
4499 arguments <<
"--audio-warm-rate" << QString::number(
audio_warm_rate,
'f', 2);
4503 arguments <<
"--enable-3d";
4520 arguments <<
"--cuda-device" << QString::number(
cuda_device);
4523 arguments <<
"--time-speed" << QString::number(
static_cast<double>(
time_speed),
'f', 2);
4525 arguments <<
"--normalized";
4529 arguments <<
"--no-cache";
4535 arguments <<
"--midi-device" << QString::number(
midi_device);
4539 arguments <<
"--duration" << QString::number(
max_duration,
'f', 1);
4543 arguments <<
"--max-size" << QString::number(
max_size_mb,
'f', 2);
4547 arguments <<
"--cross-fade" << QString::number(
static_cast<double>(
cross_fade_duration),
'f', 2);
4551 arguments <<
"--flip";
4563 if (effective_generate_interval > 0) {
4564 arguments <<
"--generate" << QString::number(effective_generate_interval);
4573 arguments <<
"--display-filter";
4583 if (!
process->waitForStarted()) {
4586 Log(
"<b style='color:red;'>Failed to start the program.</b>");
4587 QMessageBox::critical(
this,
"Error",
"Failed to start the program.");
4594 QString deep_dream_error;
4596 QMessageBox::warning(
this, tr(
"Deep Dream Settings"), deep_dream_error);
4599 QString stable_diffusion_error;
4601 QMessageBox::warning(
this, tr(
"Stable Diffusion Settings"), stable_diffusion_error);
4605 QMessageBox::information(
this,
"Select Shaders",
"Select Shader Path");
4612 Log(
"No selection, defaulting to index 0");
4616 Log(
"Selected shader: " + selectedData +
" at index: " + QString::number(index));
4618 if (
items.isEmpty()) {
4619 QMessageBox::warning(
this, tr(
"Empty Shader Library"), tr(
"The selected shader library contains no shaders."));
4622 if (index < 0 || index >=
items.size()) {
4623 QMessageBox::warning(
this, tr(
"Invalid Shader Selection"), tr(
"Select a shader from the active library."));
4627 QString launchShaderName =
items.at(index);
4629 QString runtimeError;
4631 if (is_acmxvk_source_library(
shader_path, runtimeError)) {
4632 launchShaderPath = acmxvk_build_directory(
shader_path);
4633 launchShaderName = acmxvk_runtime_shader_name(launchShaderName);
4634 }
else if (!runtimeError.isEmpty()) {
4635 QMessageBox::warning(
this, tr(
"ACMXVK Library"), runtimeError);
4639 if (!resolve_acmxvk_runtime_library(
shader_path, launchShaderPath, runtimeError)) {
4644 launchShaderName = acmxvk_runtime_shader_name(launchShaderName);
4645 if (!QFileInfo(QDir(launchShaderPath).filePath(launchShaderName)).isFile()) {
4646 QMessageBox::warning(
this,
4647 tr(
"Build ACMXVK Library"),
4648 tr(
"The compiled shader is missing. Choose Playback > "
4649 "Build and try again.\n\n%1")
4650 .arg(QDir(launchShaderPath).filePath(launchShaderName)));
4655 QString dirPath = QCoreApplication::applicationDirPath();
4661 QString shader_file = launchShaderPath;
4663 arguments <<
"--unbuffered";
4664 arguments <<
"--path" << dirPath <<
"--shaders" << shader_file;
4665 arguments <<
"--interface-shm";
4669 arguments <<
"--texture-cache-array";
4670 const QSize effectiveCameraResolution = hasPositiveResolution(
camera_res) ?
camera_res : QSize(1280, 720);
4672 QTextStream stream(&res);
4673 stream << effectiveCameraResolution.width() <<
"x" << effectiveCameraResolution.height();
4675 QTextStream stream_r(&scr_res);
4679 arguments <<
"--fullscreen";
4684 arguments <<
"--resolution" << scr_res;
4685 arguments <<
"--fps" << QString::number(
output_fps);
4687 arguments <<
"--camera-res" << res;
4689 arguments <<
"--resolution" << scr_res;
4690 arguments <<
"--device" << QString::number(
camera_index);
4691 arguments <<
"--fps" << QString::number(
output_fps);
4693 arguments <<
"--maximize-fps";
4695 arguments <<
"--use-yuv";
4697 arguments <<
"--texture-cache";
4698 arguments <<
"--cache-delay" << QString::number(
cache_delay);
4703 arguments <<
"--use-source-fps";
4705 arguments <<
"--use-source-audio";
4708 arguments <<
"--resolution" << scr_res;
4710 arguments <<
"--repeat";
4712 arguments <<
"--texture-cache";
4713 arguments <<
"--cache-delay" << QString::number(
cache_delay);
4716 arguments <<
"--copy-audio";
4720 arguments <<
"--png-level" << QString::number(
png_level);
4727 arguments <<
"--encode-crf" << QString::number(
encode_crf);
4737 arguments <<
"--encode-realtime";
4739 arguments <<
"--no-drop";
4741 arguments <<
"--constant-frame-rate";
4743 arguments <<
"--fill-pts-gaps";
4745 arguments <<
"--shader-file" << launchShaderName;
4749 arguments <<
"--enable-audio";
4753 arguments <<
"--audio-input" <<
"default";
4755 arguments <<
"--audio-input" << QString::number(
audio_input);
4761 wavPath = fi.absolutePath() +
"/" + fi.completeBaseName() +
".wav";
4765 arguments <<
"--record-audio" << wavPath;
4766 arguments <<
"--record-gain" << QString::number(
record_volume,
'f', 2);
4771 arguments <<
"--mute-output";
4775 arguments <<
"--sense" << QString::number(
audio_sense);
4777 arguments <<
"--pass-through";
4779 arguments <<
"--audio-output" <<
"default";
4781 arguments <<
"--audio-output" << QString::number(
audio_output);
4788 arguments <<
"--audio-trunc";
4791 arguments <<
"--audio-repeat";
4800 arguments <<
"--audio-warm-rate" << QString::number(
audio_warm_rate,
'f', 2);
4804 arguments <<
"--enable-3d";
4822 if (!passIndices.isEmpty()) {
4823 QStringList passFiles;
4824 const QStringList indexValues = passIndices.split(
',');
4825 for (
const QString &indexValue : indexValues) {
4827 const int passIndex = indexValue.toInt(&ok);
4828 if (ok && passIndex >= 0 && passIndex <
items.size()) {
4829 const QString passFile =
items.at(passIndex);
4830 passFiles.append(launchShaderPath ==
shader_path ? passFile : acmxvk_runtime_shader_name(passFile));
4833 QByteArray passFilePayload;
4834 for (
const QString &passFile : passFiles) {
4835 const QByteArray encodedName = passFile.toUtf8();
4836 passFilePayload.append(QByteArray::number(encodedName.size()));
4837 passFilePayload.append(
':');
4838 passFilePayload.append(encodedName);
4840 arguments <<
"--shader-pass-files" << QString::fromUtf8(passFilePayload);
4845 arguments <<
"--cuda-device" << QString::number(
cuda_device);
4848 arguments <<
"--time-speed" << QString::number(
static_cast<double>(
time_speed),
'f', 2);
4850 arguments <<
"--normalized";
4854 arguments <<
"--no-cache";
4860 arguments <<
"--midi-device" << QString::number(
midi_device);
4864 if (playlistActive) {
4866 if (plFile.isEmpty()) {
4870 if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
4871 QTextStream out(&f);
4874 out <<
"[" << nodeName <<
"]\n";
4875 for (
const QString &name : shaders) {
4876 out << (launchShaderPath ==
shader_path ? name : acmxvk_runtime_shader_name(name)) <<
"\n";
4881 out << (launchShaderPath ==
shader_path ? name : acmxvk_runtime_shader_name(name)) <<
"\n";
4887 arguments <<
"--playlist" << plFile;
4895 arguments <<
"--duration" << QString::number(
max_duration,
'f', 1);
4899 arguments <<
"--max-size" << QString::number(
max_size_mb,
'f', 2);
4903 arguments <<
"--cross-fade" << QString::number(
static_cast<double>(
cross_fade_duration),
'f', 2);
4907 arguments <<
"--flip";
4919 if (effective_generate_interval > 0) {
4920 arguments <<
"--generate" << QString::number(effective_generate_interval);
4929 arguments <<
"--display-filter";
4932 if (include_extra_arguments) {
4944 Log(
"<b style='color:red;'>HDR10 conversion already running; skipping.</b>");
4948 Log(
"<b style='color:red;'>HDR10 conversion: source file missing.</b>");
4953 const QString suffix = fi.suffix();
4954 const QString hdr10Path = fi.absolutePath() +
"/" + fi.completeBaseName() +
".HDR10" + (suffix.isEmpty() ? QString() :
"." + suffix);
4965 if (codecChoice ==
"software" || codecChoice ==
"libx265" || codecChoice ==
"x265") {
4967 }
else if (codecChoice ==
"nvenc" || codecChoice ==
"hevc_nvenc") {
4977 QString nvencPreset;
4979 if (p ==
"ultrafast")
4981 else if (p ==
"superfast")
4983 else if (p ==
"veryfast")
4985 else if (p ==
"faster")
4987 else if (p ==
"fast")
4989 else if (p ==
"medium")
4991 else if (p ==
"slow")
4993 else if (p ==
"slower")
4995 else if (p ==
"veryslow")
4997 else if (p.startsWith(
"p") && p.size() == 2 && p[1].isDigit())
5002 args <<
"-vf" <<
"zscale=p=bt2020:t=smpte2084:m=bt2020nc,format=p010le"
5003 <<
"-c:v" <<
"hevc_nvenc"
5004 <<
"-preset" << nvencPreset <<
"-tune" <<
"hq"
5006 <<
"-maxrate" <<
"60M"
5007 <<
"-bufsize" <<
"60M"
5008 <<
"-color_primaries" <<
"bt2020"
5009 <<
"-colorspace" <<
"bt2020nc"
5010 <<
"-color_trc" <<
"smpte2084";
5011 Log(
"HDR10 codec: hevc_nvenc (CUDA detected, preset=" + nvencPreset +
")<br>");
5013 args <<
"-vf" <<
"zscale=p=bt2020:t=smpte2084:m=bt2020nc,format=yuv420p10le"
5014 <<
"-c:v" <<
"libx265"
5016 <<
"-maxrate" <<
"60M"
5017 <<
"-bufsize" <<
"60M"
5018 <<
"-pix_fmt" <<
"yuv420p10le"
5020 <<
"hdr10=1:hdr10-opt=1:repeat-headers=1:"
5021 "colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:range=limited:"
5022 "master-display=G(8500,39850)B(6550,2300)R(35400,14600)WP(15635,16450)L(10000000,1):"
5024 <<
"-color_primaries" <<
"bt2020"
5025 <<
"-colorspace" <<
"bt2020nc"
5026 <<
"-color_trc" <<
"smpte2084";
5027 Log(
"HDR10 codec: libx265 (codec=" + (codecChoice.isEmpty() ? QStringLiteral(
"auto") : codecChoice) +
")<br>");
5030 args <<
"-c:a" <<
"copy" << hdr10Path;
5033 Log(
"HDR10 output: " + hdr10Path +
"<br>");
5037 hdr10Process->setProcessChannelMode(QProcess::MergedChannels);
5040 Log(
"<b style='color:red;'>Failed to start ffmpeg for HDR10 conversion.</b>");
5047 if (
process->state() == QProcess::Running) {
5048 QMessageBox::information(
this,
"Process Running",
"A process is already running. Please stop it first.");
5053 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
5054 for (
const QString &entry : defaultLinuxRunEnvAssignments()) {
5055 int eq = entry.indexOf(
'=');
5059 env.insert(entry.left(eq), entry.mid(eq + 1));
5061 process->setProcessEnvironment(env);
5064 QStringList arguments;
5075 if (!
process->waitForStarted()) {
5078 Log(
"<b style='color:red;'>Failed to start the program.</b>");
5079 QMessageBox::critical(
this,
"Error",
"Failed to start the program.");
5086 QStringList arguments;
5093 QStringList envAssignments;
5095 envAssignments = defaultLinuxRunEnvAssignments();
5097 QString commandText = buildShellCommand(envAssignments, exe, arguments).trimmed();
5099 QDialog dialog(
this);
5100 dialog.setWindowTitle(tr(
"Edit Command"));
5104 QVBoxLayout *layout =
new QVBoxLayout(&dialog);
5105 QPlainTextEdit *textBox =
new QPlainTextEdit(&dialog);
5106 textBox->setPlainText(commandText);
5107 textBox->setReadOnly(
false);
5108 textBox->setLineWrapMode(QPlainTextEdit::WidgetWidth);
5110 textBox->setStyleSheet(
"QPlainTextEdit { background-color: black; color: lime; "
5111 "font-size: 14px; font-family: 'Courier New', Courier, monospace; "
5112 "border: 1px solid red; }");
5114 QFont commandFont(
"Courier New");
5115 commandFont.setStyleHint(QFont::Monospace);
5116 commandFont.setPointSize(14);
5117 textBox->setFont(commandFont);
5119 layout->addWidget(textBox);
5121 auto *extraArgumentsEdit =
new QLineEdit(&dialog);
5123 extraArgumentsEdit->setPlaceholderText(tr(
"--option value --another-option \"value with spaces\""));
5124 extraArgumentsEdit->setToolTip(tr(
"These arguments are appended after the generated arguments for "
5125 "Run Selected, Run All, and this dialog's command. Use double "
5126 "quotes around values containing spaces."));
5127 auto *extraArgumentsLayout =
new QFormLayout();
5128 extraArgumentsLayout->addRow(tr(
"Extra arguments:"), extraArgumentsEdit);
5129 layout->addLayout(extraArgumentsLayout);
5132 QSettings settings(
"LostSideDead");
5135 auto *parallelBuildCheckBox =
new QCheckBox(tr(
"Enable parallel build"), &dialog);
5136 auto *parallelBuildJobsSpinBox =
new QSpinBox(&dialog);
5137 parallelBuildJobsSpinBox->setRange(1, 256);
5138 parallelBuildJobsSpinBox->setValue(qBound(1, settings.value(jobsKey, 2).toInt(), 256));
5139 parallelBuildCheckBox->setChecked(settings.value(enabledKey,
false).toBool());
5140 parallelBuildJobsSpinBox->setEnabled(parallelBuildCheckBox->isChecked());
5141 parallelBuildJobsSpinBox->setToolTip(tr(
"Number of concurrent ACMXVK shader compiler jobs (1-256)."));
5142 auto *parallelBuildLayout =
new QHBoxLayout();
5143 parallelBuildLayout->addWidget(parallelBuildCheckBox);
5144 parallelBuildLayout->addWidget(
new QLabel(tr(
"Jobs:"), &dialog));
5145 parallelBuildLayout->addWidget(parallelBuildJobsSpinBox);
5146 parallelBuildLayout->addStretch(1);
5147 layout->addLayout(parallelBuildLayout);
5148 connect(parallelBuildCheckBox, &QCheckBox::toggled, &dialog, [parallelBuildJobsSpinBox, enabledKey](
bool enabled) {
5149 parallelBuildJobsSpinBox->setEnabled(enabled);
5150 QSettings settings(
"LostSideDead");
5151 settings.setValue(enabledKey, enabled);
5153 connect(parallelBuildJobsSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), &dialog, [jobsKey](
int jobs) {
5154 QSettings settings(
"LostSideDead");
5155 settings.setValue(jobsKey, jobs);
5159 QDialogButtonBox *buttonBox =
new QDialogButtonBox(&dialog);
5160 QPushButton *copyButton = buttonBox->addButton(tr(
"Copy to Clipboard"), QDialogButtonBox::ActionRole);
5161 QPushButton *runButton = buttonBox->addButton(tr(
"Run"), QDialogButtonBox::ActionRole);
5162 QPushButton *okButton = buttonBox->addButton(QDialogButtonBox::Ok);
5163 layout->addWidget(buttonBox);
5165 const auto saveExtraArguments = [
this, extraArgumentsEdit]() {
5167 QSettings settings(
"LostSideDead",
"acmx2");
5170 const auto editedCommand = [textBox, extraArgumentsEdit]() {
5171 QString command = textBox->toPlainText().trimmed();
5172 const QString extras = extraArgumentsEdit->text().trimmed();
5173 if (!extras.isEmpty()) {
5174 command += QLatin1Char(
' ');
5180 connect(copyButton, &QPushButton::clicked, &dialog, [saveExtraArguments, editedCommand, &dialog]() {
5181 saveExtraArguments();
5182 const QString copiedText = editedCommand();
5183 QClipboard *clipboard = QGuiApplication::clipboard();
5184 clipboard->setText(copiedText, QClipboard::Clipboard);
5186 if (clipboard->supportsSelection()) {
5187 clipboard->setText(copiedText, QClipboard::Selection);
5190 QCoreApplication::processEvents();
5191 QMessageBox::information(&dialog, tr(
"Copied"), tr(
"Command copied to clipboard."));
5193 connect(runButton, &QPushButton::clicked, &dialog, [
this, saveExtraArguments, editedCommand, &dialog]() {
5194 if (
process->state() != QProcess::NotRunning) {
5195 QMessageBox::information(&dialog, tr(
"Process Running"), tr(
"A process is already running. Please stop it first."));
5198 saveExtraArguments();
5199 const QString cmdText = editedCommand();
5200 if (cmdText.isEmpty()) {
5201 QMessageBox::warning(&dialog, tr(
"Empty Command"), tr(
"The command is empty."));
5209 process->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
5211 QString shell = qEnvironmentVariable(
"COMSPEC");
5212 if (shell.isEmpty())
5214 QStringList shellArgs{
"/C", cmdText};
5216 QString shell =
"/bin/sh";
5217 QStringList shellArgs{
"-c", cmdText};
5219 Log(
"shell: " + cmdText +
"<br>");
5222 process->start(shell, shellArgs);
5223 if (!
process->waitForStarted()) {
5226 Log(
"<b style='color:red;'>Failed to start the program.</b>");
5227 QMessageBox::critical(&dialog, tr(
"Error"), tr(
"Failed to start the program."));
5233 connect(okButton, &QPushButton::clicked, &dialog, [saveExtraArguments, &dialog]() {
5234 saveExtraArguments();
5243 QTextStream stream(&text);
5244 for (
auto &i : lst) {
5251 QStringList indices;
5254 int idx =
items.indexOf(name);
5256 indices.append(QString::number(idx));
5259 return indices.join(
",");
5263 QString sanitized = name.trimmed();
5264 sanitized.replace(
'\\',
'/');
5265 sanitized = QDir::cleanPath(sanitized);
5267 while (sanitized.startsWith(
"./")) {
5268 sanitized = sanitized.mid(2);
5271 if (sanitized.isEmpty() || sanitized ==
"." || sanitized ==
"..") {
5272 Log(
"Warning: Invalid shader name detected: " + name);
5276 if (QDir::isAbsolutePath(sanitized) || sanitized.startsWith(
"../") || sanitized.contains(
"/../") || sanitized.endsWith(
"/..")) {
5277 Log(
"Warning: Invalid shader name detected (path traversal attempt): " + name);
5289 if (
items.isEmpty()) {
5292 std::random_device rd;
5293 std::mt19937 g(rd());
5297 Log(
"Shaders shuffled");
5301 if (
items.isEmpty()) {
5304 items.sort(Qt::CaseInsensitive);
5307 Log(
"Shaders sorted alphabetically");
5312 if (build_path.isEmpty()) {
5313 QSettings appSettings(
"LostSideDead");
5317 if (build_path.isEmpty()) {
5319 QMessageBox::warning(
this,
"Error",
"No shader library loaded. Please set a shader directory in Properties or load a shader library first.");
5323 if (
process->state() == QProcess::Running) {
5325 QMessageBox::warning(
this,
"Error",
"A process is already running. Please wait for it to finish.");
5336 Log(
"Rebuild Shader Cache is not available on macOS.");
5341 const QString assets_path = resolveAssetsPath();
5344 args <<
"--build" << build_path;
5345 args <<
"-p" << assets_path;
5348 args <<
"--texture-cache-array";
5351 args <<
"--enable-3d";
5354 Log(
"Building shader cache for: " + build_path);
5362 if (!
process->waitForStarted()) {
5363 Log(
"<b style='color:red;'>Error:</b> Failed to start shader cache build process");
5373 if (
process->state() == QProcess::Running) {
5374 QMessageBox::warning(
this, tr(
"Fix Build"), tr(
"A process is already running. Please wait for it to finish."));
5378 QMessageBox::warning(
this, tr(
"Fix Build"), tr(
"No shader library is loaded."));
5387 const QString dialogTitle = prune ? tr(
"Remove Broken Shaders") : (fix ? tr(
"Fix Build") : tr(
"Build ACMXVK Library"));
5389 if (!is_acmxvk_source_library(build_path, type_error)) {
5391 QMessageBox::warning(
this,
5393 type_error.isEmpty() ? tr(
"The selected ACMXVK library is already a compiled "
5399 const QString manifest_path = QDir(build_path).filePath(QStringLiteral(
"library.json"));
5400 if (!QFileInfo(manifest_path).isFile()) {
5402 QMessageBox::warning(
this, dialogTitle, tr(
"ACMXVK source builds require library.json:\n%1").arg(manifest_path));
5406 const QString output_path = acmxvk_build_directory(build_path);
5407 QString compilerError;
5408 const QString compiler = resolve_acmxvk_shader_compiler(compilerError);
5409 if (compiler.isEmpty()) {
5411 Log(tr(
"<b style='color:red;'>Cannot build ACMXVK library: %1</b>").arg(compilerError.toHtmlEscaped()));
5412 QMessageBox::warning(
this, tr(
"ACMXVK Shader Compiler"), compilerError);
5415 QStringList arguments{
"--unbuffered",
"--build", manifest_path};
5416 arguments << (fix ? QStringLiteral(
"--fix") : QStringLiteral(
"--builddir")) << output_path;
5417 arguments << QStringLiteral(
"--glslc") << compiler;
5418 QSettings settings(
"LostSideDead");
5420 if (parallelBuildEnabled) {
5422 arguments << QStringLiteral(
"--parallel") << QString::number(parallelBuildJobs);
5425 arguments << QStringLiteral(
"--prune") << QStringLiteral(
"--force");
5426 Log(prune ? tr(
"Removing broken ACMXVK shader sources from: %1").arg(build_path) : (fix ? tr(
"Fix building ACMXVK SPIR-V library: %1").arg(build_path) : tr(
"Building ACMXVK SPIR-V library: %1").arg(build_path)));
5432 if (!
process->waitForStarted()) {
5433 Log(
"<b style='color:red;'>Error:</b> Failed to start ACMXVK build process");
5450 if (scan_path.isEmpty()) {
5451 QSettings appSettings(
"LostSideDead");
5454 if (scan_path.isEmpty()) {
5455 QMessageBox::warning(
this,
"Error",
"No shader library loaded. Please set a shader directory in Properties or load a shader library first.");
5458 if (
process->state() == QProcess::Running) {
5459 QMessageBox::warning(
this,
"Error",
"A process is already running. Please wait for it to finish.");
5464 if (manifestPath.isEmpty()) {
5465 QMessageBox::warning(
this,
"Missing Shader Manifest",
"No library.json or index.txt found in: " + scan_path);
5468 const QString manifestName = QFileInfo(manifestPath).fileName();
5472 if (!is_acmxvk_source_library(scan_path, typeError)) {
5473 QMessageBox::warning(
this,
5474 tr(
"Remove Broken Shaders"),
5475 typeError.isEmpty() ? tr(
"Remove Broken requires an ACMXVK source library, "
5476 "not a compiled runtime library.")
5481 QMessageBox confirmation(
this);
5482 confirmation.setIcon(QMessageBox::Warning);
5483 confirmation.setWindowTitle(tr(
"Permanently Remove Broken Shaders"));
5484 confirmation.setText(tr(
"This operation permanently deletes source shader files."));
5485 confirmation.setInformativeText(tr(
"ACMXVK will compile every shader listed in:\n\n%1\n\n"
5486 "Any .frag or .comp source for which glslc reports a compilation "
5487 "failure will be deleted. The generated runtime library and the "
5488 "source library manifest will then omit those shaders.\n\n"
5489 "No backup is created and this operation cannot be undone. "
5490 "Commit or archive the library before continuing.\n\n"
5491 "Do you want to permanently remove the broken sources?")
5493 confirmation.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
5494 confirmation.setDefaultButton(QMessageBox::No);
5495 confirmation.setEscapeButton(QMessageBox::No);
5496 if (confirmation.exec() != QMessageBox::Yes)
5503 QMessageBox::StandardButton reply = QMessageBox::question(
this,
5504 tr(
"Remove Broken Shaders"),
5505 tr(
"This will compile every shader in:\n\n%1\n\n"
5506 "Any shader that fails to compile will be removed from %2 "
5507 "(the original will be backed up as %2.bak).\n\nContinue?")
5508 .arg(scan_path, manifestName),
5509 QMessageBox::Yes | QMessageBox::No);
5510 if (reply != QMessageBox::Yes)
5513 const QString assets_path = resolveAssetsPath();
5516 args <<
"--remove-broken" << scan_path;
5517 args <<
"-p" << assets_path;
5520 args <<
"--texture-cache-array";
5522 args <<
"--enable-3d";
5524 Log(
"Scanning for broken shaders in: " + scan_path);
5529 QProcess *scan =
new QProcess(
this);
5530 scan->setProcessChannelMode(QProcess::SeparateChannels);
5531 connect(scan, &QProcess::readyReadStandardOutput,
this, [
this, scan]() {
5532 QString output = scan->readAllStandardOutput();
5533 output.replace(
"\n",
"<br>");
5534 this->
Write(output);
5536 connect(scan, &QProcess::readyReadStandardError,
this, [
this, scan]() {
5537 QString output = scan->readAllStandardError();
5538 output.replace(
"\n",
"<br>");
5539 this->
Write(
"<b style='color:red;'>" + output +
"</b>");
5541 connect(scan,
static_cast<void (QProcess::*)(
int, QProcess::ExitStatus)
>(&QProcess::finished),
this, [
this, scan, scan_path, manifestName](
int exitCode, QProcess::ExitStatus) {
5542 Log(QString(
"Remove-broken finished with exit code: %1<br>").arg(exitCode));
5543 if (exitCode == 0) {
5546 QMessageBox::information(
this,
5547 tr(
"Remove Broken"),
5548 tr(
"Finished scanning shader library.\n\n"
5549 "%1 has been updated and the shader list reloaded.\n"
5550 "A backup of the original is at:\n%2/%1.bak")
5551 .arg(manifestName, scan_path));
5553 QMessageBox::warning(
this,
5554 tr(
"Remove Broken"),
5555 tr(
"Remove-broken failed with exit code %1. "
5556 "%2 was not changed.")
5558 .arg(manifestName));
5560 scan->deleteLater();
5564 if (!scan->waitForStarted()) {
5565 Log(
"<b style='color:red;'>Error:</b> Failed to start remove-broken process");
5566 scan->deleteLater();
5572 Log(
"Clean Shader Cache is not available on macOS.");
5576 if (libraryPath.isEmpty()) {
5577 QSettings appSettings(
"LostSideDead");
5578 libraryPath = appSettings.value(
"shaders",
"").toString();
5581 if (libraryPath.isEmpty()) {
5582 QMessageBox::warning(
this,
"Error",
"No shader library loaded. Please set a shader directory in Properties or load a shader library first.");
5586 QMessageBox::warning(
this,
"Error",
"A process is running. Stop it before cleaning the shader cache.");
5590 const QMessageBox::StandardButton reply = QMessageBox::question(
this,
5591 tr(
"Clean Shader Cache"),
5592 tr(
"Delete all cached shader binaries for:\n\n%1\n\n"
5593 "This will not rebuild the cache. Continue?")
5595 QMessageBox::Yes | QMessageBox::No);
5596 if (reply != QMessageBox::Yes)
5599 const QString assetsPath = resolveAssetsPath();
5600 QStringList cacheFiles;
5601 const auto addCacheFile = [&cacheFiles](
const QString &path) {
5602 if (!cacheFiles.contains(path))
5603 cacheFiles.append(path);
5609 for (
int size = 1; size <= 64; ++size) {
5610 for (
const bool useArray : {
false,
true}) {
5611 const QString filename = shaderCacheFilename(libraryPath, size, useArray);
5612 addCacheFile(assetsPath +
"/" + filename);
5613 addCacheFile(libraryPath +
"/" + filename);
5619 const std::filesystem::path libraryFsPath(libraryPath.toStdString());
5620 const std::filesystem::path absoluteLibrary = std::filesystem::absolute(libraryFsPath, ec);
5621 const std::string legacyKey = ec ? libraryPath.toStdString() : absoluteLibrary.lexically_normal().string();
5622 std::ostringstream legacyName;
5623 legacyName <<
".shader_cache_" << std::hex << std::hash<std::string>{}(legacyKey);
5624 const QString legacyHashedName = QString::fromStdString(legacyName.str());
5625 addCacheFile(assetsPath +
"/" + legacyHashedName);
5626 addCacheFile(libraryPath +
"/" + legacyHashedName);
5627 addCacheFile(libraryPath +
"/.shader_cache");
5629 int removedCount = 0;
5630 int failedCount = 0;
5631 for (
const QString &cacheFile : cacheFiles) {
5632 if (!QFileInfo::exists(cacheFile))
5634 if (QFile::remove(cacheFile)) {
5635 Log(
"Deleted shader cache: " + cacheFile);
5638 Log(
"<b style='color:red;'>Warning:</b> Could not delete cache file: " + cacheFile);
5643 if (removedCount == 0 && failedCount == 0) {
5644 Log(
"No existing shader cache found");
5646 Log(QString(
"Shader cache clean complete: removed %1 file(s), %2 failed").arg(removedCount).arg(failedCount));
5656 probe.start(exe, QStringList() << flag);
5657 if (!probe.waitForFinished(5000)) {
5661 return QString::fromLocal8Bit(probe.readAllStandardOutput()).trimmed();
5670 cuda_available = cudaOutput.contains(isAcmxvk ?
"acidcam-gpu filters: enabled" :
"CUDA: enabled", Qt::CaseInsensitive);
5678 Log(QString(
"CUDA filters: %1 (%2)").arg(
cuda_available ?
"enabled" :
"disabled", backendName));
5682 Log(QString(
"AUDIO: %1 (%2)").arg(
audio_available ?
"enabled" :
"disabled", backendName));
5683 Log(QString(
"MIDI: %1 (%2)").arg(
midi_available ?
"enabled" :
"disabled", backendName));
5684 Log(QString(
"OpenCV DNN: %1 (%2)").arg(
dnn_available ?
"enabled" :
"disabled", backendName));
5708 : tr(
"Disabled: ACMXVK was built without Stable Diffusion "
5716 gpuFilterAction->setToolTip(
cuda_available ? QString() : tr(
"Disabled: %1 was built without acidcam-gpu filter support.").arg(backendName));
5727 audioSet->setToolTip(
audio_available ? QString() : tr(
"Disabled: %1 was built without audio support.").arg(backendName));
static QString probe_feature_output(const QString &exe, const QString &flag)
static bool probeFeature(const QString &exe, const QString &flag, const QString &token)
Main launcher window for ACMX2/ACMXVK shader selection and execution.
Qt6 dialog for configuring audio reactivity settings.
Dialog for configuring live and file-based audio reactivity options.
QString getAudioFilePath() const
Get the effective audio file or M3U playlist path.
int getNumberOfChannels() const
double getSensitivity() const
int getAudioBufferFrames() const
Number of spectrum history frames requested.
bool isAudioFileEnabled() const
Check whether a file or M3U audio source is selected and has a path.
int getInputDeviceIndex() const
bool isAudioReactivityEnabled() const
bool isRecordAudioEnabled() const
bool isAudioPassThroughEnabled() const
bool isAudioRepeatEnabled() const
Check whether file audio should restart when it reaches the end.
double getRecordVolume() const
int getOutputDeviceIndex() const
double getAudioWarmRate() const
Audio startup warmup rate in 1/sec.
bool isAudioTruncEnabled() const
Check whether "stop when audio ends" is selected.
bool isAudioBuffersEnabled() const
Check whether spectrum history buffers are enabled.
DeepDreamConfiguration configuration() const
DeepDreamSettingsDialog(bool gpu_filter_enabled, QWidget *parent=nullptr)
void resultActivated(const QString &filePath, int lineNumber, int columnNumber, int matchLength)
Emitted when the user opens one search result.
FindShaderDialog(const QString &shaderPath, QWidget *parent=nullptr)
Dialog that manages optional GPU filter list and buffer settings.
bool isGPUFilterEnabled() const
Return whether GPU filtering is enabled.
QString getFilterArgument() const
Build CLI argument payload for the selected filter state.
int getBufferSize() const
Return frame-buffer depth used by filter chain.
GPUFilterDialog(const QString &executablePath, QWidget *parent=nullptr)
void settingsApplied(bool enabled, const QString &filterArgument, int bufferSize)
Emitted when settings should be applied without closing the dialog.
void libraryExported(const QString &directory)
Emitted after a complete library has been exported successfully.
LibraryBuilderDialog(acmx2::Backend backend, QWidget *parent=nullptr)
Shader-library utility dialog.
QString getShaderPath()
Return folder chosen for shader-library indexing.
void openCustomStyleEditor()
QString editorPreviewStderr
bool stable_diffusion_upscale_only
QVector< QPointer< TextEditor > > open_files
QString playlist_file_path
QString editorPreviewPath
void prompt_acmxvk_rebuild(const QString &reason, PendingAcmxvkAction resume_action)
Offer to rebuild a stale or incomplete ACMXVK source library.
double stable_diffusion_cfg_scale
void menuWatermarkSettings()
void detectFeatureSupport()
void listClicked(const QModelIndex &i)
Handle shader list selection changes.
QPointer< UniformReferenceDialog > uniformReferenceDialog
double deep_dream_strength
QProcess * editorPreviewProcess
QString currentShaderName() const
Return the filename in the Name column for the current selection.
void applyCustomStyleSheet(bool enable)
Apply or remove the custom stylesheet override.
QString stable_diffusion_upscale_model
void publishRuntimeSettingsToRunningProcess()
void menuToggleDisplayFilter(bool checked)
QAction * normalizedTimeAction
QAction * listMenu_findNext
void publishMultipassShadersToRunningProcess()
QString stable_diffusion_sampler
quint64 liveShaderCompileSequence
double deep_dream_octave_scale
QString editorPreviewStdout
void menuMetadataViewer()
void handleSavedShader(const QString &filePath)
QActionGroup * backendActionGroup
QString gpu_filter_indices
void update_backend_ui()
Update title, actions, and status text for the active backend.
QString stable_diffusion_server_arguments
double deep_dream_rotation
QAction * runFromCacheAction
QString stable_diffusion_prompt
QAction * stableDiffusionAction
QAction * shaderPassAction
void menuGPUFilterSettings()
bool stable_diffusion_upscale
QAction * watermarkAction
PendingAcmxvkAction pending_acmxvk_action
QAction * libraryBuilderAction
QHash< QString, bool > shaderCacheStatus
Cached map of shader stem -> failed flag for the current library.
void menuUniformReference()
bool audio_buffers_enabled
QAction * removeBrokenAction
void openShaderEditor(const QString &filePath, int lineNumber=1, int columnNumber=0, int matchLength=0)
Open or focus an editor for a shader source location.
void set_backend(acmx2::Backend backend, bool persist=true)
Select the active rendering backend and restore its paths.
void cleanupClosedEditors()
QPointer< PlaylistDialog > playlistDialog
bool deep_dream_gpu_filter_first
QString liveShaderCompileSource
void updateIndex()
Refresh shader index metadata timestamp.
bool stable_diffusion_enabled
int stable_diffusion_seed
void initShaderSelectionSharedMemory()
void queueAcmxvkLiveCompile(const QString &filePath)
int stable_diffusion_server_port
QAction * listMenu_shuffle
int stable_diffusion_height
QAction * fileMenu_loadLibrary
QAction * listMenu_search
void populateShaderTree()
Repopulate the shader tree widget from the current items list, recomputing Last Modified,...
QAction * deepDreamAction
QAction * runMenu_copyCommand
double deep_dream_feedback
void startNextAcmxvkEditorPreview()
bool encode_fill_pts_gaps
void menuLibraryBuilder()
QProcess * liveShaderCompileProcess
void updateOpenEditorShaderContexts()
void updateOpenEditorCompileStatus(const QString &sourcePath, bool pending, bool success=false, const QString &diagnostics=QString())
QString sanitizeShaderName(const QString &name)
Normalize shader names for filesystem/process safety.
QList< double > stable_diffusion_lora_multipliers
int currentShaderRow() const
Return the row index of the current selection, or -1.
QPointer< QDialog > shaderEditorWorkspace
void applyMainViewStyles(bool customStyleEnabled)
void publishRepeatStateToRunningProcess()
QAction * stayOnTopAction
QString stable_diffusion_server
QAction * styleSheetAction
void appendDeepDreamArguments(QStringList &arguments) const
unsigned int camera_index
bool duration_limit_enabled
void menuShaderPassSettings()
bool validateDeepDreamLaunch(QString &error) const
QString encode_rate_control
QStringList playlist_names
QStringList stable_diffusion_lora_files
CustomUniformDialog * customUniformDialog
QString activeShaderManifestPath
bool validateStableDiffusionLaunch(QString &error) const
QAction * helpMenu_uniformReference
quint64 editorPreviewSequence
void publishAcmxvkCompiledShaderReload(const QString &sourcePath, const QString &runtimePath)
QAction * backendAcmx2Action
QString pendingEditorPreviewPath
QAction * backendAcmxvkAction
QAction * listMenu_shader
QAction * customUniformsAction
void Write(const QString &message)
Write raw text to the lower output pane.
void publishSelectedShaderIndexToRunningProcess()
void menuCleanShaderCache()
QString liveShaderCompileStderr
QString editorPreviewInput
bool cacheBuildInProgress
True while an ACMX2 cache rebuild or ACMXVK source build is running.
QStringList shader_pass_names
void Log(const QString &message)
Append timestamped text to the UI log output.
void refreshShaderCacheStatus()
Refresh shaderCacheStatus from the on-disk shader cache.
QString png_output_directory
QList< QPair< QString, QStringList > > playlist_tree_data
unsigned int audio_channels
QPointer< DeepDreamSettingsDialog > deepDreamSettingsDialog
void cleanupShaderSelectionSharedMemory()
QString liveShaderCompileTemporary
void publishShaderReloadToRunningProcess(const QString &filePath)
double stable_diffusion_strength
void publishCustomUniformsToRunningProcess()
void ensureShaderEditorWorkspace()
QTextEdit * bottomTextBox
void loadSessionSettings()
Restore persisted Session Settings into the launcher's runtime state.
QString readFileContents(const QString &filePath)
Read an entire text file into memory.
acmx2::Backend active_backend
QString getShaderPassIndicesFromNames()
Map selected shader-pass names back to numeric indices.
QString stable_diffusion_scheduler
QString stable_diffusion_model
QString liveShaderCompileOutput
QString encode_parameters
int deep_dream_iterations
void appendStableDiffusionArguments(QStringList &arguments) const
void menuCustomUniforms()
bool publishAcmx2EditorPreview(const QString &filePath, const QString &source)
QString acmxvkPruneLibraryPath
void selectShaderRow(int row)
Select the row at row and scroll it into view.
QString liveShaderCompileInput
QAction * listMenu_set_current
void appendOutputRunLog(const QString &message)
bool encode_constant_frame_rate
QPointer< GPUFilterDialog > gpuFilterDialog
QString liveShaderCompileStdout
QPointer< ShaderPassDialog > shaderPassDialog
QAction * gpuFilterAction
void addRecentLibrary(const QString &path)
Add a library directory to the persisted recent-libraries list.
QTabWidget * shaderEditorTabs
int stable_diffusion_steps
int stable_diffusion_upscale_working_height
QAction * displayFilterAction
void updateRecentLibrariesMenu()
Rebuild the File > Load Recent submenu from persisted settings.
void finishOutputRunLog(int exitCode, QProcess::ExitStatus exitStatus)
QAction * listMenu_findInFiles
void cleanupShaderSelectionSemaphore()
void menuPlaylistSettings()
bool display_filter_enabled
QString baseAppStyleSheet
QPointer< StableDiffusionSettingsDialog > stableDiffusionSettingsDialog
void startNextAcmxvkLiveCompile()
QAction * listMenu_remove
float cross_fade_duration
bool stable_diffusion_available
void start_acmxvk_build(const QString &build_path, AcmxvkBuildMode mode)
Start a strict, failure-tolerant, or destructive ACMXVK build.
QString pendingEditorPreviewSource
QStringList liveShaderCompileQueue
bool deep_dream_available
int stable_diffusion_upscale_working_width
bool backend_launch_available() const
Return whether the active backend can be launched.
void menuStableDiffusionSettings()
void menuDeepDreamSettings()
bool cuda_device_available
QString stable_diffusion_negative_prompt
void initControls()
Build menus, actions, widgets, and signal wiring.
bool loadShaders(const QString &path, bool force=false)
Load shader names from index/cache for the provided path.
void menuBuildShaderCache()
QStringList editorPreviewTemporaryFiles
bool buildRunArguments(QStringList &arguments, PendingAcmxvkAction resume_action=PendingAcmxvkAction::None, bool include_extra_arguments=true)
Build acmx2 command-line arguments from current UI state.
QPointer< LibraryBuilderDialog > libraryBuilderDialog
QString editorPreviewOutput
void queueAcmxvkEditorPreview(const QString &filePath, const QString &source)
bool max_size_limit_enabled
int stable_diffusion_width
bool loadLibraryPath(const QString &path)
Validate, load, persist, and remember a shader library directory.
void runHdr10Conversion()
Run ffmpeg to convert the just-produced acmx2 output (assumed HLG HDR) into HDR10 (HEVC NVENC,...
int deep_dream_maximum_dimension
void menuSetCurrentShader()
void beginOutputRunLog(const QString &command)
QAction * cleanShaderCacheAction
QAction * midiSettingsAction
QDateTime shaderCacheMTime
Modification time of the shader cache file when last read.
QString concatList(const QStringList lst)
Join list items into a comma-separated argument string.
QAction * buildCacheAction
MIDI settings dialog used by the main launcher UI.
int getMidiDeviceIndex() const
Return selected MIDI input device index.
QString getMidiConfigFile() const
Return path to the active MIDI mapping config file.
bool isMidiEnabled() const
Return whether MIDI support is enabled.
Playlist editor dialog with tree nodes and ordered shader entries.
int getAutopilotFrames() const
Return frames-per-shader threshold for autopilot mode (minimum 4).
PlaylistDialog(const QStringList &shaderNames, acmx2::Backend backend, QWidget *parent=nullptr)
QStringList getSelectedShaderNames() const
Return flattened selection of shaders in current playlist.
bool isPlaylistEnabled() const
Return whether playlist mode is enabled.
QString getPlaylistFile() const
Return current playlist file path.
bool isAutopilotRandom() const
Return true when random autopilot timeout mode is enabled.
QList< QPair< QString, QStringList > > getPlaylistTree() const
Return tree representation as node-name and shader-list pairs.
Launcher properties dialog for executable/shader/screenshot paths.
QLineEdit * shaderCompilerPathLineEdit
QLineEdit * shaderDirLineEdit
QComboBox * shaderCompilerComboBox
QLineEdit * exePathLineEdit
QLineEdit * screenshotDirLineEdit
Dialog that collects camera, input source, output, and runtime options.
bool isSavingOutputLog() const
bool isGenerateEnabled() const
bool isDurationLimitEnabled() const
Return whether maximum duration limiting is enabled.
bool isCopyAudioEnabled() const
int getSelectedCameraIndex() const
bool isSavingToOutputVideoFile() const
bool isEncodeNoDrop() const
QSize getSelectedScreenResolution() const
QString getEncodeRateControl() const
bool is_rotate_enabled() const
Return whether input-frame rotation is enabled.
QString getEncodeParameters() const
bool isTextureCacheEnabled() const
bool isEncodeConstantFrameRate() const
float getTimeSpeed() const
double getMaxSizeLimit() const
Return configured maximum output size in MB.
bool isPngOutputEnabled() const
Return whether Write PNG mode is enabled.
QString getModelFile() const
QString getOutputVideoFile() const
QString getPngOutputDirectory() const
Return the directory used for ACMXVK PNG-sequence output.
QString getGraphicsFile() const
bool isUsingGraphicsFile() const
bool isEncodeFillPtsGaps() const
bool isConvertToHdr10Enabled() const
bool isUseSourceFpsEnabled() const
QString getEncodeBitrate() const
QSize getSelectedCameraResolution() const
QString getInputVideoFile() const
int getPngLevel() const
Return the selected ACMXVK PNG compression level.
bool isUseSourceAudioEnabled() const
bool isMaxSizeLimitEnabled() const
Return whether maximum output-size limiting is enabled.
QString get_rotation_mode() const
Return the selected rotation token for –rotate.
bool isOnnxModelEnabled() const
double getDurationLimit() const
Return configured max run duration in seconds.
bool isEncodeRealtime() const
bool isMaximizeFpsEnabled() const
float getCrossFadeDuration() const
Return crossfade duration between shader transitions.
void setCudaAvailable(bool available)
Show or hide CUDA-specific controls based on availability.
QString getEncodeCodec() const
void setDnnAvailable(bool available)
Enable or disable ONNX controls based on OpenCV DNN support.
bool isFlipEnabled() const
Return whether flip mode is enabled.
int getSelectedCudaDevice() const
int getGenerateInterval() const
QString getOnnxModelFile() const
bool isUseYuvEnabled() const
int getCacheDelay() const
QString getEncodeTune() const
QString getEncodePreset() const
bool isFullscreen() const
bool isUsingInputVideoFile() const
New-shader dialog with optional starter template content.
void setShaderPath(const QString &path)
Set output directory used for generated shader files.
UI for selecting and ordering shader passes.
void shaderEditRequested(const QString &shaderName)
Request that a shader in the pass list be opened for editing.
QStringList getSelectedShaderNames() const
Return selected shader names in execution order.
bool isShaderPassEnabled() const
Return whether shader-pass mode is enabled.
void settingsApplied(bool enabled, const QStringList &selectedShaderNames)
ShaderPassDialog(const QStringList &shaderNames, QWidget *parent=nullptr)
StableDiffusionConfiguration configuration() const
StableDiffusionSettingsDialog(QWidget *parent=nullptr)
Modal shader editor dialog used by ACMX2.
TextEditor(QWidget *parent=nullptr)
void fileSaved(const QString &path)
Emitted after the editor successfully writes its contents to disk.
void previewRequested(const QString &path, const QString &source)
void revealLocation(int lineNumber, int columnNumber=0, int matchLength=0)
Move to a one-based line and select the requested source match.
void uniformValueChanged(const QString &name, double value)
void openFileRequested(const QString &path, int lineNumber)
void setFileName(const QString &filename)
Associate editor with a shader file path.
QString fileName() const
Return the file currently associated with this editor.
void setText(const QString &text)
Replace editor contents and reset displayed text.
QString inject_safety_counters(const QString &glsl_source)
Add bounded iteration guards and preserve original GLSL diagnostic lines.
Regular-expression search dialog for shader source libraries.
Dialog for assembling portable shader libraries.
constexpr std::uint32_t kShaderSelectionMaxDreamLayer
constexpr std::uint32_t kShaderSelectionMagic
constexpr std::uint32_t kShaderSelectionMaxShaderName
constexpr std::uint32_t kShaderSelectionMaxPassCount
constexpr std::uint32_t kShaderSelectionMaxUniformName
constexpr std::uint32_t kShaderSelectionVersion
constexpr std::uint32_t kShaderSelectionMaxWatermarkText
constexpr std::uint32_t kShaderSelectionMaxReloadPath
constexpr const char * kShaderSelectionShmName
constexpr const char * kShaderSelectionSemaphoreName
constexpr std::uint32_t kShaderSelectionMaxGpuFilterCount
constexpr std::uint32_t kShaderSelectionMaxCustomUniforms
constexpr std::uint32_t kShaderSelectionMaxDreamModelPath
constexpr std::uint32_t kShaderSelectionMaxAudioFilePath
QString shader_manifest_path(const QString &directory)
Resolve library.json first, then fall back to index.txt.
bool custom_uniform_metadata_matches(const QString &leftDirectory, const QString &rightDirectory, bool &matches, QString &error)
Compare two manifests' custom-uniform ABI and numeric metadata.
QDateTime shader_manifest_last_modified(const QString &directory)
Return the selected manifest's modification time.
bool load_custom_uniforms(const QString &directory, QList< CustomUniformDefinition > &uniforms, QString &error)
Load custom float-uniform controls from library.json.
std::optional< ShaderLibraryType > shader_manifest_library_type(const QString &directory, QString &error)
Read and validate an optional top-level library_type value.
bool remove_shader_manifest_entry(const QString &directory, const QString &shader, QString &error)
Remove one shader from the preferred manifest, without deleting its file.
QString default_backend_executable(Backend backend)
bool isCustomStyleEnabled()
QString backend_settings_key(Backend backend, const QString &setting)
void applyCustomStyleIfEnabled(QWidget *widget)
bool shader_manifest_exists(const QString &directory)
Return true when either supported manifest exists.
bool load_shader_manifest(const QString &directory, QStringList &shaders, QString &error)
Load shader filenames from the preferred manifest in a directory.
std::optional< Backend > backend_from_id(const QString &value)
QString backend_id(Backend backend)
bool migrate_index_manifest_to_json(const QString &directory, bool &created, QString &error)
Create library.json from index.txt when a JSON manifest is absent.
QString backend_name(Backend backend)
bool write_shader_manifest(const QString &directory, const QStringList &shaders, QString &error)
Rewrite the preferred manifest, preserving JSON fields when possible.
QString defaultCustomStyleSheet()
QString buildStyleSheet(const CustomStylePalette &p)
std::optional< Backend > shader_manifest_backend(const QString &directory, QString &error)
Read an optional top-level backend hint from library.json.
QString resolveAssetsPath()
QString acmxvk_build_directory(const QString &sourceLibrary)
bool acmxvk_runtime_manifest_matches(const QString &source_library, const QString &runtime_library, QString &error)
AcmxvkBuildState acmxvk_shader_build_state(const QString &source_library, const QString &source_name)
bool resolve_acmxvk_runtime_library(const QString &selectedLibrary, QString &runtimeLibrary, QString &error)
QString default_stable_diffusion_server()
QString shaderCacheFilename(const QString &libraryPath, int cacheSize, bool useArray)
QString acmxvk_runtime_shader_name(const QString &sourceName)
QSize storedResolution(QSettings &settings, const QString &key, const QSize &fallback, bool defaultIsEmpty)
bool is_stable_diffusion_diagnostic(const QString &line)
bool hasPositiveResolution(const QSize &resolution)
bool take_process_output_line(QString &buffer, QString &line)
QString formatLastModified(const QDateTime &dt)
constexpr int RECENT_LIBRARY_LIMIT
QString buildShellCommand(const QStringList &envAssignments, const QString &program, const QStringList &arguments)
QString resolve_acmxvk_shader_compiler(QString &error)
QString resolve_backend_assets_path(acmx2::Backend backend, const QString &executable, const QString &libraryPath)
bool is_acmxvk_source_library(const QString &libraryPath, QString &error)
QString resolveShaderCachePath(const QString &libraryPath, int cacheSize, bool useArray)
QString shellQuote(const QString &value)
void replace_file(const std::filesystem::path &source, const std::filesystem::path &destination, std::error_code &error)
bool textureCacheArraySettingEnabled()
QHash< QString, bool > parseShaderCacheStatus(const QString &cachePath)
Main capture/playback settings dialog for ACMX2 execution.
Optional JSON and legacy text shader-library manifests.
QList< double > lora_multipliers
int upscale_working_width
int upscale_working_height
QString upscale_model_file
QString server_executable