12#include <QApplication>
15#include <QColorDialog>
21#include <QDialogButtonBox>
28#include <QGuiApplication>
32#include <QInputDialog>
38#include <QPlainTextEdit>
41#include <QRegularExpression>
44#include <QStandardPaths>
48#include <QTreeWidgetItem>
63#if defined(__linux__) || defined(__APPLE__)
76 if (value.isEmpty()) {
77 return QStringLiteral(
"\"\"");
80 QString quoted = QStringLiteral(
"\"");
81 qsizetype backslash_count = 0;
82 for (
const QChar character : value) {
83 if (character == QLatin1Char(
'\\')) {
87 if (character == QLatin1Char(
'"')) {
88 quoted += QString(backslash_count * 2 + 1, QLatin1Char(
'\\'));
93 quoted += QString(backslash_count, QLatin1Char(
'\\'));
97 quoted += QString(backslash_count * 2, QLatin1Char(
'\\'));
98 quoted += QLatin1Char(
'"');
101 if (value.isEmpty()) {
105 out.replace(
"'",
"'\\''");
106 return "'" + out +
"'";
111 const QStringList &arguments) {
113 parts.reserve(envAssignments.size() + 1 + arguments.size());
114 for (
const QString &entry : envAssignments) {
115 int eq = entry.indexOf(
'=');
119 QString key = entry.left(eq);
120 QString value = entry.mid(eq + 1);
122 parts << (QStringLiteral(
"set \"") + key + QLatin1Char(
'=') +
123 value + QStringLiteral(
"\" &&"));
129 for (
const QString &arg : arguments) {
132 return parts.join(
' ');
136 const std::filesystem::path &destination,
137 std::error_code &error) {
139 if (MoveFileExW(source.c_str(), destination.c_str(),
140 MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) !=
145 error = std::error_code(
static_cast<int>(GetLastError()),
146 std::system_category());
148 std::filesystem::rename(source, destination, error);
153 QStringList defaultLinuxRunEnvAssignments() {
154 QStringList envAssignments;
155 QString uid = QString::number(getuid());
156 QString userRunPath =
"/run/user/" + uid;
160 QByteArray display = qgetenv(
"DISPLAY");
161 QByteArray waylandDisplay = qgetenv(
"WAYLAND_DISPLAY");
162 QByteArray sessionType = qgetenv(
"XDG_SESSION_TYPE");
163 if (!display.isEmpty()) {
164 envAssignments <<
"SDL_VIDEODRIVER=x11";
165 }
else if (!waylandDisplay.isEmpty() || sessionType ==
"wayland") {
166 envAssignments <<
"SDL_VIDEODRIVER=wayland";
168 if (QDir(userRunPath).exists()) {
169 envAssignments << (
"XDG_RUNTIME_DIR=" + userRunPath);
170 envAssignments << (
"PULSE_SERVER=unix:" + userRunPath +
"/pulse/native");
172 envAssignments <<
"vblank_mode=0";
173 return envAssignments;
178 QString dirPath = QCoreApplication::applicationDirPath();
180 return dirPath +
"/../Helpers";
182 if (QFileInfo::exists(dirPath +
"/data/win-icon.png"))
184 const QString installedPath = QDir::cleanPath(dirPath +
"/../share/acmx2");
185 if (QFileInfo::exists(installedPath +
"/data/win-icon.png"))
186 return installedPath;
193 QSettings settings(
"LostSideDead");
198 "shader_compiler_mode"),
201 if (mode == QStringLiteral(
"custom")) {
206 "shader_compiler_path"))
209 if (compiler.isEmpty()) {
210 error = QStringLiteral(
211 "The custom ACMXVK shader compiler path is empty. Select "
212 "one in Properties (Ctrl+,).");
215 if (QFileInfo(compiler).isRelative()) {
216 const QString resolved =
217 QStandardPaths::findExecutable(compiler);
218 if (!resolved.isEmpty())
221 const QFileInfo compilerInfo(compiler);
222 if (!compilerInfo.isFile() || !compilerInfo.isExecutable()) {
223 error = QStringLiteral(
224 "The configured ACMXVK shader compiler is not an "
225 "executable file: %1")
229 return compilerInfo.absoluteFilePath();
234 const QString compilerName = QStringLiteral(
"glslc.exe");
235 const QFileInfo bundledCompiler(
236 QDir(QCoreApplication::applicationDirPath())
237 .filePath(compilerName));
238 if (bundledCompiler.isFile() && bundledCompiler.isExecutable())
239 compiler = bundledCompiler.absoluteFilePath();
241 const QString compilerName = QStringLiteral(
"glslc");
243 if (compiler.isEmpty())
244 compiler = QStandardPaths::findExecutable(compilerName);
245 if (compiler.isEmpty()) {
247 QString::fromLocal8Bit(qgetenv(
"VULKAN_SDK"));
248 const QString sdkCompiler =
249 QDir(sdk).filePath(QStringLiteral(
"bin/") + compilerName);
250 if (!sdk.isEmpty() && QFileInfo(sdkCompiler).isExecutable())
251 compiler = sdkCompiler;
253 if (compiler.isEmpty()) {
254 error = QStringLiteral(
255 "glslc was not found in PATH or VULKAN_SDK. Select a custom "
256 "compiler in Properties (Ctrl+,).");
262 const QString &executable,
263 const QString &libraryPath) {
267 const QString applicationDir = QCoreApplication::applicationDirPath();
269 const QString bundleResources =
270 QDir::cleanPath(applicationDir +
"/../Resources/acmxvk");
271 if (QFileInfo(bundleResources).isDir())
272 return bundleResources;
274 QStringList candidates;
275 QString resolvedExecutable = executable;
276 if (QFileInfo(resolvedExecutable).isRelative()) {
277 const QString pathExecutable =
278 QStandardPaths::findExecutable(resolvedExecutable);
279 if (!pathExecutable.isEmpty())
280 resolvedExecutable = pathExecutable;
282 const QFileInfo executableInfo(resolvedExecutable);
283 if (!executableInfo.absolutePath().isEmpty()) {
284 candidates << QDir::cleanPath(executableInfo.absolutePath() +
287 if (!libraryPath.isEmpty())
288 candidates << QDir::cleanPath(QFileInfo(libraryPath).absolutePath());
289 candidates << QDir::cleanPath(applicationDir +
"/../share/acmxvk")
290 << QStringLiteral(
"/usr/local/share/acmxvk")
291 << QStringLiteral(
"/opt/homebrew/share/acmxvk")
292 << QStringLiteral(
"/usr/share/acmxvk");
293 for (
const QString &candidate : candidates) {
294 if (QFileInfo(candidate +
"/data").isDir())
300 return applicationDir;
305 const std::optional<acmx2::ShaderLibraryType> type =
307 if (!error.isEmpty())
317 return std::any_of(entries.cbegin(), entries.cend(), [](
const QString &entry) {
318 return entry.endsWith(
".frag", Qt::CaseInsensitive) ||
319 entry.endsWith(
".comp", Qt::CaseInsensitive);
324 return QDir(sourceLibrary).filePath(QStringLiteral(
".acmxvk-build"));
328 return sourceName.endsWith(
".spv", Qt::CaseInsensitive)
330 : sourceName + QStringLiteral(
".spv");
338 const QString &source_library,
const QString &source_name) {
339 const QString runtime_library =
344 const QFileInfo source_file(
345 QDir(source_library).filePath(source_name));
346 const QFileInfo runtime_file(
347 QDir(runtime_library)
349 if (!runtime_file.isFile())
351 if (source_file.isFile() &&
352 runtime_file.lastModified() < source_file.lastModified()) {
359 const QString &runtime_library,
361 QStringList source_entries;
362 QStringList runtime_entries;
370 QStringList expected_entries;
371 expected_entries.reserve(source_entries.size());
372 for (
const QString &entry : source_entries)
374 if (runtime_entries != expected_entries) {
376 "The ACMXVK runtime manifest does not match the source "
377 "shader list. Choose Playback > Build before running.");
381 bool uniformMetadataMatches =
false;
383 source_library, runtime_library, uniformMetadataMatches,
387 if (!uniformMetadataMatches) {
389 "The ACMXVK runtime custom-uniform metadata is out of date. "
390 "Choose Playback > Build before running.");
397 QString &runtimeLibrary,
400 runtimeLibrary = selectedLibrary;
402 return error.isEmpty();
407 "The ACMXVK source library has not been built yet. "
408 "Choose Playback > Build first.\n\nExpected output: %1")
409 .arg(runtimeLibrary);
412 const std::optional<acmx2::ShaderLibraryType> type =
414 if (!error.isEmpty())
417 error = QObject::tr(
"Compiled output is not an ACMXVK runtime library: %1")
418 .arg(runtimeLibrary);
421 QStringList sourceEntries;
428 for (
const QString &sourceEntry : sourceEntries) {
429 const QFileInfo sourceFile(
430 QDir(selectedLibrary).filePath(sourceEntry));
431 const QFileInfo runtimeFile(QDir(runtimeLibrary)
434 if (!runtimeFile.isFile() ||
435 runtimeFile.lastModified() < sourceFile.lastModified()) {
437 "The ACMXVK build is missing or older than %1. "
438 "Choose Playback > Build before running.")
447 QSettings settings(
"LostSideDead",
"acmx2");
448 return settings.value(
"interface/texture_cache_array",
false).toBool();
452 const QSize &fallback,
bool defaultIsEmpty) {
453 const QString text = settings.value(key).toString().trimmed();
454 if (text.compare(
"Default", Qt::CaseInsensitive) == 0) {
455 return defaultIsEmpty ? QSize(0, 0) : fallback;
458 static const QRegularExpression resolutionPattern(
459 R
"(^\s*(\d+)\s*[xX]\s*(\d+)\s*$)");
460 const QRegularExpressionMatch match = resolutionPattern.match(text);
461 if (!match.hasMatch()) {
465 const int width = match.captured(1).toInt();
466 const int height = match.captured(2).toInt();
467 return width > 0 && height > 0 ? QSize(width, height) : fallback;
471 return resolution.width() > 0 && resolution.height() > 0;
477 const std::filesystem::path libraryFsPath(libraryPath.toStdString());
478 const std::filesystem::path absoluteLibrary =
479 std::filesystem::absolute(libraryFsPath, ec);
480 std::string key = ec ? libraryPath.toStdString()
481 : absoluteLibrary.lexically_normal().string();
482 key +=
"|s=" + std::to_string(cacheSize);
483 key +=
"|a=" + std::to_string(useArray ? 1 : 0);
484 std::ostringstream nameStream;
485 nameStream <<
".shader_cache_" << std::hex
486 << std::hash<std::string>{}(key);
487 return QString::fromStdString(nameStream.str());
493 const QString filename =
499 const QString assetsCache = assets +
"/" + filename;
500 const QString libCache = libraryPath +
"/" + filename;
501 if (QFileInfo::exists(assetsCache))
503 if (QFileInfo::exists(libCache))
511 QHash<QString, bool> result;
513 if (!f.open(QIODevice::ReadOnly))
516 auto readU32 = [&](quint32 &v) ->
bool {
517 return f.read(
reinterpret_cast<char *
>(&v),
sizeof(v)) == qint64(
sizeof(v));
519 auto readU64 = [&](quint64 &v) ->
bool {
520 return f.read(
reinterpret_cast<char *
>(&v),
sizeof(v)) == qint64(
sizeof(v));
522 auto readU8 = [&](quint8 &v) ->
bool {
523 return f.read(
reinterpret_cast<char *
>(&v),
sizeof(v)) == qint64(
sizeof(v));
525 auto readStr = [&](QString &out) ->
bool {
529 QByteArray buf = f.read(len);
530 if (quint32(buf.size()) != len)
532 out = QString::fromUtf8(buf);
535 auto skipBytes = [&](quint32 n) ->
bool {
return f.skip(n) == qint64(n); };
537 constexpr quint32 CACHE_MAGIC = 0x53484452;
538 constexpr quint32 CACHE_VERSION = 4;
540 quint32 magic = 0, version = 0;
541 if (!readU32(magic) || !readU32(version))
543 if (magic != CACHE_MAGIC || version != CACHE_VERSION)
552 quint8 dual_mode = 0;
553 if (!readU8(dual_mode))
560 for (quint32 i = 0; i < count; ++i) {
564 quint8 shader_kind = 0;
565 if (!readU8(shader_kind) || shader_kind > 2)
567 quint8 failed_flag = 0;
568 if (!readU8(failed_flag))
570 quint64 source_hash = 0;
571 if (!readU64(source_hash))
573 quint32 fmt2d = 0, sz2d = 0, fmt3d = 0, sz3d = 0;
574 if (!readU32(fmt2d) || !readU32(sz2d) || !skipBytes(sz2d))
576 if (!readU32(fmt3d) || !readU32(sz3d) || !skipBytes(sz3d))
578 result.insert(name, failed_flag != 0);
585 return QStringLiteral(
"-");
586 return dt.toLocalTime().toString(QStringLiteral(
"yyyy-MM-dd HH:mm"));
594 auto updateShaderMenuState = [
this](QProcess::ProcessState state) {
595 const bool running = (state == QProcess::Running);
626 connect(
process, &QProcess::stateChanged,
this, updateShaderMenuState);
627 updateShaderMenuState(
process->state());
628 connect(
process, &QProcess::readyReadStandardOutput,
this, [
this]() {
629 QString output =
process->readAllStandardOutput();
630 output.replace(
"\n",
"<br>");
634 connect(
process, &QProcess::readyReadStandardError,
this, [
this]() {
635 auto writeStderrLine = [
this](
const QString &line) {
636 if (line.contains(
"GStreamer"))
638 if (line.contains(
"[ WARN:"))
639 this->
Write(
"<b style='color:#ccaa00;'>Warning:</b> " + line +
"<br>");
641 this->
Write(
"<b style='color:red;'>Error:</b> " + line +
"<br>");
649 writeStderrLine(line);
658 static_cast<void (QProcess::*)(
int, QProcess::ExitStatus)
>(&QProcess::finished),
660 [
this](
int exitCode, QProcess::ExitStatus exitStatus) {
669 QTextStream stream(&text);
671 <<
": Exited with Code: " << exitCode;
675 if (exitStatus == QProcess::CrashExit) {
677 <<
"engine crashed.";
678 Log(
"<b style='color:red;'>" +
680 " engine crashed.</b><br>");
691 const QString pruneLibraryPath =
697 Log(tr(
"ACMXVK build ready: %1")
700 Log(tr(
"<b style='color:red;'>ACMXVK build failed "
701 "with exit code %1.</b>")
707 if (!pruneLibraryPath.isEmpty() && exitCode == 0 &&
708 exitStatus == QProcess::NormalExit) {
709 QStringList sourceShaders;
710 QString manifestError;
712 pruneLibraryPath, sourceShaders,
714 Log(tr(
"<b style='color:red;'>Broken sources were "
715 "pruned, but the source manifest could not "
717 .arg(manifestError.toHtmlEscaped()));
718 QMessageBox::warning(
719 this, tr(
"Remove Broken Shaders"),
720 tr(
"Broken source files were deleted, but the "
721 "source manifest could not be updated.\n\n%1")
722 .arg(manifestError));
724 QStringList retainedShaders;
725 int removedCount = 0;
726 const QDir sourceDirectory(pruneLibraryPath);
727 for (
const QString &shader : sourceShaders) {
728 const QString suffix =
729 QFileInfo(shader).suffix().toLower();
730 const bool sourceEntry =
731 suffix == QStringLiteral(
"frag") ||
732 suffix == QStringLiteral(
"comp");
734 !QFileInfo(sourceDirectory.filePath(shader))
738 retainedShaders.append(shader);
742 if (removedCount > 0 &&
744 pruneLibraryPath, retainedShaders,
746 Log(tr(
"<b style='color:red;'>Broken sources "
747 "were pruned, but the source manifest "
748 "could not be updated: %1</b>")
749 .arg(manifestError.toHtmlEscaped()));
750 QMessageBox::warning(
751 this, tr(
"Remove Broken Shaders"),
752 tr(
"%1 source file(s) were permanently "
753 "deleted, but library.json could not be "
756 .arg(manifestError));
760 Log(tr(
"Remove Broken completed: %1 source "
761 "shader(s) permanently deleted.")
763 QMessageBox::information(
764 this, tr(
"Remove Broken Shaders"),
766 ? tr(
"Removed %1 broken source shader(s) "
767 "and updated library.json.\n\n"
768 "This deletion cannot be undone.")
770 : tr(
"The build completed and no broken "
771 "source shaders were found."));
777 exitStatus == QProcess::NormalExit &&
779 Log(tr(
"ACMXVK build succeeded; resuming the requested "
781 QTimer::singleShot(0,
this, [
this, resume_action]() {
785 }
else if (resume_action ==
788 }
else if (resume_action ==
806 connect(
hdr10Process, &QProcess::readyReadStandardOutput,
this, [
this]() {
807 QString output = QString::fromUtf8(
hdr10Process->readAllStandardOutput());
808 output.replace(
"\n",
"<br>");
811 connect(
hdr10Process, &QProcess::readyReadStandardError,
this, [
this]() {
812 QString output = QString::fromUtf8(
hdr10Process->readAllStandardError());
813 output.replace(
"\n",
"<br>");
816 this->
Write(
"<span style='color:#88aaff;'>" + output +
"</span>");
819 static_cast<void (QProcess::*)(
int, QProcess::ExitStatus)
>(&QProcess::finished),
821 [
this](
int exitCode, QProcess::ExitStatus) {
823 QTextStream stream(&text);
824 stream <<
"ffmpeg (HDR10): Exited with Code: " << exitCode;
829 setStyleSheet(
" QMainWindow { background-color: rgb(0,0,0); }");
833 setGeometry(150, 150, 1280, 720);
834 setWindowTitle(
"ACMX2 - Interface");
835 QMenuBar *menuBarPtr = menuBar();
837 menuBar()->setNativeMenuBar(
false);
838 fileMenu = menuBarPtr->addMenu(tr(
"File"));
839 cameraMenu = menuBarPtr->addMenu(tr(
"Session"));
842 runMenu = menuBarPtr->addMenu(tr(
"Run"));
843 listMenu = menuBarPtr->addMenu(tr(
"List"));
844 viewMenu = menuBarPtr->addMenu(tr(
"View"));
845 helpMenu = menuBarPtr->addMenu(tr(
"Help"));
863 connect(
stayOnTopAction, &QAction::toggled,
this, [
this](
bool checked) {
865 setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
867 setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
870 if (checked && QGuiApplication::platformName() ==
"wayland") {
871 Log(
"Stay on Top may not work on Wayland. Launch with QT_QPA_PLATFORM=xcb for X11 support.");
875 QAction *metadataAction =
new QAction(tr(
"Media Metadata Viewer..."),
this);
876 metadataAction->setShortcut(QKeySequence(
"Ctrl+Alt+V"));
879 viewMenu->addAction(metadataAction);
886 loadRecentMenu->menuAction()->setShortcut(QKeySequence(
"Ctrl+Shift+O"));
900 cameraSet =
new QAction(tr(
"Session Properties"),
this);
901 cameraSet->setShortcut(QKeySequence(
"Ctrl+Shift+P"));
904 audioSet =
new QAction(tr(
"Audio Settings"),
this);
905 audioSet->setShortcut(QKeySequence(
"Ctrl+Shift+A"));
939 QAction *runMenu_clearLog =
new QAction(tr(
"Clear Log"),
this);
940 runMenu_clearLog->setShortcut(QKeySequence(
"Ctrl+L"));
941 connect(runMenu_clearLog, &QAction::triggered,
this, [
this]() {
944 runMenu->addAction(runMenu_clearLog);
949 connect(
play_repeat, &QAction::toggled,
this, [
this](
bool) {
958 tr(
"Advance shader time by a fixed amount per output frame."));
961 QSettings settings(
"LostSideDead",
"acmx2");
962 settings.setValue(
"interface/normalized_time", checked);
966 play_stop =
new QAction(tr(
"Stop"),
this);
967 play_stop->setShortcut(QKeySequence(
"Shift+F5"));
969 connect(
play_stop, &QAction::triggered,
this, [=]() {
970 if (
process->state() == QProcess::Running) {
984 playlistAction =
new QAction(tr(
"Shader Playlist Settings..."),
this);
996 tr(
"Build ACMXVK while omitting shaders that fail to compile."));
1028 tr(
"Shader binary caching is not supported on macOS."));
1035 Log(
"Shader cache enabled - will use cached shaders if available");
1037 Log(
"Shader cache disabled - shaders will be recompiled each run");
1061 listMenu_new =
new QAction(tr(
"New Shader Library"),
this);
1062 listMenu_new->setShortcut(QKeySequence(
"Ctrl+Shift+N"));
1090 listMenu_up =
new QAction(tr(
"Shift Shader Up"),
this);
1120 QMessageBox::information(
1121 this, tr(
"Find in Files"),
1122 tr(
"Load a shader library before searching its files."));
1128 [
this](
const QString &filePath,
int lineNumber,
1129 int columnNumber,
int matchLength) {
1134 dialog->activateWindow();
1148 QMessageBox box(
this);
1149 box.setWindowTitle(
"About ACMX2");
1150 box.setWindowIcon(QIcon(
":/win-icon.png"));
1151 const QString info =
1152 QStringLiteral(
"<p><b>ACMX %1</b><br>"
1153 "(C) 2026 %2 Software<br>"
1154 "<a href=\"https://lostsidedead.biz\">"
1155 "http://lostsidedead.biz</a><br>"
1156 "This software is dedicated to all that have "
1157 "experienced mental health issues.</p>")
1160 box.setTextFormat(Qt::RichText);
1161 box.setTextInteractionFlags(Qt::TextBrowserInteraction);
1163 for (QLabel *label : box.findChildren<QLabel *>())
1164 label->setOpenExternalLinks(
true);
1165 QPixmap bigIcon(
":/win-icon.png");
1166 if (!bigIcon.isNull()) {
1167 QPixmap resizedIcon = bigIcon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1168 box.setIconPixmap(resizedIcon);
1180 if (!shaderName.isEmpty())
1187 {tr(
"#"), tr(
"Name"), tr(
"Last Modified"), tr(
"Compile Health"), tr(
"Type")});
1190 list_view->setAlternatingRowColors(
false);
1191 list_view->setSelectionMode(QAbstractItemView::SingleSelection);
1192 list_view->setSelectionBehavior(QAbstractItemView::SelectRows);
1193 list_view->setContextMenuPolicy(Qt::CustomContextMenu);
1195 list_view->setAllColumnsShowFocus(
true);
1196 list_view->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
1197 list_view->header()->setSectionResizeMode(1, QHeaderView::Stretch);
1198 list_view->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
1199 list_view->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
1200 list_view->header()->setSectionResizeMode(4, QHeaderView::ResizeToContents);
1205 list_view->setToolTip(tr(
"Right click while running to change the active shader."));
1208 "<b style='color:red;'>ACMX</b> - Interface: Loaded.");
1210 connect(
list_view, &QTreeWidget::doubleClicked,
1212 connect(
list_view, &QTreeWidget::customContextMenuRequested,
1213 this, [
this](
const QPoint &pos) {
1216 if (QTreeWidgetItem *item =
list_view->itemAt(pos)) {
1227 QWidget *centralWidget =
new QWidget(
this);
1228 QVBoxLayout *layout =
new QVBoxLayout(centralWidget);
1231 centralWidget->setLayout(layout);
1232 setCentralWidget(centralWidget);
1233 QSettings appSettings(
"LostSideDead");
1235 appSettings.value(
"interface/backend",
"acmx2")
1242 const QString legacyLibrary =
1244 ? appSettings.value(
"shaders",
"").toString()
1246 QString path = appSettings
1251 path = path.trimmed();
1252 while (path.endsWith(
"/") || path.endsWith(
"\\")) {
1255 const QString legacyExecutable =
1266 prefix_path = appSettings.value(
"prefix_path",
".").toString();
1269 bool useCustomStyle = appSettings.value(
"useCustomStyle",
false).toBool();
1271 midi_enabled = appSettings.value(
"midiEnabled",
false).toBool();
1273 midi_device = appSettings.value(
"midiDevice", -1).toInt();
1275 watermark_text = appSettings.value(
"watermarkText",
"").toString();
1276 watermark_r = appSettings.value(
"watermarkR", 255).toInt();
1277 watermark_g = appSettings.value(
"watermarkG", 0).toInt();
1278 watermark_b = appSettings.value(
"watermarkB", 150).toInt();
1280 autopilot_frames = appSettings.value(
"playlistAutopilotFrames", 4).toInt();
1284 autopilot_random = appSettings.value(
"playlistAutopilotRandom",
false).toBool();
1290 if (!path.isEmpty()) {
1291 QFileInfo pathInfo(path);
1292 if (pathInfo.exists() && pathInfo.isDir() &&
1294 QString backendError;
1295 const std::optional<acmx2::Backend> libraryBackend =
1297 if (!backendError.isEmpty()) {
1298 Log(
"Warning: Saved shader library backend metadata is invalid: " +
1300 }
else if (libraryBackend && *libraryBackend !=
active_backend) {
1301 Log(tr(
"Warning: Saved shader library targets %1 while the "
1302 "active backend is %2: %3")
1309 Log(
"Successfully loaded saved shader path");
1312 QString errorMsg =
"Warning: Saved shader path is invalid: " + path +
" - ";
1313 if (!pathInfo.exists()) {
1314 errorMsg +=
"directory does not exist";
1315 }
else if (!pathInfo.isDir()) {
1316 errorMsg +=
"path is not a directory";
1318 errorMsg +=
"library.json or index.txt not found in directory";
1325 customStyleSheet = appSettings.value(
"customStyleSheet", defaultCustomStyleSheet).toString();
1331 QSettings settings(
"LostSideDead",
"acmx2");
1333 const QString inputMode =
1334 settings.value(
"interface/input_mode",
"camera").toString();
1335 const bool videoMode = inputMode ==
"video";
1336 const bool graphicsMode = inputMode ==
"graphic";
1337 const bool cameraMode = !videoMode && !graphicsMode;
1340 std::max(0, settings.value(
"interface/camera_device", 0).toInt()));
1341 camera_res = storedResolution(settings,
"interface/camera_resolution",
1342 QSize(1280, 720),
false);
1343 screen_res = storedResolution(settings,
"interface/screen_resolution",
1346 output_fps = settings.value(
"interface/camera_fps", 30.0).toDouble();
1351 ? settings.value(
"interface/input_video",
"").toString()
1354 ? settings.value(
"interface/graphics_file",
"").toString()
1357 const bool saveOutput =
1358 settings.value(
"interface/save_output",
false).toBool();
1360 ? settings.value(
"interface/output_video",
"").toString()
1363 settings.value(
"interface/fullscreen",
false).toBool();
1365 settings.value(
"interface/copy_audio",
false).toBool();
1368 settings.value(
"interface/texture_cache",
false).toBool();
1369 cache_delay = settings.value(
"interface/cache_delay", 1).toInt();
1371 settings.value(
"interface/cache_size", 8).toInt(), 1, 64);
1373 settings.value(
"interface/use_yuv",
false).toBool();
1376 settings.value(
"interface/convert_to_hdr10",
false).toBool();
1377 enable_3d = settings.value(
"interface/enable_3d",
false).toBool();
1378 model_file = settings.value(
"interface/model_file",
"cube.mxmod.z").toString();
1380 onnx_model = settings.value(
"interface/onnx_model_file",
"").toString();
1382 settings.value(
"deep_dream/enabled",
false).toBool();
1384 settings.value(
"deep_dream/model_file", QString()).toString();
1386 settings.value(
"deep_dream/layer",
"relu4_2").toString();
1388 settings.value(
"deep_dream/iterations", 1).toInt(), 1, 100);
1390 settings.value(
"deep_dream/strength", 0.05).toDouble(), 0.0001,
1393 settings.value(
"deep_dream/feedback", 0.9).toDouble(), 0.0, 0.99);
1395 settings.value(
"deep_dream/zoom", 1.01).toDouble(), 0.9, 1.1);
1397 settings.value(
"deep_dream/rotation", 0.1).toDouble(), -5.0, 5.0);
1399 settings.value(
"deep_dream/maximum_dimension", 512).toInt();
1405 settings.value(
"deep_dream/fp16",
false).toBool();
1407 settings.value(
"deep_dream/channel", -1).toInt(), -1, 65535);
1409 settings.value(
"deep_dream/octaves", 1).toInt(), 1, 8);
1411 settings.value(
"deep_dream/octave_scale", 1.4).toDouble(), 1.1,
1414 settings.value(
"deep_dream/jitter", 0).toInt(), 0, 64);
1416 settings.value(
"deep_dream/smoothing", 0).toInt(), 0, 16);
1418 settings.value(
"deep_dream/gpu_filter_first",
false).toBool();
1420 settings.value(
"deep_dream/deep_original",
false).toBool();
1426 cuda_device = settings.value(
"interface/cuda_device", 0).toInt();
1427 time_speed = settings.value(
"interface/time_speed", 1.0).toFloat();
1429 settings.value(
"interface/normalized_time",
false).toBool();
1435 settings.value(
"interface/duration_enabled",
false).toBool();
1436 max_duration = settings.value(
"interface/duration_seconds", 60.0).toDouble();
1438 settings.value(
"interface/max_size_enabled",
false).toBool();
1439 max_size_mb = settings.value(
"interface/max_size_mb", 500.0).toDouble();
1441 flip_enabled = settings.value(
"interface/flip",
false).toBool();
1442 rotate_enabled = settings.value(
"interface/rotate",
false).toBool();
1443 rotation_mode = settings.value(
"interface/rotation_mode",
"clockwise").toString();
1444 png_output = settings.value(
"interface/write_png",
false).toBool();
1445 generate_enabled = settings.value(
"interface/generate_enabled",
false).toBool();
1448 encode_preset = settings.value(
"recording/preset",
"medium").toString();
1449 encode_tune = settings.value(
"recording/tune",
"").toString();
1450 encode_crf = settings.value(
"recording/crf", 18).toInt();
1452 settings.value(
"recording/rate_control",
"quality").toString();
1454 settings.value(
"recording/bitrate",
"10M").toString();
1455 encode_codec = settings.value(
"recording/codec",
"auto").toString();
1457 encode_realtime = settings.value(
"recording/realtime",
false).toBool();
1459 settings.value(
"recording/no_drop",
false).toBool();
1461 settings.value(
"interface/acmxvk_maximize_fps",
false).toBool();
1463 settings.value(
"interface/acmxvk_use_source_fps",
false).toBool();
1465 settings.value(
"interface/acmxvk_use_source_audio",
false)
1471 QFont listFont(
"Courier New");
1472 listFont.setStyleHint(QFont::Monospace);
1473 listFont.setPointSize(12);
1476 if (customStyleEnabled) {
1480 "QTreeWidget { background-color: black; color: white; font-size: 13px;"
1481 " font-family: 'Courier New', Courier, monospace; }"
1482 "QHeaderView::section { background-color: #110000; color: lime;"
1483 " font-family: 'Courier New', Courier, monospace; padding: 4px;"
1484 " border: 1px solid #330000; }");
1489 QFont logFont(
"Courier New");
1490 logFont.setStyleHint(QFont::Monospace);
1491 logFont.setPointSize(11);
1494 if (customStyleEnabled) {
1498 "QTextEdit { background-color: black; color: lime; font-size: 13px;"
1499 " font-family: 'Courier New', Courier, monospace; }");
1505 QSettings appSettings(
"LostSideDead");
1506 appSettings.setValue(
"useCustomStyle", enable);
1524 QSettings appSettings(
"LostSideDead");
1525 const bool currentlyEnabled = appSettings.value(
"useCustomStyle",
false).toBool();
1526 const QString lastPresetName = appSettings.value(
"customStylePreset",
"Current Style").toString();
1528 auto makePalette = [](
const char *winBg,
const char *winFg,
const char *accent,
1529 const char *fieldBg,
const char *fieldFg,
const char *fieldBorder,
1530 const char *btnBg,
const char *btnHover,
const char *btnFg,
1531 const char *menuBg,
const char *menuFg,
1532 const char *menuSelBg,
const char *menuSelFg,
1533 const char *selBg,
const char *border) {
1553 const std::array<QPair<QString, QString>, 26> presetStyles = {{{
"Current Style",
customStyleSheet},
1554 {
"Light: Blue & White",
1555 makePalette(
"#f6fbff",
"#143a5c",
"#2d7cc4",
1556 "#ffffff",
"#123b61",
"#9cc6ea",
1557 "#2d7cc4",
"#2368a6",
"#ffffff",
1558 "#eaf5ff",
"#143a5c",
"#cfe6ff",
"#0b2e4d",
1559 "#bcdcff",
"1px solid #9cc6ea")},
1561 makePalette(
"#f5f7fa",
"#1f2a37",
"#4b5563",
1562 "#ffffff",
"#1f2937",
"#b6c3d4",
1563 "#4b5563",
"#374151",
"#ffffff",
1564 "#e8edf4",
"#1f2a37",
"#d2dbe7",
"#111827",
1565 "#cdd5e0",
"1px solid #b6c3d4")},
1566 {
"Light: White & Red",
1567 makePalette(
"#fffdfd",
"#5b1515",
"#d63b3b",
1568 "#ffffff",
"#5a1a1a",
"#e8bcbc",
1569 "#d63b3b",
"#bc2f2f",
"#ffffff",
1570 "#fff4f4",
"#5b1515",
"#ffdede",
"#4b0f0f",
1571 "#ffd1d1",
"1px solid #e8bcbc")},
1572 {
"Light: White & Green",
1573 makePalette(
"#fcfffc",
"#164529",
"#2e9d57",
1574 "#ffffff",
"#1a4f2f",
"#b8dfc7",
1575 "#2e9d57",
"#25824a",
"#ffffff",
1576 "#f1fbf4",
"#164529",
"#d6f3df",
"#11361f",
1577 "#c9eecf",
"1px solid #b8dfc7")},
1578 {
"Light: White & Blue",
1579 makePalette(
"#fcfdff",
"#16395f",
"#2f6ed7",
1580 "#ffffff",
"#1b446f",
"#b7d0f0",
1581 "#2f6ed7",
"#285db7",
"#ffffff",
1582 "#f1f6ff",
"#16395f",
"#d9e8ff",
"#102b49",
1583 "#cddfff",
"1px solid #b7d0f0")},
1584 {
"Light: White & Cyan",
1585 makePalette(
"#fbfeff",
"#12404a",
"#1ea9bf",
1586 "#ffffff",
"#14505d",
"#b8e2ea",
1587 "#1ea9bf",
"#198da0",
"#ffffff",
1588 "#effbfe",
"#12404a",
"#d5f3f8",
"#0e3138",
1589 "#c7edf4",
"1px solid #b8e2ea")},
1590 {
"Light: White & Amber",
1591 makePalette(
"#fffefb",
"#5a3a12",
"#d18b1f",
1592 "#ffffff",
"#644317",
"#ead7b6",
1593 "#d18b1f",
"#b37518",
"#ffffff",
1594 "#fff9ed",
"#5a3a12",
"#ffebcb",
"#4a2f0f",
1595 "#ffe2b5",
"1px solid #ead7b6")},
1597 makePalette(
"#0f0608",
"#ff637d",
"#a02949",
1598 "#1b0b10",
"#ff8fa3",
"#7f2036",
1599 "#6f1630",
"#8a1f3d",
"#ffdfe6",
1600 "#16090d",
"#ff637d",
"#52111f",
"#ffd5dc",
1601 "#52111f",
"2px solid #a02949")},
1603 makePalette(
"#06110c",
"#7af7c2",
"#2c8e68",
1604 "#0d1e16",
"#95ffd0",
"#2c8e68",
1605 "#1c6a4d",
"#258961",
"#dcfff2",
1606 "#08160f",
"#7af7c2",
"#12402d",
"#d9fff0",
1607 "#12402d",
"2px solid #2c8e68")},
1609 makePalette(
"#070713",
"#c6c8ff",
"#5362ba",
1610 "#121634",
"#d8daff",
"#4956a5",
1611 "#36439a",
"#4453b4",
"#eef0ff",
1612 "#0d1022",
"#c6c8ff",
"#232a5a",
"#eef0ff",
1613 "#232a5a",
"2px solid #5362ba")},
1614 {
"Dark: Black & Red",
1615 makePalette(
"#050505",
"#ff4d4d",
"#d90000",
1616 "#120808",
"#ff7b7b",
"#b50000",
1617 "#2a0c0c",
"#3a1010",
"#ffd6d6",
1618 "#0b0707",
"#ff5a5a",
"#6b1111",
"#ffe9e9",
1619 "#5a0c0c",
"2px solid #d90000")},
1620 {
"Dark: Black & Green",
1621 makePalette(
"#040704",
"#6dfb88",
"#22b44a",
1622 "#0a140b",
"#a8ffbe",
"#1d9a3e",
1623 "#12331b",
"#164425",
"#e1ffe8",
1624 "#08100a",
"#74ff95",
"#12331b",
"#e7ffed",
1625 "#10381d",
"2px solid #22b44a")},
1626 {
"Dark: Black & Blue",
1627 makePalette(
"#04060a",
"#81b9ff",
"#2f6ed7",
1628 "#0a1222",
"#b4d4ff",
"#2a5eb7",
1629 "#132749",
"#1a3260",
"#e7f1ff",
1630 "#070d1a",
"#8cc0ff",
"#1a3260",
"#eef5ff",
1631 "#17335f",
"2px solid #2f6ed7")},
1632 {
"Dark: Black & Cyan",
1633 makePalette(
"#030809",
"#7defff",
"#1ba8c3",
1634 "#09161a",
"#b8f7ff",
"#1990a7",
1635 "#10323a",
"#14414b",
"#e7fbff",
1636 "#071015",
"#89f3ff",
"#0f3943",
"#e8fcff",
1637 "#0f3943",
"2px solid #1ba8c3")},
1638 {
"Dark: Black & Amber",
1639 makePalette(
"#090704",
"#ffd77a",
"#d88c1d",
1640 "#1a1308",
"#ffe7b4",
"#bf7a19",
1641 "#3d2810",
"#523618",
"#fff3db",
1642 "#130e07",
"#ffdf8a",
"#5a3a16",
"#fff4df",
1643 "#5a3a16",
"2px solid #d88c1d")},
1644 {
"Light: Lavender Mist",
1645 makePalette(
"#f8f6ff",
"#302653",
"#7157c8",
1646 "#ffffff",
"#34295b",
"#c9bdea",
1647 "#7157c8",
"#5d45ae",
"#ffffff",
1648 "#eee9ff",
"#302653",
"#ded5ff",
"#241a48",
1649 "#d9d0ff",
"1px solid #c9bdea")},
1650 {
"Light: Rose Quartz",
1651 makePalette(
"#fff8fa",
"#532535",
"#c25578",
1652 "#ffffff",
"#5b293c",
"#e8c1cf",
1653 "#c25578",
"#a94465",
"#ffffff",
1654 "#fff0f4",
"#532535",
"#f6d7e1",
"#411a28",
1655 "#f1ccd8",
"1px solid #e8c1cf")},
1656 {
"Light: Sandstone",
1657 makePalette(
"#fbf7ef",
"#493728",
"#a66a3f",
1658 "#fffdf8",
"#4f3929",
"#d9c3aa",
1659 "#a66a3f",
"#895431",
"#ffffff",
1660 "#f3eadc",
"#493728",
"#ead8c1",
"#35251a",
1661 "#e5d1b7",
"1px solid #d9c3aa")},
1662 {
"Light: Mint & Navy",
1663 makePalette(
"#f3fbf8",
"#173a3c",
"#2b8c7f",
1664 "#ffffff",
"#173a3c",
"#addbd2",
1665 "#1d5962",
"#287681",
"#ffffff",
1666 "#e5f6f1",
"#173a3c",
"#c8eee5",
"#102f34",
1667 "#bde5dc",
"1px solid #addbd2")},
1668 {
"Light: High Contrast",
1669 makePalette(
"#ffffff",
"#111111",
"#005fcc",
1670 "#ffffff",
"#000000",
"#4d4d4d",
1671 "#111111",
"#005fcc",
"#ffffff",
1672 "#f0f0f0",
"#000000",
"#005fcc",
"#ffffff",
1673 "#9dccff",
"2px solid #111111")},
1674 {
"Dark: Cyberpunk Neon",
1675 makePalette(
"#070513",
"#f3e7ff",
"#ff2bd6",
1676 "#100c24",
"#5ffbf1",
"#6e4cff",
1677 "#2a145c",
"#ff2bd6",
"#ffffff",
1678 "#0c081c",
"#5ffbf1",
"#381b72",
"#ffffff",
1679 "#381b72",
"2px solid #ff2bd6")},
1681 makePalette(
"#282a36",
"#f8f8f2",
"#bd93f9",
1682 "#21222c",
"#f8f8f2",
"#6272a4",
1683 "#44475a",
"#6272a4",
"#f8f8f2",
1684 "#21222c",
"#f8f8f2",
"#44475a",
"#f8f8f2",
1685 "#44475a",
"1px solid #6272a4")},
1686 {
"Dark: Nord Frost",
1687 makePalette(
"#2e3440",
"#eceff4",
"#88c0d0",
1688 "#3b4252",
"#eceff4",
"#4c566a",
1689 "#4c566a",
"#5e81ac",
"#eceff4",
1690 "#242933",
"#d8dee9",
"#434c5e",
"#eceff4",
1691 "#434c5e",
"1px solid #88c0d0")},
1693 makePalette(
"#002b36",
"#93a1a1",
"#b58900",
1694 "#073642",
"#eee8d5",
"#586e75",
1695 "#07576b",
"#268bd2",
"#fdf6e3",
1696 "#00242d",
"#93a1a1",
"#07576b",
"#fdf6e3",
1697 "#07576b",
"1px solid #586e75")},
1698 {
"Dark: Graphite Orange",
1699 makePalette(
"#171717",
"#f2f2f2",
"#ff8a3d",
1700 "#242424",
"#f7f7f7",
"#5f5f5f",
1701 "#3a3a3a",
"#ff8a3d",
"#ffffff",
1702 "#202020",
"#f2f2f2",
"#59311c",
"#ffffff",
1703 "#59311c",
"2px solid #ff8a3d")}}};
1710 QDialog dialog(
this);
1711 dialog.setWindowTitle(tr(
"Custom Style Editor"));
1712 dialog.resize(900, 640);
1714 dialog.setStyleSheet(
"");
1716 auto *layout =
new QVBoxLayout(&dialog);
1717 auto *topRow =
new QHBoxLayout();
1718 auto *enableCheck =
new QCheckBox(tr(
"Use custom style"), &dialog);
1719 enableCheck->setChecked(currentlyEnabled);
1720 auto *presetLabel =
new QLabel(tr(
"Preset:"), &dialog);
1721 auto *presetCombo =
new QComboBox(&dialog);
1722 for (
const auto &preset : presetStyles) {
1723 presetCombo->addItem(preset.first);
1725 int presetIndex = 0;
1726 for (
int i = 0; i < static_cast<int>(presetStyles.size()); ++i) {
1727 if (presetStyles[
static_cast<std::size_t
>(i)].first == lastPresetName) {
1732 presetCombo->setCurrentIndex(presetIndex);
1734 auto *editor =
new QPlainTextEdit(&dialog);
1736 editor->setLineWrapMode(QPlainTextEdit::NoWrap);
1737 editor->setPlaceholderText(tr(
"Enter a Qt stylesheet (QSS) for ACMX2 interface..."));
1739 QFont qssFont(
"Courier New");
1740 qssFont.setStyleHint(QFont::Monospace);
1741 qssFont.setPointSize(10);
1742 editor->setFont(qssFont);
1745 auto *buttonBox =
new QDialogButtonBox(&dialog);
1746 auto *applyButton = buttonBox->addButton(tr(
"Apply"), QDialogButtonBox::ApplyRole);
1747 auto *saveButton = buttonBox->addButton(tr(
"Save"), QDialogButtonBox::ActionRole);
1748 auto *closeButton = buttonBox->addButton(QDialogButtonBox::Close);
1750 topRow->addWidget(enableCheck);
1751 topRow->addSpacing(12);
1752 topRow->addWidget(presetLabel);
1753 topRow->addWidget(presetCombo, 1);
1754 layout->addLayout(topRow);
1755 layout->addWidget(editor, 1);
1756 layout->addWidget(buttonBox);
1758 connect(presetCombo, &QComboBox::currentTextChanged, &dialog,
1759 [editor, &presetStyles, &appSettings](
const QString &name) {
1760 for (
const auto &preset : presetStyles) {
1761 if (preset.first == name) {
1762 editor->setPlainText(preset.second);
1763 appSettings.setValue(
"customStylePreset", name);
1769 auto applyEditorStyle = [
this, &dialog, enableCheck, editor, presetCombo]() {
1771 QSettings styleSettings(
"LostSideDead");
1773 styleSettings.setValue(
"customStylePreset", presetCombo->currentText());
1774 styleSettings.setValue(
"useCustomStyle", enableCheck->isChecked());
1777 dialog.setStyleSheet(
"");
1784 connect(applyButton, &QPushButton::clicked, &dialog, applyEditorStyle);
1785 connect(saveButton, &QPushButton::clicked, &dialog, applyEditorStyle);
1786 connect(closeButton, &QPushButton::clicked, &dialog, &QDialog::accept);
1794 if (library.exec() == QDialog::Accepted) {
1815 [
this](
const QString &directory) {
1826 QString searchText = QInputDialog::getText(
this,
1827 tr(
"Search Shaders"),
1828 tr(
"Enter shader name to search:"),
1833 if (!ok || searchText.isEmpty()) {
1839 if (
items.isEmpty()) {
1840 QMessageBox::information(
this, tr(
"Search Shaders"),
1841 tr(
"No shaders are loaded."));
1844 int foundIndex = -1;
1846 for (
int i = 0; i <
items.size(); ++i) {
1847 if (
items[i].compare(searchText, Qt::CaseInsensitive) == 0) {
1853 if (foundIndex == -1) {
1854 for (
int i = 0; i <
items.size(); ++i) {
1855 if (
items[i].contains(searchText, Qt::CaseInsensitive)) {
1862 if (foundIndex != -1) {
1865 Log(
"Found shader: " +
items[foundIndex] +
" at index " + QString::number(foundIndex));
1867 QMessageBox::information(
this,
1869 tr(
"Shader \"") + searchText + tr(
"\" not found in the list."));
1870 Log(
"Shader not found: " + searchText);
1876 QMessageBox::information(
this,
1878 tr(
"Please perform a search first (Ctrl+F)."));
1882 if (
items.isEmpty()) {
1886 int foundIndex = -1;
1889 for (
int i = startIndex; i <
items.size(); ++i) {
1896 if (foundIndex == -1 && startIndex > 0) {
1897 for (
int i = 0; i < startIndex; ++i) {
1905 if (foundIndex != -1) {
1908 Log(
"Found next: " +
items[foundIndex] +
" at index " + QString::number(foundIndex));
1910 QMessageBox::information(
this,
1911 tr(
"No More Results"),
1919 QMessageBox::information(
this, tr(
"New Shader File"),
1920 tr(
"Create or load a shader library first."));
1925 const auto libraryType =
1927 if (!typeError.isEmpty()) {
1928 QMessageBox::warning(
this, tr(
"New Shader File"), typeError);
1933 QMessageBox::information(
1934 this, tr(
"New Shader File"),
1935 tr(
"New ACMXVK shaders must be added to a source library, not "
1936 "a compiled SPIR-V runtime library."));
1942 if (new_shader.exec() == QDialog::Accepted) {
1943 QSettings appSettings(
"LostSideDead");
1944 appSettings.setValue(
1955 if (row < 0 || row >=
items.size())
1957 const QString shaderName =
items.at(row);
1958 QString manifestError;
1961 QMessageBox::warning(
this, tr(
"Could Not Remove Shader"),
1963 Log(tr(
"Could not remove %1 from the library manifest: %2")
1964 .arg(shaderName, manifestError));
1967 items.removeAt(row);
1971 Log(tr(
"Removed shader from library manifest: %1").arg(shaderName));
1979 if (row < 0 || row >=
items.size()) {
1980 Log(
"No shader selected.");
1987 QStringList writtenItems;
1988 const int rowCount =
items.size();
1990 for (
int row = 0; row < rowCount; ++row) {
1991 const QString shaderName =
items.at(row).trimmed();
1992 if (shaderName.isEmpty() || writtenItems.contains(shaderName, Qt::CaseInsensitive)) {
1996 QString fullPath =
shader_path +
"/" + shaderName;
1997 QFileInfo fileInfo(fullPath);
1998 if (fileInfo.exists() && fileInfo.isFile()) {
1999 writtenItems.append(shaderName);
2001 Log(
"Warning: File no longer exists, removing from list: " + shaderName);
2004 QString manifestError;
2005 QStringList existingItems;
2008 existingItems == writtenItems) {
2014 manifestError.clear();
2016 Log(
"Failed to update shader manifest: " + manifestError);
2022 if (writtenItems.size() != rowCount) {
2023 items = writtenItems;
2025 Log(
"Updated shader list, removed " + QString::number(rowCount - writtenItems.size()) +
2026 " non-existent files");
2032 if (row <= 0 || row >=
items.size())
2034 items.swapItemsAt(row, row - 1);
2042 if (row < 0 || row >=
items.size() - 1)
2044 items.swapItemsAt(row, row + 1);
2051 QFile file(filePath);
2052 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
2053 Log(
"Failed to open file: " + filePath);
2057 QTextStream in(&file);
2058 QString contents = in.readAll();
2066 const int row = i.row();
2067 if (row < 0 || row >=
items.size())
2070 if (itemText.isEmpty()) {
2071 Log(
"Invalid shader name");
2079 int columnNumber,
int matchLength) {
2080 const QFileInfo requestedFile(filePath);
2081 if (!requestedFile.exists() || !requestedFile.isFile()) {
2082 QMessageBox::warning(
this, tr(
"Open Shader"),
2083 tr(
"Shader file no longer exists:\n%1").arg(filePath));
2088 const QString canonicalPath = requestedFile.canonicalFilePath();
2089 for (
const QPointer<TextEditor> &openEditor :
open_files) {
2092 const QString openPath = QFileInfo(openEditor->fileName()).canonicalFilePath();
2093 if (!canonicalPath.isEmpty() && openPath == canonicalPath) {
2099 openEditor->revealLocation(lineNumber, columnNumber, matchLength);
2106 editor->setWindowFlags(Qt::Widget);
2113 [
this](
const QString &includePath,
int lineNumber) {
2119 [
this](
const QString &name,
double value) {
2124 for (
const QPointer<TextEditor> &openEditor :
open_files) {
2126 openEditor->setUniformValue(name, value);
2132 connect(editor, &QWidget::windowTitleChanged,
this,
2133 [
this, editor](
const QString &title) {
2139 QString tabTitle = title;
2140 const int separator = tabTitle.indexOf(QStringLiteral(
" - "));
2142 tabTitle = tabTitle.mid(separator + 3);
2161 layout->setContentsMargins(4, 4, 4, 4);
2169 auto *editor = qobject_cast<TextEditor *>(
2171 if (editor && editor->close())
2174 QSettings settings(
"LostSideDead");
2176 settings.value(
"editor/workspaceGeometry").toByteArray())) {
2182 QSettings(
"LostSideDead")
2183 .setValue(
"editor/workspaceGeometry",
2190 const QString &sourcePath,
bool pending,
bool success,
2191 const QString &diagnostics) {
2192 const QFileInfo sourceInfo(sourcePath);
2193 const QString sourceCanonical = sourceInfo.canonicalFilePath();
2194 for (
const QPointer<TextEditor> &editor :
open_files) {
2197 const QFileInfo editorInfo(editor->fileName());
2198 const QString editorCanonical = editorInfo.canonicalFilePath();
2199 const bool sameFile =
2200 (!sourceCanonical.isEmpty() && !editorCanonical.isEmpty() &&
2201 sourceCanonical == editorCanonical) ||
2202 sourceInfo.absoluteFilePath() == editorInfo.absoluteFilePath();
2206 editor->setCompilePending();
2208 editor->setCompileResult(success, diagnostics);
2214 QList<acmx2::CustomUniformDefinition> definitions;
2218 definitions.clear();
2221 QVector<ShaderEditorUniform> uniforms;
2222 uniforms.reserve(definitions.size());
2224 uniforms.append({definition.name, definition.slot, definition.minimum,
2225 definition.maximum, definition.step, definition.value});
2227 const QString libraryRoot = QFileInfo(
shader_path).canonicalFilePath();
2228 for (
const QPointer<TextEditor> &editor :
open_files) {
2231 const QString editorPath = QFileInfo(editor->fileName()).canonicalFilePath();
2232 const QString relative =
2233 libraryRoot.isEmpty() || editorPath.isEmpty()
2234 ? QStringLiteral(
"..")
2235 : QDir(libraryRoot).relativeFilePath(editorPath);
2236 const bool inActiveLibrary =
2237 relative != QStringLiteral(
"..") &&
2238 !relative.startsWith(QStringLiteral(
"../"));
2239 if (inActiveLibrary)
2240 editor->setShaderContext(
acmxvk, uniforms, libraryRoot);
2248 const int row =
list_view->indexOfTopLevelItem(it);
2249 if (row < 0 || row >=
items.size())
2251 return items.at(row);
2257 QTreeWidgetItem *it =
list_view->currentItem();
2260 return list_view->indexOfTopLevelItem(it);
2264#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2265#if defined(__linux__) || defined(__APPLE__)
2269 if (shaderSelectionSemaphore != SEM_FAILED) {
2270 sem_t *publishedSemaphore = ::sem_open(
2272 if (publishedSemaphore != SEM_FAILED) {
2273 ::sem_close(publishedSemaphore);
2275 ::sem_close(shaderSelectionSemaphore);
2276 shaderSelectionSemaphore = SEM_FAILED;
2279 if (shaderSelectionSemaphore == SEM_FAILED) {
2280 shaderSelectionSemaphore = ::sem_open(
2283 if (shaderSelectionSemaphore == SEM_FAILED) {
2284 Log(tr(
"Shared interface control unavailable: sem_open(%1) failed: %2")
2286 QString::fromLocal8Bit(std::strerror(errno))));
2290 if (shaderSelectionShm)
2296 if (shaderSelectionShmFd < 0) {
2297 const int openError = errno;
2298 Log(tr(
"Shared interface control unavailable: shm_open(%1) failed: "
2301 QString::fromLocal8Bit(std::strerror(openError))));
2306 constexpr std::size_t SHARED_MEMORY_SIZE =
2308 struct stat shmStat{};
2309 if (::fstat(shaderSelectionShmFd, &shmStat) != 0) {
2310 const int statError = errno;
2311 Log(tr(
"Shared interface control unavailable: fstat(%1) failed: %2")
2313 QString::fromLocal8Bit(std::strerror(statError))));
2318 if (shmStat.st_size == 0) {
2319 if (::ftruncate(shaderSelectionShmFd,
2320 static_cast<off_t
>(SHARED_MEMORY_SIZE)) != 0) {
2321 const int truncateError = errno;
2322 Log(tr(
"Shared interface control unavailable: ftruncate(%1, %2) "
2325 .arg(
static_cast<qulonglong
>(SHARED_MEMORY_SIZE))
2326 .arg(QString::fromLocal8Bit(
2327 std::strerror(truncateError))));
2331 }
else if (shmStat.st_size <
static_cast<off_t
>(SHARED_MEMORY_SIZE)) {
2332 Log(tr(
"Shared interface control unavailable: %1 has size %2 bytes; "
2333 "expected %3. Refusing to resize an active or stale shared "
2336 .arg(
static_cast<qlonglong
>(shmStat.st_size))
2337 .arg(
static_cast<qulonglong
>(SHARED_MEMORY_SIZE)));
2342 void *mapped = ::mmap(
nullptr,
2344 PROT_READ | PROT_WRITE,
2346 shaderSelectionShmFd,
2348 if (mapped == MAP_FAILED) {
2349 const int mapError = errno;
2350 Log(tr(
"Shared interface control unavailable: mmap(%1, %2) failed: "
2353 .arg(
static_cast<qulonglong
>(SHARED_MEMORY_SIZE))
2354 .arg(QString::fromLocal8Bit(std::strerror(mapError))));
2361 if (shaderSelectionSemaphore ==
nullptr) {
2362 shaderSelectionSemaphore = ::CreateMutexW(
2363 nullptr, FALSE, acmx2::ipc::kShaderSelectionMutexNameWindows);
2365 if (shaderSelectionSemaphore ==
nullptr) {
2366 Log(tr(
"Shared interface control unavailable: CreateMutexW failed "
2367 "with Windows error %1")
2368 .arg(
static_cast<qulonglong
>(::GetLastError())));
2372 if (shaderSelectionShm)
2375 constexpr std::size_t SHARED_MEMORY_SIZE =
2377 shaderSelectionMapping = ::CreateFileMappingW(
2378 INVALID_HANDLE_VALUE,
nullptr, PAGE_READWRITE, 0,
2379 static_cast<DWORD
>(SHARED_MEMORY_SIZE),
2380 acmx2::ipc::kShaderSelectionMappingNameWindows);
2381 if (shaderSelectionMapping ==
nullptr) {
2382 Log(tr(
"Shared interface control unavailable: CreateFileMappingW "
2383 "failed with Windows error %1")
2384 .arg(
static_cast<qulonglong
>(::GetLastError())));
2389 void *mapped = ::MapViewOfFile(shaderSelectionMapping, FILE_MAP_ALL_ACCESS,
2390 0, 0, SHARED_MEMORY_SIZE);
2391 if (mapped ==
nullptr) {
2392 Log(tr(
"Shared interface control unavailable: MapViewOfFile failed "
2393 "with Windows error %1")
2394 .arg(
static_cast<qulonglong
>(::GetLastError())));
2398 shaderSelectionShm =
2402 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2404#if defined(__linux__) || defined(__APPLE__)
2405 Log(tr(
"Shared interface control unavailable: could not lock %1: %2")
2407 QString::fromLocal8Bit(std::strerror(errno))));
2409 Log(tr(
"Shared interface control unavailable: could not lock the "
2410 "Windows control mutex (error %1)")
2411 .arg(
static_cast<qulonglong
>(::GetLastError())));
2421 shaderSelectionShm->selected_index = -1;
2422 shaderSelectionShm->shader_pass_count = 0;
2423 shaderSelectionShm->shader_pass_enabled = 0;
2424 shaderSelectionShm->repeat_enabled = 0;
2425 shaderSelectionShm->display_filter_enabled = 0;
2426 shaderSelectionShm->watermark_enabled = 0;
2427 shaderSelectionShm->normalized_time_enabled = 0;
2428 std::fill(std::begin(shaderSelectionShm->reserved_flags),
2429 std::end(shaderSelectionShm->reserved_flags), 0);
2430 std::fill(std::begin(shaderSelectionShm->shader_pass_indices), std::end(shaderSelectionShm->shader_pass_indices), -1);
2431 std::fill(&shaderSelectionShm->shader_pass_names[0][0],
2432 &shaderSelectionShm->shader_pass_names[0][0] +
2436 shaderSelectionShm->gpu_filter_count = 0;
2437 shaderSelectionShm->gpu_filter_enabled = 0;
2438 shaderSelectionShm->gpu_buffer_size = 8;
2439 shaderSelectionShm->watermark_r = 255;
2440 shaderSelectionShm->watermark_g = 0;
2441 shaderSelectionShm->watermark_b = 150;
2442 std::fill(std::begin(shaderSelectionShm->reserved), std::end(shaderSelectionShm->reserved), 0);
2443 std::fill(std::begin(shaderSelectionShm->gpu_filter_indices), std::end(shaderSelectionShm->gpu_filter_indices), -1);
2444 std::fill(std::begin(shaderSelectionShm->watermark_text), std::end(shaderSelectionShm->watermark_text),
'\0');
2445 shaderSelectionShm->reload_shader_index = -1;
2446 std::fill(std::begin(shaderSelectionShm->reload_shader_path), std::end(shaderSelectionShm->reload_shader_path),
'\0');
2447 shaderSelectionShm->reload_sequence = 0;
2448 shaderSelectionShm->custom_uniform_count = 0;
2449 std::fill(&shaderSelectionShm->custom_uniform_names[0][0],
2450 &shaderSelectionShm->custom_uniform_names[0][0] +
2454 std::fill(std::begin(shaderSelectionShm->custom_uniform_values),
2455 std::end(shaderSelectionShm->custom_uniform_values), 0.0f);
2456 std::fill(std::begin(shaderSelectionShm->audio_file_path),
2457 std::end(shaderSelectionShm->audio_file_path),
'\0');
2458 shaderSelectionShm->audio_output_device = -1;
2459 shaderSelectionShm->audio_pass_through = 0;
2460 shaderSelectionShm->audio_trunc = 0;
2461 shaderSelectionShm->audio_repeat = 0;
2462 shaderSelectionShm->audio_reserved = 0;
2463 shaderSelectionShm->audio_file_sequence = 0;
2464 shaderSelectionShm->dream_enabled = 0;
2465 shaderSelectionShm->dream_fp16 = 0;
2466 shaderSelectionShm->dream_gpu_filter_first = 0;
2467 shaderSelectionShm->dream_reserved = 0;
2468 shaderSelectionShm->dream_iterations = 1;
2469 shaderSelectionShm->dream_maximum_dimension = 512;
2470 shaderSelectionShm->dream_channel = -1;
2471 shaderSelectionShm->dream_octaves = 1;
2472 shaderSelectionShm->dream_jitter = 0;
2473 shaderSelectionShm->dream_smoothing = 0;
2474 shaderSelectionShm->dream_strength = 0.05F;
2475 shaderSelectionShm->dream_feedback = 0.9F;
2476 shaderSelectionShm->dream_zoom = 1.01F;
2477 shaderSelectionShm->dream_rotation = 0.1F;
2478 shaderSelectionShm->dream_octave_scale = 1.4F;
2479 std::fill(std::begin(shaderSelectionShm->dream_model_path),
2480 std::end(shaderSelectionShm->dream_model_path),
'\0');
2481 std::fill(std::begin(shaderSelectionShm->dream_layer),
2482 std::end(shaderSelectionShm->dream_layer),
'\0');
2483 std::fill(std::begin(shaderSelectionShm->selected_shader_name),
2484 std::end(shaderSelectionShm->selected_shader_name),
'\0');
2485 shaderSelectionShm->sequence = 0;
2491#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2492 if (!shaderSelectionShm)
2495 if (row < 0 || row >=
items.size())
2497 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2499 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
2502 shaderSelectionShm->selected_index = row;
2503 const QByteArray shaderName =
items.at(row).toUtf8();
2504 const qsizetype copyLength = std::min<qsizetype>(
2507 std::fill(std::begin(shaderSelectionShm->selected_shader_name),
2508 std::end(shaderSelectionShm->selected_shader_name),
'\0');
2509 std::copy_n(shaderName.constData(), copyLength,
2510 shaderSelectionShm->selected_shader_name);
2511 ++shaderSelectionShm->sequence;
2516#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2522 const QFileInfo savedFile(filePath);
2523 const QString shaderName = QDir(
shader_path).relativeFilePath(savedFile.absoluteFilePath());
2524 const int shaderIndex =
items.indexOf(shaderName, 0, Qt::CaseInsensitive);
2525 if (shaderIndex < 0) {
2526 Log(
"Saved shader is not in the active library; live reload was skipped: " + filePath);
2530 const QByteArray reloadPath = savedFile.canonicalFilePath().toUtf8();
2531 if (reloadPath.isEmpty() ||
2533 Log(
"Shader path is too long for live reload: " + filePath);
2537 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2539 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
2542 shaderSelectionShm->reload_shader_index = shaderIndex;
2543 std::fill(std::begin(shaderSelectionShm->reload_shader_path), std::end(shaderSelectionShm->reload_shader_path),
'\0');
2544 std::copy(reloadPath.cbegin(), reloadPath.cend(), shaderSelectionShm->reload_shader_path);
2545 ++shaderSelectionShm->reload_sequence;
2546 ++shaderSelectionShm->sequence;
2547 Log(
"Requested live shader reload: " + shaderName +
"<br>");
2563#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2565 if (!is_acmxvk_source_library(
shader_path, typeError)) {
2566 const QString diagnostic =
2568 ? tr(
"ACMXVK live compile requires a source library.")
2575 const QFileInfo sourceInfo(filePath);
2576 const QString sourcePath = sourceInfo.canonicalFilePath();
2577 const QString sourceRoot = QFileInfo(
shader_path).canonicalFilePath();
2578 if (sourcePath.isEmpty() || sourceRoot.isEmpty()) {
2579 const QString diagnostic =
2580 tr(
"Could not resolve saved ACMXVK shader: %1").arg(filePath);
2585 const QString sourceName =
2587 if (sourceName.isEmpty() ||
2588 (!sourceName.endsWith(QStringLiteral(
".frag"), Qt::CaseInsensitive) &&
2589 !sourceName.endsWith(QStringLiteral(
".comp"), Qt::CaseInsensitive)) ||
2590 !
items.contains(sourceName, Qt::CaseInsensitive)) {
2591 const QString diagnostic =
2592 tr(
"Saved file is not a fragment or compute source in the active "
2593 "ACMXVK library: %1")
2610#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2622 QProcess::SeparateChannels);
2625 QString output = QString::fromUtf8(
2631 Write(output.toHtmlEscaped().replace(
2632 '\n', QStringLiteral(
"<br>")));
2636 QString output = QString::fromUtf8(
2642 Write(QStringLiteral(
"<b style='color:red;'>") +
2643 output.toHtmlEscaped().replace(
2644 '\n', QStringLiteral(
"<br>")) +
2645 QStringLiteral(
"</b>"));
2649 static_cast<void (QProcess::*)(
int, QProcess::ExitStatus)
>(
2650 &QProcess::finished),
2651 this, [
this](
int exitCode, QProcess::ExitStatus exitStatus) {
2656 bool installed =
false;
2657 QString editorDiagnostics;
2660 if (!compilerOutput.isEmpty())
2661 compilerOutput += QLatin1Char(
'\n');
2664 if (exitStatus == QProcess::NormalExit && exitCode == 0) {
2667 if (compiled.open(QIODevice::ReadOnly)) {
2668 QDataStream stream(&compiled);
2669 stream.setByteOrder(QDataStream::LittleEndian);
2673 constexpr quint32 SPIRV_MAGIC = 0x07230203U;
2674 if (magic != SPIRV_MAGIC) {
2676 tr(
"Compiler did not produce valid SPIR-V.");
2677 Log(tr(
"<b style='color:red;'>Live ACMXVK compile did "
2678 "not produce valid SPIR-V for %1.</b>")
2681 editorDiagnostics = tr(
2682 "The compiled output is a symbolic link and cannot "
2683 "be replaced safely.");
2684 Log(tr(
"<b style='color:red;'>Refusing to replace "
2685 "symbolic-link shader output: %1</b>")
2688 std::error_code error;
2690 std::filesystem::u8path(
2692 std::filesystem::u8path(
2697 tr(
"Could not install compiled shader: %1")
2698 .arg(QString::fromStdString(
2700 Log(tr(
"<b style='color:red;'>Could not install "
2701 "live ACMXVK shader: %1</b>")
2702 .arg(QString::fromStdString(
2709 Log(tr(
"<b style='color:red;'>Live ACMXVK compile failed "
2710 "for %1 (%2, exit code %3).</b>")
2712 .arg(exitStatus == QProcess::CrashExit
2713 ? tr(
"compiler crashed")
2714 : tr(
"compiler error"))
2716 if (compilerOutput.isEmpty())
2718 editorDiagnostics = compilerOutput;
2719 Log(tr(
"<b style='color:red;'>Compiler message:</b>"
2720 "<pre style='white-space:pre-wrap;'>%1</pre>")
2721 .arg(compilerOutput.toHtmlEscaped()));
2726 installed ? compilerOutput : editorDiagnostics);
2731 Log(tr(
"Compiled and installed ACMXVK shader: %1")
2748 QString compilerError;
2749 const QString glslc = resolve_acmxvk_shader_compiler(compilerError);
2750 if (glslc.isEmpty()) {
2751 Log(tr(
"<b style='color:red;'>Cannot live compile ACMXVK shader: "
2753 .arg(compilerError.toHtmlEscaped()));
2764 const QString sourceRoot = QFileInfo(
shader_path).canonicalFilePath();
2765 const QString sourceName =
2768 QDir(acmxvk_build_directory(sourceRoot))
2769 .filePath(acmxvk_runtime_shader_name(sourceName));
2771 const QString diagnostic =
2772 tr(
"Could not create live shader output directory for %1.")
2774 Log(QStringLiteral(
"<b style='color:red;'>%1</b>")
2775 .arg(diagnostic.toHtmlEscaped()));
2780 QTimer::singleShot(0,
this,
2787 .arg(QCoreApplication::applicationPid())
2789 const QStringList arguments{
2792 Log(tr(
"Live compiling ACMXVK shader: %1").arg(sourceName));
2793 Log(tr(
"Command: %1 %2<br>")
2799 const QString diagnostic =
2800 tr(
"Failed to start the ACMXVK shader compiler: %1")
2802 Log(QStringLiteral(
"<b style='color:red;'>%1</b>")
2803 .arg(diagnostic.toHtmlEscaped()));
2812 QTimer::singleShot(0,
this,
2819 const QString &source) {
2832 const QString &source) {
2833#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
2834 if (!shaderSelectionShm || !
process ||
2835 process->state() != QProcess::Running) {
2836 const QString error =
2837 tr(
"Start ACMX2 before using shader live preview.");
2843 const QFileInfo sourceInfo(filePath);
2844 const QString sourcePath = sourceInfo.canonicalFilePath();
2845 const QString sourceRoot = QFileInfo(
shader_path).canonicalFilePath();
2846 if (sourcePath.isEmpty() || sourceRoot.isEmpty()) {
2847 const QString error =
2848 tr(
"Could not resolve the ACMX2 shader preview path: %1")
2855 const QString shaderName =
2857 const int shaderIndex =
items.indexOf(shaderName, 0, Qt::CaseInsensitive);
2858 const QString suffix = sourceInfo.suffix().toLower();
2859 if (shaderIndex < 0 ||
2860 (suffix != QStringLiteral(
"glsl") &&
2861 suffix != QStringLiteral(
"frag") &&
2862 suffix != QStringLiteral(
"comp"))) {
2863 const QString error =
2864 tr(
"The editor file is not an active ACMX2 fragment or compute "
2872 const QString previewDirectory =
2873 QDir(sourceRoot).filePath(QStringLiteral(
".acmx2-editor-preview"));
2874 if (!QDir().mkpath(previewDirectory)) {
2875 const QString error =
2876 tr(
"Could not create the ACMX2 editor preview directory.");
2882 const QString previewPath = QDir(previewDirectory)
2883 .filePath(QStringLiteral(
"preview-%1-%2.%3")
2884 .arg(QCoreApplication::applicationPid())
2887 QSaveFile previewFile(previewPath);
2888 const QByteArray sourceBytes = source.toUtf8();
2889 if (!previewFile.open(QIODevice::WriteOnly | QIODevice::Text) ||
2890 previewFile.write(sourceBytes) != sourceBytes.size() ||
2891 !previewFile.commit()) {
2892 const QString error =
2893 tr(
"Could not write the temporary ACMX2 shader preview.");
2899 const QByteArray reloadPath = QFileInfo(previewPath).canonicalFilePath().toUtf8();
2900 if (reloadPath.isEmpty() ||
2901 reloadPath.size() >=
2903 QFile::remove(previewPath);
2904 const QString error = tr(
"The ACMX2 shader preview path is too long.");
2910 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
2912 QFile::remove(previewPath);
2913 const QString error = tr(
"Could not lock ACMX2 interface control.");
2918 shaderSelectionShm->reload_shader_index = shaderIndex;
2919 std::fill(std::begin(shaderSelectionShm->reload_shader_path),
2920 std::end(shaderSelectionShm->reload_shader_path),
'\0');
2921 std::copy(reloadPath.cbegin(), reloadPath.cend(),
2922 shaderSelectionShm->reload_shader_path);
2923 ++shaderSelectionShm->reload_sequence;
2924 ++shaderSelectionShm->sequence;
2930 filePath,
false,
true,
2931 tr(
"Preview source sent to the running ACMX2 backend."));
2932 Log(tr(
"Requested ACMX2 editor preview: %1").arg(shaderName));
2964 qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
this,
2965 [
this](
int exitCode, QProcess::ExitStatus exitStatus) {
2972 if (!diagnostics.isEmpty())
2973 diagnostics += QLatin1Char(
'\n');
2977 const bool success =
2978 exitStatus == QProcess::NormalExit && exitCode == 0 &&
2989 Log(tr(
"Compiled ACMXVK editor preview: %1")
2993 Log(tr(
"<b style='color:red;'>ACMXVK editor preview failed: "
2994 "%1</b><pre style='white-space:pre-wrap;'>%2</pre>")
2996 diagnostics.toHtmlEscaped()));
3009 QString compilerError;
3010 const QString glslc = resolve_acmxvk_shader_compiler(compilerError);
3015 if (glslc.isEmpty()) {
3023 const QString sourceRoot = QFileInfo(
shader_path).canonicalFilePath();
3024 const QString previewDirectory =
3025 QDir(acmxvk_build_directory(sourceRoot))
3026 .filePath(QStringLiteral(
".editor-preview"));
3027 if (!QDir().mkpath(previewDirectory)) {
3028 const QString error = tr(
"Could not create the editor preview directory.");
3033 const QString suffix = sourceInfo.suffix().toLower();
3034 const QString baseName =
3035 QStringLiteral(
"preview-%1-%2.%3")
3036 .arg(QCoreApplication::applicationPid())
3038 .arg(suffix == QStringLiteral(
"comp") ? QStringLiteral(
"comp")
3039 : QStringLiteral(
"frag"));
3043 const QByteArray sourceBytes = source.toUtf8();
3044 if (!inputFile.open(QIODevice::WriteOnly | QIODevice::Text) ||
3045 inputFile.write(sourceBytes) != sourceBytes.size() ||
3046 !inputFile.commit()) {
3047 const QString error = tr(
"Could not write the temporary preview source.");
3056 QStringList arguments{QStringLiteral(
"-I"), sourceInfo.absolutePath()};
3057 if (!sourceRoot.isEmpty() && sourceRoot != sourceInfo.absolutePath())
3058 arguments << QStringLiteral(
"-I") << sourceRoot;
3065 const QString error = tr(
"Failed to start the shader compiler: %1")
3076 const QString &sourcePath,
const QString &runtimePath) {
3077#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
3083 const QString sourceRoot = QFileInfo(
shader_path).canonicalFilePath();
3084 const QString resolvedSourcePath = QFileInfo(sourcePath).canonicalFilePath();
3085 const QString sourceName =
3086 sourceRoot.isEmpty() || resolvedSourcePath.isEmpty()
3089 QDir(sourceRoot).relativeFilePath(resolvedSourcePath));
3090 if (sourceName.isEmpty()) {
3091 Log(tr(
"Compiled shader source cannot be resolved inside the active "
3097 const int shaderIndex =
items.indexOf(sourceName, 0, Qt::CaseInsensitive);
3098 if (shaderIndex < 0) {
3099 Log(tr(
"Compiled shader is not present in the active library "
3105 const QByteArray reloadPath =
3106 QFileInfo(runtimePath).canonicalFilePath().toUtf8();
3107 if (reloadPath.isEmpty()) {
3108 Log(tr(
"Compiled shader output cannot be resolved for live reload: %1")
3112 if (reloadPath.size() >=
3114 Log(tr(
"Compiled shader path is too long for live reload: %1")
3119 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
3121 Log(tr(
"Could not lock the live shader reload channel."));
3124 shaderSelectionShm->reload_shader_index = shaderIndex;
3125 std::fill(std::begin(shaderSelectionShm->reload_shader_path),
3126 std::end(shaderSelectionShm->reload_shader_path),
'\0');
3127 std::copy(reloadPath.cbegin(), reloadPath.cend(),
3128 shaderSelectionShm->reload_shader_path);
3129 ++shaderSelectionShm->reload_sequence;
3130 ++shaderSelectionShm->sequence;
3131 Log(tr(
"Requested live ACMXVK pipeline reload: %1<br>")
3134 Q_UNUSED(sourcePath);
3135 Q_UNUSED(runtimePath);
3140#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
3141 if (!shaderSelectionShm)
3144 std::array<qint32, acmx2::ipc::kShaderSelectionMaxPassCount> passIndices;
3145 passIndices.fill(-1);
3146 std::array<std::array<char, acmx2::ipc::kShaderSelectionMaxShaderName>,
3150 quint32 passCount = 0;
3156 const int idx =
items.indexOf(name);
3159 passIndices[passCount] = idx;
3160 const QByteArray shaderName = name.toUtf8();
3161 const qsizetype copyLength = std::min<qsizetype>(
3164 std::copy_n(shaderName.constData(), copyLength,
3165 passNames[passCount].begin());
3170 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
3172 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
3176 shaderSelectionShm->shader_pass_count = passCount;
3177 std::copy(passIndices.begin(), passIndices.end(), std::begin(shaderSelectionShm->shader_pass_indices));
3178 for (std::size_t i = 0; i < passNames.size(); ++i) {
3179 std::copy(passNames[i].begin(), passNames[i].end(),
3180 shaderSelectionShm->shader_pass_names[i]);
3182 ++shaderSelectionShm->sequence;
3187#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
3188 if (!shaderSelectionShm)
3190 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
3192 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
3196 ++shaderSelectionShm->sequence;
3201#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
3202 if (!shaderSelectionShm)
3205 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
3207 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
3211 shaderSelectionShm->normalized_time_enabled =
normalized_time ? 1 : 0;
3214 shaderSelectionShm->watermark_enabled = watermarkActive ? 1 : 0;
3215 shaderSelectionShm->watermark_r =
static_cast<uint8_t
>(std::clamp(
watermark_r, 0, 255));
3216 shaderSelectionShm->watermark_g =
static_cast<uint8_t
>(std::clamp(
watermark_g, 0, 255));
3217 shaderSelectionShm->watermark_b =
static_cast<uint8_t
>(std::clamp(
watermark_b, 0, 255));
3218 std::fill(std::begin(shaderSelectionShm->watermark_text), std::end(shaderSelectionShm->watermark_text),
'\0');
3221 const std::size_t wmLen = std::min<std::size_t>(wmCap,
static_cast<std::size_t
>(wmUtf8.size()));
3222 std::copy_n(wmUtf8.constData(),
static_cast<int>(wmLen), shaderSelectionShm->watermark_text);
3224 std::array<qint32, acmx2::ipc::kShaderSelectionMaxGpuFilterCount> gpuIndices;
3225 gpuIndices.fill(-1);
3226 quint32 gpuCount = 0;
3229 for (
const QString &part : parts) {
3233 const int idx = part.trimmed().toInt(&ok);
3236 gpuIndices[gpuCount++] = idx;
3240 shaderSelectionShm->gpu_filter_enabled = (gpuCount > 0) ? 1 : 0;
3241 shaderSelectionShm->gpu_filter_count = gpuCount;
3242 shaderSelectionShm->gpu_buffer_size =
static_cast<uint8_t
>(std::clamp(
gpu_buffer_size, 4, 32));
3243 std::copy(gpuIndices.begin(), gpuIndices.end(), std::begin(shaderSelectionShm->gpu_filter_indices));
3247 const bool dreamStringsFit =
3248 dreamModel.size() <
static_cast<int>(
3252 const bool dreamActive =
3255 shaderSelectionShm->dream_enabled = dreamActive ? 1 : 0;
3257 shaderSelectionShm->dream_gpu_filter_first =
3260 shaderSelectionShm->dream_maximum_dimension =
3266 shaderSelectionShm->dream_strength =
3268 shaderSelectionShm->dream_feedback =
3271 shaderSelectionShm->dream_rotation =
3273 shaderSelectionShm->dream_octave_scale =
3275 std::fill(std::begin(shaderSelectionShm->dream_model_path),
3276 std::end(shaderSelectionShm->dream_model_path),
'\0');
3277 std::fill(std::begin(shaderSelectionShm->dream_layer),
3278 std::end(shaderSelectionShm->dream_layer),
'\0');
3279 if (dreamStringsFit) {
3280 std::copy(dreamModel.cbegin(), dreamModel.cend(),
3281 shaderSelectionShm->dream_model_path);
3282 std::copy(dreamLayer.cbegin(), dreamLayer.cend(),
3283 shaderSelectionShm->dream_layer);
3285 Log(
"Deep Dream settings were not published because the model path "
3286 "or layer name is too long");
3289 ++shaderSelectionShm->sequence;
3294#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
3298 acmx2::ipc::ShaderSelectionLock lock(shaderSelectionSemaphore);
3300 Log(
"<br><style color=\"red\">Error lock failed</style><br>");
3303 std::fill(&shaderSelectionShm->custom_uniform_names[0][0],
3304 &shaderSelectionShm->custom_uniform_names[0][0] +
3308 std::fill(std::begin(shaderSelectionShm->custom_uniform_values),
3309 std::end(shaderSelectionShm->custom_uniform_values), 0.0f);
3316 const QByteArray name = uniform.name.toUtf8();
3317 if (name.isEmpty() ||
3322 std::copy(name.cbegin(), name.cend(),
3323 shaderSelectionShm->custom_uniform_names[count]);
3324 shaderSelectionShm->custom_uniform_values[count] =
3325 static_cast<float>(uniform.value);
3328 shaderSelectionShm->custom_uniform_count = count;
3329 ++shaderSelectionShm->sequence;
3334#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
3335 if (shaderSelectionShm) {
3336#if defined(__linux__) || defined(__APPLE__)
3339 ::UnmapViewOfFile(shaderSelectionShm);
3341 shaderSelectionShm =
nullptr;
3343#if defined(__linux__) || defined(__APPLE__)
3344 if (shaderSelectionShmFd >= 0) {
3345 ::close(shaderSelectionShmFd);
3346 shaderSelectionShmFd = -1;
3349 if (shaderSelectionMapping !=
nullptr) {
3350 ::CloseHandle(shaderSelectionMapping);
3351 shaderSelectionMapping =
nullptr;
3359#if defined(__linux__) || defined(__APPLE__)
3360 if (shaderSelectionSemaphore == SEM_FAILED)
3364 ::sem_close(shaderSelectionSemaphore);
3365 shaderSelectionSemaphore = SEM_FAILED;
3366#elif defined(_WIN32)
3367 if (shaderSelectionSemaphore ==
nullptr)
3370 ::CloseHandle(shaderSelectionSemaphore);
3371 shaderSelectionSemaphore =
nullptr;
3378 QTreeWidgetItem *it =
list_view->topLevelItem(row);
3382 list_view->scrollToItem(it, QAbstractItemView::PositionAtCenter);
3397 const QString cachePath = resolveShaderCachePath(
3400 QFileInfo cacheInfo(cachePath);
3401 if (!cacheInfo.exists() || !cacheInfo.isFile()) {
3402 Log(
"Shader cache not found at: " + cachePath);
3420 const QSignalBlocker blocker(
list_view);
3423 QString acmxvk_type_error;
3424 const bool acmxvk_source =
3426 is_acmxvk_source_library(
shader_path, acmxvk_type_error) &&
3427 acmxvk_type_error.isEmpty();
3428 const int width = QString::number(
items.size()).size();
3429 for (
int i = 0; i <
items.size(); ++i) {
3430 const QString &name =
items.at(i);
3432 const QString stem = QFileInfo(name).completeBaseName();
3433 const bool isCompute =
3434 name.endsWith(QStringLiteral(
".comp"), Qt::CaseInsensitive) ||
3435 name.endsWith(QStringLiteral(
".comp.spv"), Qt::CaseInsensitive);
3436 const QString shaderType = isCompute ? tr(
"Compute") : tr(
"Fragment");
3441 const AcmxvkBuildState state =
3446 health = tr(
"Not Built");
3447 healthColor = QColor(
"#888888");
3449 health = tr(
"Stale");
3450 healthColor = QColor(
"#ffaa00");
3452 health = tr(
"Up to Date");
3453 healthColor = QColor(
"#55ff55");
3456 health = tr(
"No cache");
3457 healthColor = QColor(
"#888888");
3459 health = tr(
"Uncached");
3460 healthColor = QColor(
"#cccc00");
3462 health = tr(
"Failed");
3463 healthColor = QColor(
"#ff5555");
3466 health = tr(
"Stale");
3467 healthColor = QColor(
"#ffaa00");
3469 health = tr(
"Cached");
3470 healthColor = QColor(
"#55ff55");
3474 cols << QString(
"%1").arg(i, width, 10, QLatin1Char(
' '))
3476 << (fi.exists() ? formatLastModified(fi.lastModified()) : tr(
"missing"))
3479 auto *item =
new QTreeWidgetItem(
list_view, cols);
3480 item->setTextAlignment(0, Qt::AlignRight | Qt::AlignVCenter);
3481 item->setForeground(3, QBrush(healthColor));
3483 item->setForeground(2, QBrush(QColor(
"#ff5555")));
3487 if (previousRow >= 0 && previousRow < list_view->topLevelItemCount()) {
3488 QTreeWidgetItem *it =
list_view->topLevelItem(previousRow);
3491 list_view->scrollToItem(it, QAbstractItemView::PositionAtCenter);
3497 QString normalized = message;
3498 while (normalized.endsWith(
'\n') || normalized.endsWith(
'\r')) {
3504 cursor.movePosition(QTextCursor::End);
3510 cursor.movePosition(QTextCursor::End);
3511 cursor.insertHtml(message);
3514 constexpr int MAX_BLOCKS = 5000;
3516 int excess = doc->blockCount() - MAX_BLOCKS;
3518 QTextCursor trim(doc);
3519 trim.movePosition(QTextCursor::Start);
3520 trim.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor, excess);
3521 trim.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
3522 trim.removeSelectedText();
3528 QSettings settings(
"LostSideDead");
3529 QString startDirectory = settings.value(
"lastShaderDir").toString();
3530 if (startDirectory.isEmpty())
3532 if (startDirectory.isEmpty())
3533 startDirectory = QDir::homePath();
3535 const QString directory = QFileDialog::getExistingDirectory(
3536 this, tr(
"Load Shader Library"), startDirectory,
3537 QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
3538 if (directory.isEmpty())
3541 settings.setValue(
"lastShaderDir", directory);
3552 if (!is_acmxvk_source_library(
shader_path, type_error) ||
3553 !type_error.isEmpty()) {
3554 QMessageBox::warning(
this, tr(
"Build ACMXVK Library"), reason);
3558 const QMessageBox::StandardButton answer = QMessageBox::question(
3559 this, tr(
"Build ACMXVK Library"),
3560 tr(
"The ACMXVK build is out of date or incomplete.\n\n%1\n\n"
3561 "Do you wish to rebuild it now?")
3563 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
3564 if (answer != QMessageBox::Yes)
3568 Log(tr(
"ACMXVK rebuild requested before launch."));
3574 setWindowTitle(tr(
"%1 - Interface").arg(name));
3585 QString sourceTypeError;
3586 const bool acmxvkSource =
3588 is_acmxvk_source_library(
shader_path, sourceTypeError) &&
3589 sourceTypeError.isEmpty();
3598 : tr(
"Rebuild Shader Cache"));
3603 ? tr(
"Compile changed GLSL sources into %1")
3612 ? tr(
"Build into %1 and omit shaders that fail to compile")
3625 ? tr(
"Permanently delete .frag and .comp sources that fail "
3626 "the ACMXVK Fix Build")
3643 const bool processIdle =
3653 list_view->setColumnHidden(3, acmx2Tools);
3658 3, acmx2Tools ? tr(
"Compile Health") : tr(
"Build Status"));
3668 tr(
"Right click while running to change the active shader."));
3674 QMessageBox::information(
3675 this, tr(
"Process Running"),
3676 tr(
"Stop the running process before changing backends."));
3694 QSettings settings(
"LostSideDead");
3709 const QString nextLibrary =
3722 if (!nextLibrary.isEmpty() && QFileInfo(nextLibrary).isDir() &&
3724 QString backendError;
3725 const std::optional<acmx2::Backend> libraryBackend =
3727 if (!backendError.isEmpty()) {
3728 Log(tr(
"Warning: Could not read backend metadata for %1: %2")
3729 .arg(nextLibrary, backendError));
3730 }
else if (libraryBackend && *libraryBackend !=
active_backend) {
3731 Log(tr(
"Warning: Saved %1 library belongs to %2: %3")
3753 Log(tr(
"ACMXVK launching and live shader selection are enabled."));
3757 const QString trimmedPath = path.trimmed();
3758 if (trimmedPath.isEmpty())
3760 const QString libraryPath = QDir::cleanPath(trimmedPath);
3761 const QFileInfo libraryInfo(libraryPath);
3762 if (!libraryInfo.exists()) {
3763 QMessageBox::warning(
this, tr(
"Invalid Shader Path"),
3764 tr(
"Shader directory does not exist:\n%1")
3768 if (!libraryInfo.isDir()) {
3769 QMessageBox::warning(
this, tr(
"Invalid Shader Path"),
3770 tr(
"Shader path is not a directory:\n%1")
3775 QMessageBox::warning(
3776 this, tr(
"Missing Shader Manifest"),
3777 tr(
"Shader directory does not contain library.json or index.txt:\n%1")
3781 QString backendError;
3782 const std::optional<acmx2::Backend> libraryBackend =
3784 if (!backendError.isEmpty()) {
3785 QMessageBox::warning(
this, tr(
"Invalid Backend Metadata"), backendError);
3789 const QMessageBox::StandardButton reply = QMessageBox::question(
3790 this, tr(
"Switch Backend"),
3791 tr(
"This library targets %1, but the active backend is %2.\n\n"
3792 "Switch to %1 and load it?")
3795 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
3796 if (reply != QMessageBox::Yes)
3801 QString libraryTypeError;
3803 if (!libraryTypeError.isEmpty()) {
3804 QMessageBox::warning(
this, tr(
"Invalid Library Type"),
3810 Log(tr(
"Warning: Could not load shaders from directory: %1")
3815 QSettings settings(
"LostSideDead");
3819 settings.setValue(
"shaders", libraryPath);
3822 Log(tr(
"Successfully loaded shader library: %1").arg(libraryPath));
3828 const QString trimmedPath = path.trimmed();
3829 if (trimmedPath.isEmpty())
3831 const QString libraryPath = QDir::cleanPath(trimmedPath);
3833 QSettings settings(
"LostSideDead");
3834 const QString recentKey =
3837 ? settings.value(
"recentLibraries")
3840 QStringList recentLibraries =
3841 settings.value(recentKey, legacyRecent).toStringList();
3842 for (
auto it = recentLibraries.begin(); it != recentLibraries.end();) {
3843 if (QDir::cleanPath(*it).compare(libraryPath, Qt::CaseInsensitive) == 0)
3844 it = recentLibraries.erase(it);
3848 recentLibraries.prepend(libraryPath);
3849 while (recentLibraries.size() > RECENT_LIBRARY_LIMIT)
3850 recentLibraries.removeLast();
3851 settings.setValue(recentKey, recentLibraries);
3853 settings.setValue(
"recentLibraries", recentLibraries);
3863 QSettings settings(
"LostSideDead");
3864 const QString recentKey =
3867 ? settings.value(
"recentLibraries")
3870 const QStringList recentLibraries =
3871 settings.value(recentKey, legacyRecent).toStringList();
3872 if (recentLibraries.isEmpty()) {
3873 QAction *emptyAction =
loadRecentMenu->addAction(tr(
"No Recent Libraries"));
3874 emptyAction->setEnabled(
false);
3878 for (
const QString &path : recentLibraries) {
3880 connect(action, &QAction::triggered,
this,
3887 PropWindow propWindow(propertiesBackend,
this);
3888 if (propWindow.exec() == QDialog::Accepted) {
3892 QString compilerMode;
3893 QString compilerPath;
3900 if (compilerMode == QStringLiteral(
"custom") &&
3901 compilerPath.isEmpty()) {
3902 QMessageBox::information(
3903 this, tr(
"Shader Compiler"),
3904 tr(
"Select a custom glslc-compatible compiler path."));
3908 if (exePath.length() == 0) {
3909 QMessageBox::information(
this,
"No Path",
"Requires Executable path");
3912 if (shaderDir.length() == 0) {
3913 QMessageBox::information(
this,
"Shader Path",
"Requires Shader Path");
3920 QSettings appSettings(
"LostSideDead");
3922 appSettings.setValue(
3926 appSettings.setValue(
"exePath", exePath);
3929 appSettings.setValue(
3932 "shader_compiler_mode"),
3933 compilerMode.isEmpty() ? QStringLiteral(
"auto")
3935 appSettings.setValue(
3938 "shader_compiler_path"),
3942 Log(tr(
"Backend changed while loading the library; retained the "
3943 "%1 executable setting.")
3946 appSettings.setValue(
"prefix_path", prefix);
3952 Log(
"Prefix Path: " + prefix);
3953 Log(
"Shader Directory: " + shaderDir);
3962 QMessageBox::information(
this, tr(
"Custom Uniforms"),
3963 tr(
"Load a shader library first."));
3966 const QString jsonPath = QDir(
shader_path).filePath(
"library.json");
3967 if (!QFileInfo(jsonPath).isFile()) {
3968 QMessageBox::warning(
3969 this, tr(
"Custom Uniforms"),
3970 tr(
"Custom uniforms require a library.json manifest."));
3976 QMessageBox::warning(
this, tr(
"Could Not Load Custom Uniforms"), error);
3998 if (manifestPath.isEmpty()) {
3999 QMessageBox::warning(
this,
"Could not open shader manifest",
4000 "No library.json or index.txt found in: " + path);
4005 QFileInfo(manifestPath).fileName().compare(
"index.txt", Qt::CaseInsensitive) == 0) {
4006 bool generated =
false;
4007 QString migrationError;
4010 Log(
"Could not generate library.json from index.txt: " + migrationError);
4011 }
else if (generated) {
4013 Log(
"Generated library.json from index.txt");
4017 QDateTime modified = QFileInfo(manifestPath).lastModified();
4022 QStringList manifestEntries;
4023 QString manifestError;
4025 QMessageBox::warning(
this,
"Could not open shader manifest", manifestError);
4033 QFileInfo(manifestPath).fileName().compare(
"library.json", Qt::CaseInsensitive) == 0) {
4034 QString uniformError;
4036 Log(
"Could not load custom uniforms: " + uniformError);
4042 QStringList uniqueItems;
4043 for (
const QString &rawEntry : manifestEntries) {
4044 const QString line = rawEntry.trimmed();
4046 if (line.isEmpty()) {
4050 if (shaderEntry.isEmpty()) {
4051 Log(
"Skipping invalid shader path in " + QFileInfo(manifestPath).fileName() +
": " + line);
4054 QString fullPath = path +
"/" + shaderEntry;
4055 QFileInfo fileInfo(fullPath);
4056 if (!fileInfo.exists() || !fileInfo.isFile()) {
4057 Log(
"Skipping non-existent file: " + shaderEntry);
4060 if (!uniqueItems.contains(shaderEntry, Qt::CaseInsensitive)) {
4061 uniqueItems.append(shaderEntry);
4063 Log(
"Skipping duplicate shader: " + shaderEntry);
4066 items = uniqueItems;
4068 Log(
"Loaded " + QString::number(
items.size()) +
" unique shader files");
4072 if (!
items.isEmpty()) {
4073 int restoredRow = previousRow;
4074 if (restoredRow < 0 || restoredRow >=
items.size()) {
4075 if (!previouslySelected.isEmpty() &&
items.contains(previouslySelected)) {
4076 restoredRow =
items.indexOf(previouslySelected);
4088 QApplication::quit();
4093 QMessageBox::information(
this, tr(
"Audio Settings"),
4094 tr(
"Audio support is unavailable: acmx2 was built without audio support."));
4097 const QString previousAudioFile =
audio_file;
4103 if (audio_set.exec() == QDialog::Accepted) {
4122 Log(
"Audio Settings Saved");
4123#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
4124 const bool liveAudioSettingsChanged =
4126 QFileInfo(previousAudioFile).absoluteFilePath() ||
4131 if (shaderSelectionShm &&
process &&
4133 liveAudioSettingsChanged) {
4134 const QByteArray path =
4135 QFileInfo(
audio_file).absoluteFilePath().toUtf8();
4139 Log(
"Audio file path is too long for live playback: " +
4142 acmx2::ipc::ShaderSelectionLock lock(
4143 shaderSelectionSemaphore);
4145 Log(
"Could not lock the live playback control channel");
4148 std::fill(std::begin(shaderSelectionShm->audio_file_path),
4149 std::end(shaderSelectionShm->audio_file_path),
'\0');
4150 std::copy(path.cbegin(), path.cend(),
4151 shaderSelectionShm->audio_file_path);
4152 shaderSelectionShm->audio_output_device =
audio_output;
4153 shaderSelectionShm->audio_pass_through =
4155 shaderSelectionShm->audio_trunc =
audio_trunc ? 1 : 0;
4156 shaderSelectionShm->audio_repeat =
audio_repeat ? 1 : 0;
4157 ++shaderSelectionShm->audio_file_sequence;
4158 ++shaderSelectionShm->sequence;
4169 QMessageBox::information(
this, tr(
"GPU Filter Settings"),
4170 tr(
"GPU filters are unavailable: acmx2 was built without CUDA support."));
4185 auto applyGpuDialogSettings = [
this](
bool enabled,
const QString &filters,
int bufferSize) {
4192 QSettings(
"LostSideDead",
"acmx2")
4193 .setValue(
"deep_dream/gpu_filter_first",
false);
4194 Log(
"Deep Dream pipeline order reset because GPU filtering was disabled");
4199 Log(
"GPU Filtering Disabled");
4205 [applyGpuDialogSettings](
bool enabled,
const QString &filterArgument,
int bufferSize) {
4206 applyGpuDialogSettings(enabled, filterArgument, bufferSize);
4208 connect(dialog, &QDialog::accepted,
this,
4209 [dialog, applyGpuDialogSettings]() {
4217 dialog->activateWindow();
4223 QMessageBox::information(
4224 this, tr(
"Deep Dream Settings"),
4225 tr(
"Deep Dream is unavailable: ACMXVK must be built with "
4226 "-DWITH_DEEP_DREAM=ON and CUDA-enabled LibTorch."));
4237 const bool gpu_filter_configured =
4269 Log(tr(
"Deep Dream Settings Applied: %1/%2, %3 "
4270 "iteration(s), rotation %4 degrees, %5, %6")
4276 ? tr(
"acidcam-gpu first")
4277 : tr(
"Deep Dream first"))
4279 ? tr(
"independent-frame preview")
4280 : tr(
"temporal feedback")));
4282 Log(
"Deep Dream Disabled");
4287 dialog->activateWindow();
4297 error = tr(
"The selected ACMXVK executable does not provide Deep "
4302 error = tr(
"The configured Deep Dream model does not exist:\n%1")
4307 error = tr(
"Select a Deep Dream feature layer.");
4312 error = tr(
"Independent-frame preview requires video input and an "
4313 "enabled video output file.");
4321 error = tr(
"Running acidcam-gpu before Deep Dream requires an enabled "
4322 "GPU filter chain.");
4326 error = tr(
"Running acidcam-gpu before Deep Dream currently supports "
4327 "camera and video input, not still graphics.");
4331 error = tr(
"Running acidcam-gpu before Deep Dream cannot be combined "
4332 "with Maximize FPS.");
4336 error = tr(
"Running acidcam-gpu before Deep Dream cannot be combined "
4337 "with an ONNX input effect.");
4351 arguments <<
"--dream-iterations"
4353 arguments <<
"--dream-strength"
4355 arguments <<
"--dream-feedback"
4357 arguments <<
"--dream-zoom"
4359 arguments <<
"--dream-rotation"
4361 arguments <<
"--dream-size"
4364 arguments <<
"--dream-fp16";
4366 arguments <<
"--dream-channel"
4371 arguments <<
"--dream-octave-scale"
4374 arguments <<
"--dream-smoothing"
4377 arguments <<
"--gpu-filter-before-dream";
4380 arguments <<
"--deep-orig";
4386 QMessageBox::information(
this, tr(
"MIDI Settings"),
4387 tr(
"MIDI support is unavailable: acmx2 was built without MIDI support."));
4391 if (midiDialog.exec() == QDialog::Accepted) {
4395 QSettings appSettings(
"LostSideDead");
4402 Log(
"MIDI Disabled");
4409 QSettings appSettings(
"LostSideDead");
4417 dlg.setWindowTitle(tr(
"Watermark Settings"));
4420 auto *enableCheck =
new QCheckBox(tr(
"Enable watermark in recorded video"), &dlg);
4424 textEdit->setPlaceholderText(tr(
"Watermark text (shown upper-left of recorded video)"));
4426 auto *colorPreview =
new QLabel(&dlg);
4427 colorPreview->setAutoFillBackground(
true);
4428 colorPreview->setMinimumSize(80, 24);
4429 colorPreview->setFrameStyle(QFrame::Box | QFrame::Plain);
4430 colorPreview->setAlignment(Qt::AlignCenter);
4433 auto applyPreview = [colorPreview, &curR, &curG, &curB]() {
4434 QPalette pal = colorPreview->palette();
4435 pal.setColor(QPalette::Window, QColor(curR, curG, curB));
4436 QColor fg = (curR * 0.299 + curG * 0.587 + curB * 0.114) > 140 ? Qt::black : Qt::white;
4437 pal.setColor(QPalette::WindowText, fg);
4438 colorPreview->setPalette(pal);
4439 colorPreview->setText(QString(
" %1, %2, %3 ").arg(curR).arg(curG).arg(curB));
4443 auto *colorBtn =
new QPushButton(tr(
"Choose Color..."), &dlg);
4444 QObject::connect(colorBtn, &QPushButton::clicked, &dlg, [&]() {
4445 QColor chosen = QColorDialog::getColor(QColor(curR, curG, curB), &dlg, tr(
"Watermark Color"));
4446 if (chosen.isValid()) {
4447 curR = chosen.red();
4448 curG = chosen.green();
4449 curB = chosen.blue();
4454 auto *form =
new QFormLayout();
4455 form->addRow(enableCheck);
4456 form->addRow(tr(
"Text:"), textEdit);
4457 auto *colorRow =
new QHBoxLayout();
4458 colorRow->addWidget(colorPreview, 1);
4459 colorRow->addWidget(colorBtn);
4460 form->addRow(tr(
"Color:"), colorRow);
4462 auto *buttons =
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
4463 QObject::connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
4464 QObject::connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
4466 auto *layout =
new QVBoxLayout(&dlg);
4467 layout->addLayout(form);
4468 layout->addWidget(buttons);
4470 if (dlg.exec() != QDialog::Accepted) {
4480 QSettings appSettings(
"LostSideDead");
4487 Log(QString(
"Watermark %1: \"%2\" color=%3,%4,%5")
4498 QMessageBox::information(
this,
"Load Shaders First",
4499 "Please load a shader library before configuring multi-pass shaders.");
4505 if (
items.isEmpty()) {
4506 QMessageBox::information(
this,
"Load Shaders First",
4507 "Please load a shader library before configuring multi-pass shaders.");
4527 auto applyMultipassSettings = [
this, dialog]() {
4532 Log(
"Multi-Pass Shader Settings Saved: " + QString::number(
shader_pass_names.size()) +
" passes");
4534 Log(
"Multi-Pass Shader Disabled");
4539 [
this](
bool enabled,
const QStringList &selectedShaderNames) {
4544 Log(
"Multi-Pass Shader Settings Saved: " + QString::number(
shader_pass_names.size()) +
" passes");
4546 Log(
"Multi-Pass Shader Disabled");
4550 [
this](
const QString &shaderName) {
4552 if (!safeName.isEmpty())
4555 connect(dialog, &QDialog::accepted,
this, applyMultipassSettings);
4559 dialog->activateWindow();
4564 QMessageBox::information(
this,
"Load Shaders First",
4565 "Please load a shader library before configuring playlist.");
4571 if (
items.isEmpty()) {
4572 QMessageBox::information(
this,
"Load Shaders First",
4573 "Please load a shader library before configuring playlist.");
4600 connect(dialog, &QDialog::accepted,
this, [
this, dialog]() {
4607 QSettings appSettings(
"LostSideDead");
4611 Log(
"Playlist Settings Saved: " + QString::number(
playlist_names.size()) +
" shaders");
4616 Log(QString(
"Autopilot timeout mode: %1 (%2 frames)")
4621 Log(
"Playlist Disabled");
4627 dialog->activateWindow();
4634 if (settingsWindow.exec() == QDialog::Accepted) {
4716 if (
process->state() == QProcess::Running) {
4717 QMessageBox::information(
this,
"Process Running",
"A process is already running. Please stop it first.");
4721 QString deep_dream_error;
4723 QMessageBox::warning(
this, tr(
"Deep Dream Settings"),
4729 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
4730 QString uid = QString::number(getuid());
4731 QString user_run_path =
"/run/user/" + uid;
4734 QByteArray display = qgetenv(
"DISPLAY");
4735 QByteArray waylandDisplay = qgetenv(
"WAYLAND_DISPLAY");
4736 QByteArray sessionType = qgetenv(
"XDG_SESSION_TYPE");
4737 if (!display.isEmpty()) {
4738 env.insert(
"SDL_VIDEODRIVER",
"x11");
4739 }
else if (!waylandDisplay.isEmpty() || sessionType ==
"wayland") {
4740 env.insert(
"SDL_VIDEODRIVER",
"wayland");
4742 if (QDir(user_run_path).exists()) {
4743 env.insert(
"XDG_RUNTIME_DIR", user_run_path);
4744 env.insert(
"PULSE_SERVER",
"unix:" + user_run_path +
"/pulse/native");
4746 env.insert(
"vblank_mode",
"0");
4747 process->setProcessEnvironment(env);
4751 QMessageBox::information(
this,
"Select Shaders",
"Select Shader Path");
4759 if (data.isEmpty()) {
4760 Log(
"<b>No item selected.</b>");
4764 QString launchShaderName = data;
4766 QString runtimeError;
4767 if (!resolve_acmxvk_runtime_library(
shader_path, launchShaderPath,
4774 launchShaderName = acmxvk_runtime_shader_name(data);
4775 if (!QFileInfo(QDir(launchShaderPath).filePath(launchShaderName)).isFile()) {
4776 QMessageBox::warning(
4777 this, tr(
"Build ACMXVK Library"),
4778 tr(
"The compiled shader is missing. Choose Playback > Build "
4779 "and try again.\n\n%1")
4780 .arg(QDir(launchShaderPath).filePath(launchShaderName)));
4784 QStringList arguments;
4785 QString dirPath = QCoreApplication::applicationDirPath();
4793 if (selectedIndex < 0 || selectedIndex >=
items.size()) {
4794 Log(
"<b>No valid shader selection.</b>");
4798 arguments <<
"--unbuffered";
4799 arguments <<
"--path" << dirPath;
4803 arguments <<
"--shaders" << launchShaderPath <<
"--shader-file"
4804 << launchShaderName <<
"--interface-shm";
4808 arguments <<
"--fragment" << (
shader_path +
"/" + data)
4809 <<
"--interface-shm";
4815 arguments <<
"--texture-cache-array";
4816 const QSize effectiveCameraResolution =
4819 QTextStream stream(&res);
4820 stream << effectiveCameraResolution.width() <<
"x"
4821 << effectiveCameraResolution.height();
4824 QTextStream stream_r(&scr_res);
4828 arguments <<
"--fullscreen";
4833 arguments <<
"--resolution" << scr_res;
4834 arguments <<
"--fps" << QString::number(
output_fps);
4836 arguments <<
"--camera-res" << res;
4838 arguments <<
"--resolution" << scr_res;
4839 arguments <<
"--device" << QString::number(
camera_index);
4840 arguments <<
"--fps" << QString::number(
output_fps);
4842 arguments <<
"--maximize-fps";
4844 arguments <<
"--use-yuv";
4846 arguments <<
"--texture-cache";
4847 arguments <<
"--cache-delay" << QString::number(
cache_delay);
4852 arguments <<
"--use-source-fps";
4854 arguments <<
"--use-source-audio";
4857 arguments <<
"--resolution" << scr_res;
4859 arguments <<
"--repeat";
4861 arguments <<
"--texture-cache";
4862 arguments <<
"--cache-delay" << QString::number(
cache_delay);
4865 arguments <<
"--copy-audio";
4875 arguments <<
"--encode-crf" << QString::number(
encode_crf);
4885 arguments <<
"--encode-realtime";
4888 arguments <<
"--no-drop";
4890 const bool sourceAudioActive =
4894 arguments <<
"--enable-audio";
4898 arguments <<
"--audio-input" <<
"default";
4900 arguments <<
"--audio-input" << QString::number(
audio_input);
4906 wavPath = fi.absolutePath() +
"/" + fi.completeBaseName() +
".wav";
4910 arguments <<
"--record-audio" << wavPath;
4911 arguments <<
"--record-gain" << QString::number(
record_volume,
'f', 2);
4917 arguments <<
"--mute-output";
4922 arguments <<
"--sense" << QString::number(
audio_sense);
4924 arguments <<
"--pass-through";
4926 arguments <<
"--audio-output" <<
"default";
4928 arguments <<
"--audio-output" << QString::number(
audio_output);
4935 arguments <<
"--audio-trunc";
4938 arguments <<
"--audio-repeat";
4948 arguments <<
"--audio-warm-rate" << QString::number(
audio_warm_rate,
'f', 2);
4952 arguments <<
"--enable-3d";
4968 arguments <<
"--cuda-device" << QString::number(
cuda_device);
4971 arguments <<
"--time-speed"
4972 << QString::number(
static_cast<double>(
time_speed),
'f', 2);
4974 arguments <<
"--normalized";
4978 arguments <<
"--no-cache";
4984 arguments <<
"--midi-device" << QString::number(
midi_device);
4988 arguments <<
"--duration" << QString::number(
max_duration,
'f', 1);
4992 arguments <<
"--max-size" << QString::number(
max_size_mb,
'f', 2);
4996 arguments <<
"--cross-fade" << QString::number(
static_cast<double>(
cross_fade_duration),
'f', 2);
5000 arguments <<
"--flip";
5008 arguments <<
"--png";
5017 arguments <<
"--use-watermark-color"
5022 arguments <<
"--display-filter";
5029 if (!
process->waitForStarted()) {
5030 Log(
"<b style='color:red;'>Failed to start the program.</b>");
5031 QMessageBox::critical(
this,
"Error",
"Failed to start the program.");
5039 QString deep_dream_error;
5041 QMessageBox::warning(
this, tr(
"Deep Dream Settings"),
5046 QMessageBox::information(
this,
"Select Shaders",
"Select Shader Path");
5053 Log(
"No selection, defaulting to index 0");
5057 Log(
"Selected shader: " + selectedData +
" at index: " + QString::number(index));
5059 if (
items.isEmpty()) {
5060 QMessageBox::warning(
this, tr(
"Empty Shader Library"),
5061 tr(
"The selected shader library contains no shaders."));
5064 if (index < 0 || index >=
items.size()) {
5065 QMessageBox::warning(
this, tr(
"Invalid Shader Selection"),
5066 tr(
"Select a shader from the active library."));
5070 QString launchShaderName =
items.at(index);
5072 QString runtimeError;
5074 if (is_acmxvk_source_library(
shader_path, runtimeError)) {
5075 launchShaderPath = acmxvk_build_directory(
shader_path);
5077 acmxvk_runtime_shader_name(launchShaderName);
5078 }
else if (!runtimeError.isEmpty()) {
5079 QMessageBox::warning(
this, tr(
"ACMXVK Library"),
5084 if (!resolve_acmxvk_runtime_library(
shader_path, launchShaderPath,
5091 acmxvk_runtime_shader_name(launchShaderName);
5092 if (!QFileInfo(QDir(launchShaderPath)
5093 .filePath(launchShaderName))
5095 QMessageBox::warning(
5096 this, tr(
"Build ACMXVK Library"),
5097 tr(
"The compiled shader is missing. Choose Playback > "
5098 "Build and try again.\n\n%1")
5099 .arg(QDir(launchShaderPath)
5100 .filePath(launchShaderName)));
5105 QString dirPath = QCoreApplication::applicationDirPath();
5113 QString shader_file = launchShaderPath;
5115 arguments <<
"--unbuffered";
5116 arguments <<
"--path" << dirPath <<
"--shaders" << shader_file;
5117 arguments <<
"--interface-shm";
5121 arguments <<
"--texture-cache-array";
5122 const QSize effectiveCameraResolution =
5125 QTextStream stream(&res);
5126 stream << effectiveCameraResolution.width() <<
"x"
5127 << effectiveCameraResolution.height();
5129 QTextStream stream_r(&scr_res);
5133 arguments <<
"--fullscreen";
5138 arguments <<
"--resolution" << scr_res;
5139 arguments <<
"--fps" << QString::number(
output_fps);
5141 arguments <<
"--camera-res" << res;
5143 arguments <<
"--resolution" << scr_res;
5144 arguments <<
"--device" << QString::number(
camera_index);
5145 arguments <<
"--fps" << QString::number(
output_fps);
5147 arguments <<
"--maximize-fps";
5149 arguments <<
"--use-yuv";
5151 arguments <<
"--texture-cache";
5152 arguments <<
"--cache-delay" << QString::number(
cache_delay);
5157 arguments <<
"--use-source-fps";
5159 arguments <<
"--use-source-audio";
5162 arguments <<
"--resolution" << scr_res;
5164 arguments <<
"--repeat";
5166 arguments <<
"--texture-cache";
5167 arguments <<
"--cache-delay" << QString::number(
cache_delay);
5170 arguments <<
"--copy-audio";
5179 arguments <<
"--encode-crf" << QString::number(
encode_crf);
5189 arguments <<
"--encode-realtime";
5192 arguments <<
"--no-drop";
5194 arguments <<
"--shader-file" << launchShaderName;
5196 const bool sourceAudioActive =
5200 arguments <<
"--enable-audio";
5204 arguments <<
"--audio-input" <<
"default";
5206 arguments <<
"--audio-input" << QString::number(
audio_input);
5212 wavPath = fi.absolutePath() +
"/" + fi.completeBaseName() +
".wav";
5216 arguments <<
"--record-audio" << wavPath;
5217 arguments <<
"--record-gain" << QString::number(
record_volume,
'f', 2);
5223 arguments <<
"--mute-output";
5228 arguments <<
"--sense" << QString::number(
audio_sense);
5230 arguments <<
"--pass-through";
5232 arguments <<
"--audio-output" <<
"default";
5234 arguments <<
"--audio-output" << QString::number(
audio_output);
5241 arguments <<
"--audio-trunc";
5244 arguments <<
"--audio-repeat";
5254 arguments <<
"--audio-warm-rate" << QString::number(
audio_warm_rate,
'f', 2);
5258 arguments <<
"--enable-3d";
5275 if (!passIndices.isEmpty()) {
5276 QStringList passFiles;
5277 const QStringList indexValues = passIndices.split(
',');
5278 for (
const QString &indexValue : indexValues) {
5280 const int passIndex = indexValue.toInt(&ok);
5281 if (ok && passIndex >= 0 && passIndex <
items.size()) {
5282 const QString passFile =
items.at(passIndex);
5285 : acmxvk_runtime_shader_name(passFile));
5288 QByteArray passFilePayload;
5289 for (
const QString &passFile : passFiles) {
5290 const QByteArray encodedName = passFile.toUtf8();
5291 passFilePayload.append(QByteArray::number(encodedName.size()));
5292 passFilePayload.append(
':');
5293 passFilePayload.append(encodedName);
5295 arguments <<
"--shader-pass-files"
5296 << QString::fromUtf8(passFilePayload);
5301 arguments <<
"--cuda-device" << QString::number(
cuda_device);
5304 arguments <<
"--time-speed"
5305 << QString::number(
static_cast<double>(
time_speed),
'f', 2);
5307 arguments <<
"--normalized";
5311 arguments <<
"--no-cache";
5317 arguments <<
"--midi-device" << QString::number(
midi_device);
5321 if (playlistActive) {
5323 if (plFile.isEmpty()) {
5327 if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
5328 QTextStream out(&f);
5331 out <<
"[" << nodeName <<
"]\n";
5332 for (
const QString &name : shaders) {
5335 : acmxvk_runtime_shader_name(name))
5343 : acmxvk_runtime_shader_name(name))
5350 arguments <<
"--playlist" << plFile;
5354 arguments << (
autopilot_random ?
"--autopilot-random" :
"--autopilot-frames")
5359 arguments <<
"--duration" << QString::number(
max_duration,
'f', 1);
5363 arguments <<
"--max-size" << QString::number(
max_size_mb,
'f', 2);
5367 arguments <<
"--cross-fade" << QString::number(
static_cast<double>(
cross_fade_duration),
'f', 2);
5371 arguments <<
"--flip";
5379 arguments <<
"--png";
5388 arguments <<
"--use-watermark-color"
5393 arguments <<
"--display-filter";
5404 Log(
"<b style='color:red;'>HDR10 conversion already running; skipping.</b>");
5408 Log(
"<b style='color:red;'>HDR10 conversion: source file missing.</b>");
5413 const QString suffix = fi.suffix();
5414 const QString hdr10Path = fi.absolutePath() +
"/" + fi.completeBaseName() +
5415 ".HDR10" + (suffix.isEmpty() ? QString() :
"." + suffix);
5426 if (codecChoice ==
"software" || codecChoice ==
"libx265" || codecChoice ==
"x265") {
5428 }
else if (codecChoice ==
"nvenc" || codecChoice ==
"hevc_nvenc") {
5438 QString nvencPreset;
5440 if (p ==
"ultrafast")
5442 else if (p ==
"superfast")
5444 else if (p ==
"veryfast")
5446 else if (p ==
"faster")
5448 else if (p ==
"fast")
5450 else if (p ==
"medium")
5452 else if (p ==
"slow")
5454 else if (p ==
"slower")
5456 else if (p ==
"veryslow")
5458 else if (p.startsWith(
"p") && p.size() == 2 && p[1].isDigit())
5463 args <<
"-vf" <<
"zscale=p=bt2020:t=smpte2084:m=bt2020nc,format=p010le"
5464 <<
"-c:v" <<
"hevc_nvenc"
5465 <<
"-preset" << nvencPreset
5468 <<
"-maxrate" <<
"60M"
5469 <<
"-bufsize" <<
"60M"
5470 <<
"-color_primaries" <<
"bt2020"
5471 <<
"-colorspace" <<
"bt2020nc"
5472 <<
"-color_trc" <<
"smpte2084";
5473 Log(
"HDR10 codec: hevc_nvenc (CUDA detected, preset=" + nvencPreset +
")<br>");
5475 args <<
"-vf" <<
"zscale=p=bt2020:t=smpte2084:m=bt2020nc,format=yuv420p10le"
5476 <<
"-c:v" <<
"libx265"
5479 <<
"-maxrate" <<
"60M"
5480 <<
"-bufsize" <<
"60M"
5481 <<
"-pix_fmt" <<
"yuv420p10le"
5483 <<
"hdr10=1:hdr10-opt=1:repeat-headers=1:"
5484 "colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:range=limited:"
5485 "master-display=G(8500,39850)B(6550,2300)R(35400,14600)WP(15635,16450)L(10000000,1):"
5487 <<
"-color_primaries" <<
"bt2020"
5488 <<
"-colorspace" <<
"bt2020nc"
5489 <<
"-color_trc" <<
"smpte2084";
5490 Log(
"HDR10 codec: libx265 (codec=" + (codecChoice.isEmpty() ? QStringLiteral(
"auto") : codecChoice) +
")<br>");
5493 args <<
"-c:a" <<
"copy"
5497 Log(
"HDR10 output: " + hdr10Path +
"<br>");
5501 hdr10Process->setProcessChannelMode(QProcess::MergedChannels);
5504 Log(
"<b style='color:red;'>Failed to start ffmpeg for HDR10 conversion.</b>");
5511 if (
process->state() == QProcess::Running) {
5512 QMessageBox::information(
this,
"Process Running",
"A process is already running. Please stop it first.");
5517 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
5518 for (
const QString &entry : defaultLinuxRunEnvAssignments()) {
5519 int eq = entry.indexOf(
'=');
5523 env.insert(entry.left(eq), entry.mid(eq + 1));
5525 process->setProcessEnvironment(env);
5528 QStringList arguments;
5538 if (!
process->waitForStarted()) {
5539 Log(
"<b style='color:red;'>Failed to start the program.</b>");
5540 QMessageBox::critical(
this,
"Error",
"Failed to start the program.");
5547 QStringList arguments;
5554 QStringList envAssignments;
5556 envAssignments = defaultLinuxRunEnvAssignments();
5558 QString commandText = buildShellCommand(envAssignments, exe, arguments).trimmed();
5560 QDialog dialog(
this);
5561 dialog.setWindowTitle(tr(
"Edit Command"));
5566 QVBoxLayout *layout =
new QVBoxLayout(&dialog);
5567 QPlainTextEdit *textBox =
new QPlainTextEdit(&dialog);
5568 textBox->setPlainText(commandText);
5569 textBox->setReadOnly(
false);
5570 textBox->setLineWrapMode(QPlainTextEdit::WidgetWidth);
5572 textBox->setStyleSheet(
"QPlainTextEdit { background-color: black; color: lime; "
5573 "font-size: 14px; font-family: 'Courier New', Courier, monospace; "
5574 "border: 1px solid red; }");
5576 QFont commandFont(
"Courier New");
5577 commandFont.setStyleHint(QFont::Monospace);
5578 commandFont.setPointSize(14);
5579 textBox->setFont(commandFont);
5581 layout->addWidget(textBox);
5584 QSettings settings(
"LostSideDead");
5589 auto *parallelBuildCheckBox =
5590 new QCheckBox(tr(
"Enable parallel build"), &dialog);
5591 auto *parallelBuildJobsSpinBox =
new QSpinBox(&dialog);
5592 parallelBuildJobsSpinBox->setRange(1, 256);
5593 parallelBuildJobsSpinBox->setValue(
5594 qBound(1, settings.value(jobsKey, 2).toInt(), 256));
5595 parallelBuildCheckBox->setChecked(
5596 settings.value(enabledKey,
false).toBool());
5597 parallelBuildJobsSpinBox->setEnabled(
5598 parallelBuildCheckBox->isChecked());
5599 parallelBuildJobsSpinBox->setToolTip(
5600 tr(
"Number of concurrent ACMXVK shader compiler jobs (1-256)."));
5601 auto *parallelBuildLayout =
new QHBoxLayout();
5602 parallelBuildLayout->addWidget(parallelBuildCheckBox);
5603 parallelBuildLayout->addWidget(
new QLabel(tr(
"Jobs:"), &dialog));
5604 parallelBuildLayout->addWidget(parallelBuildJobsSpinBox);
5605 parallelBuildLayout->addStretch(1);
5606 layout->addLayout(parallelBuildLayout);
5607 connect(parallelBuildCheckBox, &QCheckBox::toggled, &dialog,
5608 [parallelBuildJobsSpinBox, enabledKey](
bool enabled) {
5609 parallelBuildJobsSpinBox->setEnabled(enabled);
5610 QSettings settings(
"LostSideDead");
5611 settings.setValue(enabledKey, enabled);
5613 connect(parallelBuildJobsSpinBox,
5614 QOverload<int>::of(&QSpinBox::valueChanged), &dialog,
5615 [jobsKey](
int jobs) {
5616 QSettings settings(
"LostSideDead");
5617 settings.setValue(jobsKey, jobs);
5621 QDialogButtonBox *buttonBox =
new QDialogButtonBox(&dialog);
5622 QPushButton *copyButton = buttonBox->addButton(tr(
"Copy to Clipboard"), QDialogButtonBox::ActionRole);
5623 QPushButton *runButton = buttonBox->addButton(tr(
"Run"), QDialogButtonBox::ActionRole);
5624 QPushButton *okButton = buttonBox->addButton(QDialogButtonBox::Ok);
5625 layout->addWidget(buttonBox);
5627 connect(copyButton, &QPushButton::clicked, &dialog, [textBox, &dialog]() {
5628 const QString copiedText = textBox->toPlainText();
5629 QClipboard *clipboard = QGuiApplication::clipboard();
5630 clipboard->setText(copiedText, QClipboard::Clipboard);
5632 if (clipboard->supportsSelection()) {
5633 clipboard->setText(copiedText, QClipboard::Selection);
5636 QCoreApplication::processEvents();
5637 QMessageBox::information(&dialog, tr(
"Copied"),
5638 tr(
"Command copied to clipboard."));
5640 connect(runButton, &QPushButton::clicked, &dialog, [
this, textBox, &dialog]() {
5641 if (
process->state() != QProcess::NotRunning) {
5642 QMessageBox::information(&dialog, tr(
"Process Running"),
5643 tr(
"A process is already running. Please stop it first."));
5646 QString cmdText = textBox->toPlainText().trimmed();
5647 if (cmdText.isEmpty()) {
5648 QMessageBox::warning(&dialog, tr(
"Empty Command"), tr(
"The command is empty."));
5656 process->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
5658 QString shell = qEnvironmentVariable(
"COMSPEC");
5659 if (shell.isEmpty())
5661 QStringList shellArgs{
"/C", cmdText};
5663 QString shell =
"/bin/sh";
5664 QStringList shellArgs{
"-c", cmdText};
5666 Log(
"shell: " + cmdText +
"<br>");
5668 process->start(shell, shellArgs);
5669 if (!
process->waitForStarted()) {
5670 Log(
"<b style='color:red;'>Failed to start the program.</b>");
5671 QMessageBox::critical(&dialog, tr(
"Error"), tr(
"Failed to start the program."));
5677 connect(okButton, &QPushButton::clicked, &dialog, &QDialog::accept);
5684 QTextStream stream(&text);
5685 for (
auto &i : lst) {
5692 QStringList indices;
5695 int idx =
items.indexOf(name);
5697 indices.append(QString::number(idx));
5700 return indices.join(
",");
5704 QString sanitized = name.trimmed();
5705 sanitized.replace(
'\\',
'/');
5706 sanitized = QDir::cleanPath(sanitized);
5708 while (sanitized.startsWith(
"./")) {
5709 sanitized = sanitized.mid(2);
5712 if (sanitized.isEmpty() || sanitized ==
"." || sanitized ==
"..") {
5713 Log(
"Warning: Invalid shader name detected: " + name);
5717 if (QDir::isAbsolutePath(sanitized) ||
5718 sanitized.startsWith(
"../") ||
5719 sanitized.contains(
"/../") ||
5720 sanitized.endsWith(
"/..")) {
5721 Log(
"Warning: Invalid shader name detected (path traversal attempt): " + name);
5731 [](
const QPointer<TextEditor> &ptr) { return ptr.isNull(); }),
5736 if (
items.isEmpty()) {
5739 std::random_device rd;
5740 std::mt19937 g(rd());
5744 Log(
"Shaders shuffled");
5748 if (
items.isEmpty()) {
5751 items.sort(Qt::CaseInsensitive);
5754 Log(
"Shaders sorted alphabetically");
5759 if (build_path.isEmpty()) {
5760 QSettings appSettings(
"LostSideDead");
5765 ? appSettings.value(
"shaders",
"").toString()
5770 if (build_path.isEmpty()) {
5772 QMessageBox::warning(
this,
"Error",
"No shader library loaded. Please set a shader directory in Properties or load a shader library first.");
5776 if (
process->state() == QProcess::Running) {
5778 QMessageBox::warning(
this,
"Error",
"A process is already running. Please wait for it to finish.");
5789 Log(
"Rebuild Shader Cache is not available on macOS.");
5794 const QString assets_path = resolveAssetsPath();
5797 args <<
"--build" << build_path;
5798 args <<
"-p" << assets_path;
5801 args <<
"--texture-cache-array";
5804 args <<
"--enable-3d";
5807 Log(
"Building shader cache for: " + build_path);
5815 if (!
process->waitForStarted()) {
5816 Log(
"<b style='color:red;'>Error:</b> Failed to start shader cache build process");
5826 if (
process->state() == QProcess::Running) {
5827 QMessageBox::warning(
5828 this, tr(
"Fix Build"),
5829 tr(
"A process is already running. Please wait for it to finish."));
5833 QMessageBox::warning(
5834 this, tr(
"Fix Build"),
5835 tr(
"No shader library is loaded."));
5845 const QString dialogTitle =
5846 prune ? tr(
"Remove Broken Shaders")
5847 : (fix ? tr(
"Fix Build") : tr(
"Build ACMXVK Library"));
5849 if (!is_acmxvk_source_library(build_path, type_error)) {
5851 QMessageBox::warning(
5853 type_error.isEmpty()
5854 ? tr(
"The selected ACMXVK library is already a compiled "
5860 const QString manifest_path =
5861 QDir(build_path).filePath(QStringLiteral(
"library.json"));
5862 if (!QFileInfo(manifest_path).isFile()) {
5864 QMessageBox::warning(
5866 tr(
"ACMXVK source builds require library.json:\n%1")
5867 .arg(manifest_path));
5871 const QString output_path = acmxvk_build_directory(build_path);
5872 QString compilerError;
5873 const QString compiler =
5874 resolve_acmxvk_shader_compiler(compilerError);
5875 if (compiler.isEmpty()) {
5877 Log(tr(
"<b style='color:red;'>Cannot build ACMXVK library: %1</b>")
5878 .arg(compilerError.toHtmlEscaped()));
5879 QMessageBox::warning(
this, tr(
"ACMXVK Shader Compiler"),
5883 QStringList arguments{
"--unbuffered",
"--build", manifest_path};
5884 arguments << (fix ? QStringLiteral(
"--fix")
5885 : QStringLiteral(
"--builddir"))
5887 arguments << QStringLiteral(
"--glslc") << compiler;
5888 QSettings settings(
"LostSideDead");
5889 const bool parallelBuildEnabled =
5895 if (parallelBuildEnabled) {
5896 const int parallelBuildJobs = qBound(
5904 arguments << QStringLiteral(
"--parallel")
5905 << QString::number(parallelBuildJobs);
5908 arguments << QStringLiteral(
"--prune") << QStringLiteral(
"--force");
5909 Log(prune ? tr(
"Removing broken ACMXVK shader sources from: %1")
5911 : (fix ? tr(
"Fix building ACMXVK SPIR-V library: %1")
5913 : tr(
"Building ACMXVK SPIR-V library: %1")
5920 if (!
process->waitForStarted()) {
5921 Log(
"<b style='color:red;'>Error:</b> Failed to start ACMXVK build process");
5939 if (scan_path.isEmpty()) {
5940 QSettings appSettings(
"LostSideDead");
5945 ? appSettings.value(
"shaders",
"").toString()
5949 if (scan_path.isEmpty()) {
5950 QMessageBox::warning(
this,
"Error",
5951 "No shader library loaded. Please set a shader directory in Properties or load a shader library first.");
5954 if (
process->state() == QProcess::Running) {
5955 QMessageBox::warning(
this,
"Error",
5956 "A process is already running. Please wait for it to finish.");
5961 if (manifestPath.isEmpty()) {
5962 QMessageBox::warning(
this,
"Missing Shader Manifest",
5963 "No library.json or index.txt found in: " + scan_path);
5966 const QString manifestName = QFileInfo(manifestPath).fileName();
5970 if (!is_acmxvk_source_library(scan_path, typeError)) {
5971 QMessageBox::warning(
5972 this, tr(
"Remove Broken Shaders"),
5974 ? tr(
"Remove Broken requires an ACMXVK source library, "
5975 "not a compiled runtime library.")
5980 QMessageBox confirmation(
this);
5981 confirmation.setIcon(QMessageBox::Warning);
5982 confirmation.setWindowTitle(tr(
"Permanently Remove Broken Shaders"));
5983 confirmation.setText(
5984 tr(
"This operation permanently deletes source shader files."));
5985 confirmation.setInformativeText(
5986 tr(
"ACMXVK will compile every shader listed in:\n\n%1\n\n"
5987 "Any .frag or .comp source for which glslc reports a compilation "
5988 "failure will be deleted. The generated runtime library and the "
5989 "source library manifest will then omit those shaders.\n\n"
5990 "No backup is created and this operation cannot be undone. "
5991 "Commit or archive the library before continuing.\n\n"
5992 "Do you want to permanently remove the broken sources?")
5994 confirmation.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
5995 confirmation.setDefaultButton(QMessageBox::No);
5996 confirmation.setEscapeButton(QMessageBox::No);
5997 if (confirmation.exec() != QMessageBox::Yes)
6004 QMessageBox::StandardButton reply = QMessageBox::question(
this,
6005 tr(
"Remove Broken Shaders"),
6006 tr(
"This will compile every shader in:\n\n%1\n\n"
6007 "Any shader that fails to compile will be removed from %2 "
6008 "(the original will be backed up as %2.bak).\n\nContinue?")
6009 .arg(scan_path, manifestName),
6010 QMessageBox::Yes | QMessageBox::No);
6011 if (reply != QMessageBox::Yes)
6014 const QString assets_path = resolveAssetsPath();
6017 args <<
"--remove-broken" << scan_path;
6018 args <<
"-p" << assets_path;
6019 args <<
"--texture-cache-size"
6022 args <<
"--texture-cache-array";
6024 args <<
"--enable-3d";
6026 Log(
"Scanning for broken shaders in: " + scan_path);
6031 QProcess *scan =
new QProcess(
this);
6032 scan->setProcessChannelMode(QProcess::SeparateChannels);
6033 connect(scan, &QProcess::readyReadStandardOutput,
this, [
this, scan]() {
6034 QString output = scan->readAllStandardOutput();
6035 output.replace(
"\n",
"<br>");
6036 this->
Write(output);
6038 connect(scan, &QProcess::readyReadStandardError,
this, [
this, scan]() {
6039 QString output = scan->readAllStandardError();
6040 output.replace(
"\n",
"<br>");
6041 this->
Write(
"<b style='color:red;'>" + output +
"</b>");
6044 static_cast<void (QProcess::*)(
int, QProcess::ExitStatus)
>(&QProcess::finished),
6046 [
this, scan, scan_path, manifestName](
int exitCode, QProcess::ExitStatus) {
6047 Log(QString(
"Remove-broken finished with exit code: %1<br>").arg(exitCode));
6048 if (exitCode == 0) {
6051 QMessageBox::information(
this,
6052 tr(
"Remove Broken"),
6053 tr(
"Finished scanning shader library.\n\n"
6054 "%1 has been updated and the shader list reloaded.\n"
6055 "A backup of the original is at:\n%2/%1.bak")
6056 .arg(manifestName, scan_path));
6058 QMessageBox::warning(
this,
6059 tr(
"Remove Broken"),
6060 tr(
"Remove-broken failed with exit code %1. "
6061 "%2 was not changed.")
6063 .arg(manifestName));
6065 scan->deleteLater();
6069 if (!scan->waitForStarted()) {
6070 Log(
"<b style='color:red;'>Error:</b> Failed to start remove-broken process");
6071 scan->deleteLater();
6077 Log(
"Clean Shader Cache is not available on macOS.");
6081 if (libraryPath.isEmpty()) {
6082 QSettings appSettings(
"LostSideDead");
6083 libraryPath = appSettings.value(
"shaders",
"").toString();
6086 if (libraryPath.isEmpty()) {
6087 QMessageBox::warning(
this,
"Error",
"No shader library loaded. Please set a shader directory in Properties or load a shader library first.");
6091 QMessageBox::warning(
this,
"Error",
"A process is running. Stop it before cleaning the shader cache.");
6095 const QMessageBox::StandardButton reply = QMessageBox::question(
6096 this, tr(
"Clean Shader Cache"),
6097 tr(
"Delete all cached shader binaries for:\n\n%1\n\n"
6098 "This will not rebuild the cache. Continue?")
6100 QMessageBox::Yes | QMessageBox::No);
6101 if (reply != QMessageBox::Yes)
6104 const QString assetsPath = resolveAssetsPath();
6105 QStringList cacheFiles;
6106 const auto addCacheFile = [&cacheFiles](
const QString &path) {
6107 if (!cacheFiles.contains(path))
6108 cacheFiles.append(path);
6114 for (
int size = 1; size <= 64; ++size) {
6115 for (
const bool useArray : {
false,
true}) {
6116 const QString filename =
6117 shaderCacheFilename(libraryPath, size, useArray);
6118 addCacheFile(assetsPath +
"/" + filename);
6119 addCacheFile(libraryPath +
"/" + filename);
6125 const std::filesystem::path libraryFsPath(libraryPath.toStdString());
6126 const std::filesystem::path absoluteLibrary =
6127 std::filesystem::absolute(libraryFsPath, ec);
6128 const std::string legacyKey =
6129 ec ? libraryPath.toStdString()
6130 : absoluteLibrary.lexically_normal().string();
6131 std::ostringstream legacyName;
6132 legacyName <<
".shader_cache_" << std::hex
6133 << std::hash<std::string>{}(legacyKey);
6134 const QString legacyHashedName =
6135 QString::fromStdString(legacyName.str());
6136 addCacheFile(assetsPath +
"/" + legacyHashedName);
6137 addCacheFile(libraryPath +
"/" + legacyHashedName);
6138 addCacheFile(libraryPath +
"/.shader_cache");
6140 int removedCount = 0;
6141 int failedCount = 0;
6142 for (
const QString &cacheFile : cacheFiles) {
6143 if (!QFileInfo::exists(cacheFile))
6145 if (QFile::remove(cacheFile)) {
6146 Log(
"Deleted shader cache: " + cacheFile);
6149 Log(
"<b style='color:red;'>Warning:</b> Could not delete cache file: " + cacheFile);
6154 if (removedCount == 0 && failedCount == 0) {
6155 Log(
"No existing shader cache found");
6157 Log(QString(
"Shader cache clean complete: removed %1 file(s), %2 failed")
6171 probe.start(exe, QStringList() << flag);
6172 if (!probe.waitForFinished(5000)) {
6176 return QString::fromLocal8Bit(probe.readAllStandardOutput()).trimmed();
6180 const QString &token) {
6187 const QString cudaOutput =
6190 isAcmxvk ?
"acidcam-gpu filters: enabled" :
"CUDA: enabled",
6191 Qt::CaseInsensitive);
6194 ? cudaOutput.contains(
"MXVK CUDA interop: enabled",
6195 Qt::CaseInsensitive)
6201 isAcmxvk ?
"OpenCV DNN effects: enabled" :
"OpenCV DNN: enabled");
6204 "Deep Dream: enabled");
6206 Log(QString(
"CUDA filters: %1 (%2)")
6209 Log(QString(
"CUDA device interop: %1 (%2)")
6213 Log(QString(
"AUDIO: %1 (%2)")
6215 Log(QString(
"MIDI: %1 (%2)")
6217 Log(QString(
"OpenCV DNN: %1 (%2)")
6220 Log(QString(
"Deep Dream: %1 (%2)")
6236 : tr(
"Disabled: ACMXVK was built without Deep Dream support."));
6247 : tr(
"Disabled: %1 was built without acidcam-gpu filter support.")
6262 : tr(
"Disabled: %1 was built without audio support.")
6279 : tr(
"Disabled: %1 was built without MIDI support.")
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
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.
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.
void publishRuntimeSettingsToRunningProcess()
void menuToggleDisplayFilter(bool checked)
QAction * normalizedTimeAction
QAction * listMenu_findNext
void publishMultipassShadersToRunningProcess()
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.
double deep_dream_rotation
QAction * runFromCacheAction
QAction * shaderPassAction
void menuGPUFilterSettings()
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.
void initShaderSelectionSharedMemory()
void queueAcmxvkLiveCompile(const QString &filePath)
QAction * listMenu_shuffle
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()
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.
int currentShaderRow() const
Return the row index of the current selection, or -1.
QPointer< QDialog > shaderEditorWorkspace
void applyMainViewStyles(bool customStyleEnabled)
void publishRepeatStateToRunningProcess()
QAction * stayOnTopAction
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
CustomUniformDialog * customUniformDialog
QString activeShaderManifestPath
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.
QList< QPair< QString, QStringList > > playlist_tree_data
unsigned int audio_channels
QPointer< DeepDreamSettingsDialog > deepDreamSettingsDialog
bool buildRunArguments(QStringList &arguments, PendingAcmxvkAction resume_action=PendingAcmxvkAction::None)
Build acmx2 command-line arguments from current UI state.
void cleanupShaderSelectionSharedMemory()
QString liveShaderCompileTemporary
void publishShaderReloadToRunningProcess(const QString &filePath)
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 liveShaderCompileOutput
QString encode_parameters
int deep_dream_iterations
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.
QAction * listMenu_set_current
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
QAction * displayFilterAction
void updateRecentLibrariesMenu()
Rebuild the File > Load Recent submenu from persisted settings.
QAction * listMenu_findInFiles
void cleanupShaderSelectionSemaphore()
void menuPlaylistSettings()
bool display_filter_enabled
QString baseAppStyleSheet
void startNextAcmxvkLiveCompile()
QAction * listMenu_remove
float cross_fade_duration
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
bool backend_launch_available() const
Return whether the active backend can be launched.
void menuDeepDreamSettings()
bool cuda_device_available
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
QPointer< LibraryBuilderDialog > libraryBuilderDialog
QString editorPreviewOutput
void queueAcmxvkEditorPreview(const QString &filePath, const QString &source)
bool max_size_limit_enabled
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()
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, 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 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
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 getGraphicsFile() const
bool isUsingGraphicsFile() const
bool isConvertToHdr10Enabled() const
bool isUseSourceFpsEnabled() const
QString getEncodeBitrate() const
QSize getSelectedCameraResolution() const
QString getInputVideoFile() const
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)
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.
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 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 hasPositiveResolution(const QSize &resolution)
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.