mirror of
https://github.com/PancakeTAS/lsfg-vk.git
synced 2025-12-30 11:42:24 +00:00
refactor(cleanup): synchronize configuration state with ui
This commit is contained in:
parent
2f4b0cc5db
commit
effd469b5e
10 changed files with 358 additions and 49 deletions
|
|
@ -1,7 +1,7 @@
|
|||
find_package(Qt6 REQUIRED COMPONENTS Quick)
|
||||
|
||||
set(UI_SOURCES
|
||||
"src/ui.cpp"
|
||||
"src/backend.cpp"
|
||||
"src/main.cpp")
|
||||
|
||||
set(UI_RESOURCES
|
||||
|
|
@ -28,5 +28,6 @@ target_compile_options(lsfg-vk-ui PRIVATE
|
|||
-Wno-unsafe-buffer-usage-in-libc-call
|
||||
-Wno-global-constructors)
|
||||
|
||||
target_link_libraries(lsfg-vk-ui PRIVATE
|
||||
Qt6::Quick)
|
||||
target_link_libraries(lsfg-vk-ui
|
||||
PUBLIC lsfg-vk-common
|
||||
PRIVATE Qt6::Quick)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,51 @@ ApplicationWindow {
|
|||
minimumHeight: 400
|
||||
visible: true
|
||||
|
||||
Dialog {
|
||||
id: dialog_name
|
||||
title: "(...)"
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
modal: true
|
||||
dim: true
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 8
|
||||
|
||||
TextField {
|
||||
Layout.fillWidth: true
|
||||
|
||||
id: nameField
|
||||
placeholderText: "Choose a profile name"
|
||||
selectByMouse: true
|
||||
focus: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dialog {
|
||||
id: dialog_confirm
|
||||
title: "Confirm Deletion"
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
modal: true
|
||||
dim: true
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 8
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: "Are you sure you want to delete the selected profile?"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SplitView {
|
||||
anchors.fill: parent
|
||||
orientation: Qt.Horizontal
|
||||
|
|
@ -30,20 +75,37 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
ProfileList {
|
||||
|
||||
model: backend.profiles
|
||||
selected: backend.profile_index
|
||||
onSelect: (index) => backend.profile_index = index
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: "Create New Profile"
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
dialog_name.title = "Create New Profile"
|
||||
nameField.text = ""
|
||||
|
||||
dialog_name.open()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: "Rename Profile"
|
||||
Layout.fillWidth: true
|
||||
onClicked: {
|
||||
dialog_name.title = "Rename Profile"
|
||||
nameField.text = "(...)"
|
||||
|
||||
dialog_name.open()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Delete Profile"
|
||||
Layout.fillWidth: true
|
||||
text: "Delete Profile"
|
||||
onClicked: {
|
||||
dialog_confirm.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,56 +116,91 @@ ApplicationWindow {
|
|||
name: "Global Settings"
|
||||
|
||||
GroupEntry {
|
||||
title: "Browse for Lossless.dll"
|
||||
title: "Path to Lossless Scaling"
|
||||
description: "Change the location of Lossless.dll"
|
||||
|
||||
FileEdit {}
|
||||
FileEdit {
|
||||
title: "Select Lossless.dll"
|
||||
filter: "Dynamic Link Library Files (*.dll)"
|
||||
|
||||
text: backend.dll
|
||||
onUpdate: (text) => backend.dll = text
|
||||
}
|
||||
}
|
||||
|
||||
GroupEntry {
|
||||
title: "Allow half-precision"
|
||||
description: "Allow acceleration through half-precision"
|
||||
|
||||
CheckBox {}
|
||||
CheckBox {
|
||||
checked: backend.allow_fp16
|
||||
onToggled: backend.allow_fp16 = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Group {
|
||||
name: "Profile Settings"
|
||||
enabled: backend.available
|
||||
|
||||
GroupEntry {
|
||||
title: "Multiplier"
|
||||
description: "Control the amount of generated frames"
|
||||
|
||||
SpinBox { from: 2; to: 100 }
|
||||
SpinBox {
|
||||
from: 2
|
||||
to: 100
|
||||
|
||||
value: backend.multiplier
|
||||
onValueModified: backend.multiplier = value
|
||||
}
|
||||
}
|
||||
|
||||
GroupEntry {
|
||||
title: "Flow Scale"
|
||||
description: "Lower the internal motion estimation resolution"
|
||||
|
||||
FlowSlider { from: 0.25; to: 1.00 }
|
||||
FlowSlider {
|
||||
from: 0.25
|
||||
to: 1.00
|
||||
|
||||
value: backend.flow_scale
|
||||
onUpdate: (value) => backend.flow_scale = value
|
||||
}
|
||||
}
|
||||
|
||||
GroupEntry {
|
||||
title: "Performance Mode"
|
||||
description: "Use a significantly lighter frame generation modeln"
|
||||
|
||||
CheckBox {}
|
||||
CheckBox {
|
||||
checked: backend.performance_mode
|
||||
onToggled: backend.performance_mode = checked
|
||||
}
|
||||
}
|
||||
|
||||
GroupEntry {
|
||||
title: "Pacing Mode"
|
||||
description: "Change how frames are presented to the display"
|
||||
|
||||
ComboBox { model: ["None"] }
|
||||
ComboBox {
|
||||
model: ["None"]
|
||||
|
||||
currentValue: backend.pacing_mode
|
||||
onActivated: (index) => backend.pacing_mode = model[index]
|
||||
}
|
||||
}
|
||||
|
||||
GroupEntry {
|
||||
title: "GPU"
|
||||
description: "Select which GPU to use for frame generation"
|
||||
|
||||
ComboBox { model: ["Auto"] }
|
||||
ComboBox {
|
||||
model: ["Default"]
|
||||
|
||||
currentValue: backend.gpu
|
||||
onActivated: (index) => backend.gpu = model[index]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ import QtQuick
|
|||
import QtQuick.Layouts
|
||||
|
||||
Rectangle {
|
||||
property var model
|
||||
property int selected
|
||||
signal select(int index)
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
id: root
|
||||
|
|
@ -12,5 +16,31 @@ Rectangle {
|
|||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
id: view
|
||||
|
||||
model: root.model
|
||||
currentIndex: root.selected
|
||||
|
||||
delegate: Rectangle {
|
||||
width: view.width
|
||||
height: 32
|
||||
color: ListView.isCurrentItem ? palette.highlight : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
|
||||
text: display
|
||||
color: ListView.isCurrentItem ? palette.highlightedText : palette.text
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.select(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,34 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
|
||||
RowLayout {
|
||||
property string title
|
||||
property string filter
|
||||
property string text
|
||||
signal update(string text)
|
||||
|
||||
id: root
|
||||
spacing: 4
|
||||
|
||||
TextField {
|
||||
Layout.fillWidth: true;
|
||||
Layout.maximumWidth: 450;
|
||||
|
||||
text: root.text
|
||||
onEditingFinished: root.update(text)
|
||||
}
|
||||
|
||||
Button {
|
||||
icon.name: "folder-open"
|
||||
onClicked: picker.open()
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: picker
|
||||
title: root.title
|
||||
nameFilters: [root.filter, "All Files (*)"]
|
||||
onAccepted: root.update(selectedFile.toString().replace("file://", ""))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,30 @@ import QtQuick.Layouts
|
|||
RowLayout {
|
||||
property real from
|
||||
property real to
|
||||
property real value
|
||||
signal update(real value)
|
||||
|
||||
id: root
|
||||
spacing: 4
|
||||
|
||||
Slider {
|
||||
Layout.fillWidth: true;
|
||||
Layout.maximumWidth: 450;
|
||||
|
||||
value: root.value
|
||||
from: root.from
|
||||
to: root.to
|
||||
|
||||
onMoved: root.update(value.toFixed(2))
|
||||
onValueChanged: label.text = (value * 100).toFixed(0) + "%"
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.preferredWidth: 40;
|
||||
|
||||
id: label
|
||||
text: "0%"
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,43 @@
|
|||
#include "backend.hpp"
|
||||
#include "lsfg-vk-common/configuration/config.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringListModel>
|
||||
#include <QStringList>
|
||||
#include <QString>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
using namespace lsfgvk::ui;
|
||||
|
||||
Backend::Backend(QObject* parent) : QObject(parent) {
|
||||
Backend::Backend() {
|
||||
// load configuration
|
||||
ls::Configuration config{false};
|
||||
config.reload();
|
||||
|
||||
this->m_global = config.getGlobalConf();
|
||||
this->m_profiles = config.getProfiles();
|
||||
|
||||
// create profile list model
|
||||
QStringList profiles; // NOLINT (IWYU)
|
||||
for (const auto& profile : this->m_profiles)
|
||||
profiles.append(QString::fromStdString(profile.name));
|
||||
|
||||
this->m_profile_list_model = new QStringListModel(profiles, this);
|
||||
|
||||
// try to select first profile
|
||||
if (!this->m_profiles.empty())
|
||||
this->m_profile_index = 0;
|
||||
|
||||
// spawn saving thread
|
||||
std::thread([this]() {
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
if (!this->m_dirty.exchange(false))
|
||||
continue;
|
||||
|
||||
std::cerr << "configuration updated >:3" << '\n';
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,158 @@
|
|||
#pragma once
|
||||
|
||||
#include "lsfg-vk-common/configuration/config.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringListModel>
|
||||
#include <QString>
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
|
||||
#define getters public
|
||||
#define setters public
|
||||
|
||||
namespace lsfgvk::ui {
|
||||
|
||||
/// Class tying ui and configuration together
|
||||
class Backend : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Backend(QObject* parent = nullptr);
|
||||
private:
|
||||
Q_PROPERTY(QStringListModel* profiles READ calculateProfileListModel NOTIFY refreshUI)
|
||||
Q_PROPERTY(int profile_index READ getProfileIndex WRITE profileSelected NOTIFY refreshUI)
|
||||
Q_PROPERTY(bool available READ isValidProfileIndex NOTIFY refreshUI)
|
||||
|
||||
Q_PROPERTY(QString dll READ getDll WRITE dllUpdated NOTIFY refreshUI)
|
||||
Q_PROPERTY(bool allow_fp16 READ getAllowFP16 WRITE allowFP16Updated NOTIFY refreshUI)
|
||||
|
||||
Q_PROPERTY(size_t multiplier READ getMultiplier WRITE multiplierUpdated NOTIFY refreshUI)
|
||||
Q_PROPERTY(float flow_scale READ getFlowScale WRITE flowScaleUpdated NOTIFY refreshUI)
|
||||
Q_PROPERTY(bool performance_mode READ getPerformanceMode WRITE performanceModeUpdated NOTIFY refreshUI)
|
||||
Q_PROPERTY(QString pacing_mode READ getPacingMode WRITE pacingModeUpdated NOTIFY refreshUI)
|
||||
Q_PROPERTY(QString gpu READ getGPU WRITE gpuUpdated NOTIFY refreshUI)
|
||||
|
||||
public:
|
||||
explicit Backend();
|
||||
|
||||
getters:
|
||||
[[nodiscard]] QStringListModel* calculateProfileListModel() const {
|
||||
return this->m_profile_list_model;
|
||||
}
|
||||
[[nodiscard]] bool isValidProfileIndex() const {
|
||||
return this->m_profile_index >= 0 && std::cmp_less(this->m_profile_index, this->m_profiles.size());
|
||||
}
|
||||
[[nodiscard]] int getProfileIndex() const {
|
||||
return this->m_profile_index;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString getDll() const {
|
||||
return QString::fromStdString(this->m_global.dll.value_or(""));
|
||||
}
|
||||
[[nodiscard]] bool getAllowFP16() const {
|
||||
return this->m_global.allow_fp16;
|
||||
}
|
||||
|
||||
#define VALIDATE_AND_GET_PROFILE(default) \
|
||||
if (!isValidProfileIndex()) return default; \
|
||||
auto& conf = this->m_profiles[static_cast<size_t>(this->m_profile_index)];
|
||||
|
||||
[[nodiscard]] size_t getMultiplier() const {
|
||||
VALIDATE_AND_GET_PROFILE(2)
|
||||
return conf.multiplier;
|
||||
}
|
||||
[[nodiscard]] float getFlowScale() const {
|
||||
VALIDATE_AND_GET_PROFILE(1.0F)
|
||||
return conf.flow_scale;
|
||||
}
|
||||
[[nodiscard]] bool getPerformanceMode() const {
|
||||
VALIDATE_AND_GET_PROFILE(false)
|
||||
return conf.performance_mode;
|
||||
}
|
||||
[[nodiscard]] QString getPacingMode() const {
|
||||
VALIDATE_AND_GET_PROFILE("None")
|
||||
switch (conf.pacing) {
|
||||
case ls::Pacing::None: return "None";
|
||||
}
|
||||
throw std::runtime_error("Unknown pacing type in backend");
|
||||
}
|
||||
[[nodiscard]] QString getGPU() const {
|
||||
VALIDATE_AND_GET_PROFILE("Default")
|
||||
return QString::fromStdString(conf.gpu.value_or("Default"));
|
||||
}
|
||||
|
||||
#undef VALIDATE_AND_GET_PROFILE
|
||||
|
||||
setters:
|
||||
void profileSelected(int idx) {
|
||||
this->m_profile_index = idx;
|
||||
emit refreshUI();
|
||||
}
|
||||
|
||||
#define MARK_DIRTY() \
|
||||
this->m_dirty.store(true, std::memory_order_relaxed); \
|
||||
emit refreshUI();
|
||||
|
||||
void dllUpdated(const QString& dll) {
|
||||
auto& conf = this->m_global;
|
||||
if (dll.trimmed().isEmpty())
|
||||
conf.dll = std::nullopt;
|
||||
else
|
||||
conf.dll = dll.toStdString();
|
||||
MARK_DIRTY()
|
||||
}
|
||||
void allowFP16Updated(bool allow_fp16) {
|
||||
auto& conf = this->m_global;
|
||||
conf.allow_fp16 = allow_fp16;
|
||||
MARK_DIRTY()
|
||||
}
|
||||
|
||||
#define VALIDATE_AND_GET_PROFILE() \
|
||||
if (!isValidProfileIndex()) return; \
|
||||
auto& conf = this->m_profiles[static_cast<size_t>(this->m_profile_index)];
|
||||
|
||||
void multiplierUpdated(size_t multiplier) {
|
||||
VALIDATE_AND_GET_PROFILE()
|
||||
conf.multiplier = multiplier;
|
||||
MARK_DIRTY()
|
||||
}
|
||||
void flowScaleUpdated(float flow_scale) {
|
||||
VALIDATE_AND_GET_PROFILE()
|
||||
conf.flow_scale = flow_scale;
|
||||
MARK_DIRTY()
|
||||
}
|
||||
void performanceModeUpdated(bool performance_mode) {
|
||||
VALIDATE_AND_GET_PROFILE()
|
||||
conf.performance_mode = performance_mode;
|
||||
MARK_DIRTY()
|
||||
}
|
||||
void pacingModeUpdated(const QString& pacing_mode) {
|
||||
VALIDATE_AND_GET_PROFILE()
|
||||
if (pacing_mode == "None")
|
||||
conf.pacing = ls::Pacing::None;
|
||||
MARK_DIRTY()
|
||||
}
|
||||
void gpuUpdated(const QString& gpu) {
|
||||
VALIDATE_AND_GET_PROFILE()
|
||||
if (gpu.trimmed().isEmpty())
|
||||
conf.gpu = std::nullopt;
|
||||
else
|
||||
conf.gpu.emplace(gpu.toStdString());
|
||||
MARK_DIRTY()
|
||||
}
|
||||
|
||||
#undef VALIDATE_AND_GET_PROFILE
|
||||
#undef MARK_DIRTY
|
||||
|
||||
signals:
|
||||
void refreshUI();
|
||||
|
||||
private:
|
||||
ls::GlobalConf m_global;
|
||||
std::vector<ls::GameConf> m_profiles;
|
||||
|
||||
QStringListModel* m_profile_list_model;
|
||||
int m_profile_index{-1};
|
||||
|
||||
std::atomic_bool m_dirty{false};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "ui.hpp"
|
||||
#include "backend.hpp"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
|
|
@ -12,9 +13,9 @@ int main(int argc, char* argv[]) {
|
|||
QGuiApplication::setApplicationDisplayName("lsfg-vk-ui");
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
UI ui;
|
||||
Backend backend;
|
||||
|
||||
engine.rootContext()->setContextProperty("ui", &ui);
|
||||
engine.rootContext()->setContextProperty("backend", &backend);
|
||||
engine.load("qrc:/rsc/UI.qml");
|
||||
|
||||
return QGuiApplication::exec();
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
#include "ui.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <iostream>
|
||||
|
||||
using namespace lsfgvk::ui;
|
||||
|
||||
UI::UI(QObject* parent) : QObject(parent) {
|
||||
std::cerr << "Hello, world!" << '\n';
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace lsfgvk::ui {
|
||||
|
||||
class UI : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UI(QObject* parent = nullptr);
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue