├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── bug_report.md │ └── compatibility_report.md └── workflows │ ├── package_ci.yml │ ├── package.yml │ ├── flatpak.yml │ ├── flatpak_ci.yml │ ├── build.yml │ └── build_ci.yml ├── ui ├── rsc │ ├── icon.png │ ├── gay.pancake.lsfg-vk-ui.desktop │ ├── popup │ │ ├── process_entry.ui │ │ └── process.ui │ ├── window.ui │ ├── lsfg-vk.xml │ ├── entry │ │ └── entry.ui │ ├── pref │ │ ├── switch.ui │ │ ├── dropdown.ui │ │ ├── number.ui │ │ ├── slider.ui │ │ └── entry.ui │ └── pane │ │ └── sidebar.ui ├── build.rs ├── Cargo.toml ├── src │ ├── wrapper │ │ ├── entry.rs │ │ ├── pane.rs │ │ ├── popup.rs │ │ ├── pane │ │ │ ├── sidebar.rs │ │ │ └── main.rs │ │ ├── popup │ │ │ ├── process_entry.rs │ │ │ └── process.rs │ │ ├── entry │ │ │ └── entry.rs │ │ ├── pref │ │ │ ├── slider.rs │ │ │ ├── number.rs │ │ │ ├── switch.rs │ │ │ ├── dropdown.rs │ │ │ └── entry.rs │ │ └── pref.rs │ ├── main.rs │ ├── ui.rs │ ├── utils.rs │ ├── ui │ │ ├── entry_handler.rs │ │ └── sidebar_handler.rs │ ├── wrapper.rs │ └── config │ │ └── structs.rs └── build_appimage.sh ├── .gitattributes ├── framegen ├── .gitattributes ├── .gitignore ├── src │ ├── common │ │ └── exception.cpp │ ├── core │ │ ├── commandpool.cpp │ │ ├── sampler.cpp │ │ ├── fence.cpp │ │ ├── instance.cpp │ │ ├── descriptorpool.cpp │ │ ├── pipeline.cpp │ │ └── buffer.cpp │ └── pool │ │ ├── shaderpool.cpp │ │ └── resourcepool.cpp ├── include │ ├── core │ │ ├── instance.hpp │ │ ├── commandpool.hpp │ │ ├── descriptorpool.hpp │ │ ├── sampler.hpp │ │ ├── pipeline.hpp │ │ ├── shadermodule.hpp │ │ ├── fence.hpp │ │ ├── device.hpp │ │ ├── buffer.hpp │ │ └── semaphore.hpp │ ├── common │ │ └── exception.hpp │ └── pool │ │ ├── shaderpool.hpp │ │ └── resourcepool.hpp ├── LICENSE.md ├── README.md ├── .clang-tidy ├── v3.1_include │ └── v3_1 │ │ ├── shaders │ │ ├── mipmaps.hpp │ │ ├── alpha.hpp │ │ ├── beta.hpp │ │ ├── generate.hpp │ │ ├── gamma.hpp │ │ └── delta.hpp │ │ └── context.hpp ├── v3.1p_include │ └── v3_1p │ │ ├── shaders │ │ ├── mipmaps.hpp │ │ ├── alpha.hpp │ │ ├── beta.hpp │ │ ├── generate.hpp │ │ ├── gamma.hpp │ │ └── delta.hpp │ │ └── context.hpp ├── CMakeLists.txt ├── v3.1_src │ └── shaders │ │ ├── mipmaps.cpp │ │ └── generate.cpp ├── v3.1p_src │ └── shaders │ │ ├── mipmaps.cpp │ │ └── generate.cpp └── public │ ├── lsfg_3_1.hpp │ └── lsfg_3_1p.hpp ├── .gitignore ├── include ├── utils │ └── benchmark.hpp ├── hooks.hpp ├── extract │ ├── dll.hpp │ └── extract.hpp ├── config │ ├── default_conf.hpp │ └── config.hpp ├── mini │ ├── commandpool.hpp │ ├── semaphore.hpp │ ├── image.hpp │ └── commandbuffer.hpp └── context.hpp ├── package ├── alpm.PKGINFO ├── dpkg.control ├── rpm.spec └── package.sh ├── VkLayer_LS_frame_generation.json ├── flatpak ├── VkLayer_LS_frame_generation.patch ├── org.freedesktop.Platform.VulkanLayer.lsfgvk_23.08.yml ├── org.freedesktop.Platform.VulkanLayer.lsfgvk_24.08.yml ├── org.freedesktop.Platform.VulkanLayer.lsfgvk_25.08.yml ├── gay.pancake.lsfg-vk-ui.yml └── gay.pancake.lsfg-vk-ui.metainfo.xml ├── src ├── mini │ ├── commandpool.cpp │ └── semaphore.cpp └── main.cpp ├── LICENSE.md ├── .clang-tidy ├── test └── CMakeLists.txt ├── CMakeLists.txt └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [PancakeTAS] 2 | ko_fi: pancaketas 3 | -------------------------------------------------------------------------------- /ui/rsc/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PancakeTAS/lsfg-vk/HEAD/ui/rsc/icon.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.cpp diff=cpp eol=lf 2 | *.hpp diff=cpp eol=lf 3 | *.md diff=markdown eol=lf 4 | -------------------------------------------------------------------------------- /framegen/.gitattributes: -------------------------------------------------------------------------------- 1 | *.cpp diff=cpp eol=lf 2 | *.hpp diff=cpp eol=lf 3 | *.md diff=markdown eol=lf 4 | -------------------------------------------------------------------------------- /framegen/.gitignore: -------------------------------------------------------------------------------- 1 | # cmake files 2 | /build 3 | 4 | # ide/lsp files 5 | /.zed 6 | /.vscode 7 | /.clangd 8 | /.cache 9 | /.ccls 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # cmake files 2 | /build 3 | 4 | # cargo files 5 | /ui/target 6 | 7 | # ide/lsp files 8 | /.zed 9 | /.vscode 10 | /.clangd 11 | /.cache 12 | /.ccls 13 | -------------------------------------------------------------------------------- /ui/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | glib_build_tools::compile_resources( 3 | &["rsc"], 4 | "rsc/lsfg-vk.xml", 5 | "lsfg-vk.gresource", 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /include/utils/benchmark.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Benchmark { 6 | 7 | /// 8 | /// Run the benchmark. 9 | /// 10 | /// @param width The width of the benchmark. 11 | /// @param height The height of the benchmark. 12 | /// 13 | [[noreturn]] void run(uint32_t width, uint32_t height); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /package/alpm.PKGINFO: -------------------------------------------------------------------------------- 1 | pkgname = lsfg-vk 2 | pkgbase = lsfg-vk 3 | pkgver = ${ALPM_VERSION}-1 4 | pkgdesc = "Lossless Scaling Frame Generation on Linux via DXVK/Vulkan" 5 | url = https://discord.gg/losslessscaling 6 | packager = "PancakeTAS " 7 | arch = x86_64 8 | license = MIT 9 | depend = vulkan-icd-loader 10 | depend = libadwaita 11 | depend = gtk4 12 | depend = glibc 13 | -------------------------------------------------------------------------------- /package/dpkg.control: -------------------------------------------------------------------------------- 1 | Package: lsfg-vk 2 | Description: Lossless Scaling Frame Generation on Linux via DXVK/Vulkan 3 | Homepage: https://discord.gg/losslessscaling 4 | Maintainer: "PancakeTAS " 5 | Version: ${DPKG_VERSION} 6 | Architecture: amd64 7 | Section: utils 8 | Priority: optional 9 | Depends: libc6 (>= 2.38-1), 10 | libvulkan1, 11 | libadwaita-1-0, 12 | libgtk-4-1 13 | -------------------------------------------------------------------------------- /ui/rsc/gay.pancake.lsfg-vk-ui.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Name=lsfg-vk Configuration Window 5 | Comment=Easy to use configuration editor for lsfg-vk. 6 | Exec=lsfg-vk-ui %U 7 | Icon=gay.pancake.lsfg-vk-ui 8 | Terminal=false 9 | Categories=GTK;Settings; 10 | Keywords=gaming;graphics;configuration; 11 | StartupNotify=true 12 | StartupWMClass=gay.pancake.lsfg-vk-ui 13 | MimeType=application/x-lsfg-profile; 14 | -------------------------------------------------------------------------------- /ui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lsfg-vk-ui" 3 | edition = "2021" 4 | 5 | [dependencies] 6 | gtk = { version = "0.10.0", package = "gtk4", features = ["v4_10"] } 7 | adw = { version = "0.8.0", package = "libadwaita", features = ["v1_4"] } 8 | serde = { version = "1.0", features = ["derive"] } 9 | toml = "0.9.2" 10 | anyhow = "1.0" 11 | procfs = "0.17.0" 12 | proc-maps = "0.4.0" 13 | 14 | [build-dependencies] 15 | glib-build-tools = "0.21.0" 16 | -------------------------------------------------------------------------------- /ui/src/wrapper/entry.rs: -------------------------------------------------------------------------------- 1 | use gtk::glib; 2 | use gtk; 3 | 4 | pub mod entry; 5 | 6 | glib::wrapper! { 7 | pub struct Entry(ObjectSubclass) 8 | @extends 9 | gtk::ListBoxRow, gtk::Widget, 10 | @implements 11 | gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; 12 | } 13 | 14 | impl Entry { 15 | pub fn new() -> Self { 16 | glib::Object::new() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /include/hooks.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace Hooks { 11 | 12 | /// Vulkan device information structure. 13 | struct DeviceInfo { 14 | VkDevice device; 15 | VkPhysicalDevice physicalDevice; 16 | std::pair queue; // graphics family 17 | }; 18 | 19 | /// Map of hooked Vulkan functions. 20 | extern std::unordered_map hooks; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Request a new feature (DO NOT REQUEST ANYTHING UNRELATED TO FRAME GENERATION) 4 | title: "[COMPATIBILITY] Explain your bug" 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the feature** 11 | A short and fitting description of what you want to see in the project. 12 | 13 | If someone already reported a variation of this feature, comment on it. If the issue has been closed, do not resubmit the feature request. 14 | 15 | **Example usecase/scenario** 16 | When would users want this feature? 17 | -------------------------------------------------------------------------------- /VkLayer_LS_frame_generation.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_format_version": "1.0.0", 3 | "layer": { 4 | "name": "VK_LAYER_LS_frame_generation", 5 | "type": "GLOBAL", 6 | "api_version": "1.4.313", 7 | "library_path": "liblsfg-vk.so", 8 | "implementation_version": "1", 9 | "description": "Lossless Scaling frame generation layer", 10 | "functions": { 11 | "vkGetInstanceProcAddr": "layer_vkGetInstanceProcAddr", 12 | "vkGetDeviceProcAddr": "layer_vkGetDeviceProcAddr" 13 | }, 14 | "disable_environment": { 15 | "DISABLE_LSFG": "1" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /flatpak/VkLayer_LS_frame_generation.patch: -------------------------------------------------------------------------------- 1 | diff --git a/VkLayer_LS_frame_generation.json b/VkLayer_LS_frame_generation.json 2 | index ece2a5f..774a027 100644 3 | --- a/VkLayer_LS_frame_generation.json 4 | +++ b/VkLayer_LS_frame_generation.json 5 | @@ -4,7 +4,7 @@ 6 | "name": "VK_LAYER_LS_frame_generation", 7 | "type": "GLOBAL", 8 | "api_version": "1.4.313", 9 | - "library_path": "liblsfg-vk.so", 10 | + "library_path": "/usr/lib/extensions/vulkan/lsfgvk/lib/liblsfg-vk.so", 11 | "implementation_version": "1", 12 | "description": "Lossless Scaling frame generation layer", 13 | "functions": { 14 | -------------------------------------------------------------------------------- /include/extract/dll.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace DLL { 9 | 10 | /// 11 | /// Parse all resources from a DLL file. 12 | /// 13 | /// *Shouldn't* cause any segmentation faults. 14 | /// 15 | /// @param filename Path to the DLL file. 16 | /// @return A map of resource IDs to their binary data. 17 | /// 18 | /// @throws std::runtime_error on various failure points. 19 | /// 20 | std::unordered_map> parse_dll(const std::string& filename); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /ui/rsc/popup/process_entry.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | -------------------------------------------------------------------------------- /include/extract/extract.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Extract { 8 | 9 | /// 10 | /// Extract all known shaders. 11 | /// 12 | /// @throws std::runtime_error if shader extraction fails. 13 | /// 14 | void extractShaders(); 15 | 16 | /// 17 | /// Get a shader by name. 18 | /// 19 | /// @param name The name of the shader to get. 20 | /// @param fp16 If true, use the FP16 variant of shaders. 21 | /// @return The shader bytecode. 22 | /// 23 | /// @throws std::runtime_error if the shader is not found. 24 | /// 25 | std::vector getShader(const std::string& name, bool fp16); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /framegen/src/common/exception.cpp: -------------------------------------------------------------------------------- 1 | #include "common/exception.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace LSFG; 12 | 13 | vulkan_error::vulkan_error(VkResult result, const std::string& message) 14 | : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), 15 | result(result) {} 16 | 17 | vulkan_error::~vulkan_error() noexcept = default; 18 | 19 | rethrowable_error::rethrowable_error(const std::string& message, const std::exception& exe) 20 | : std::runtime_error(message) { 21 | this->message = std::format("{}\n- {}", message, exe.what()); 22 | } 23 | 24 | rethrowable_error::~rethrowable_error() noexcept = default; 25 | -------------------------------------------------------------------------------- /include/config/default_conf.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const std::string DEFAULT_CONFIG = R"(version = 1 6 | [global] 7 | # override the location of Lossless Scaling 8 | # dll = "/games/Lossless Scaling/Lossless.dll" 9 | # force-disable fp16 (use on older nvidia cards) 10 | # no_fp16 = true 11 | 12 | # [[game]] # example entry 13 | # exe = "Game.exe" 14 | # 15 | # multiplier = 3 16 | # flow_scale = 0.7 17 | # performance_mode = true 18 | # hdr_mode = false 19 | # 20 | # experimental_present_mode = "fifo" 21 | 22 | [[game]] # default vkcube entry 23 | exe = "vkcube" 24 | 25 | multiplier = 4 26 | performance_mode = true 27 | 28 | [[game]] # default benchmark entry 29 | exe = "benchmark" 30 | 31 | multiplier = 4 32 | performance_mode = false 33 | 34 | [[game]] # override Genshin Impact 35 | exe = "GenshinImpact.exe" 36 | 37 | multiplier = 3 38 | )"; 39 | -------------------------------------------------------------------------------- /ui/src/wrapper/pane.rs: -------------------------------------------------------------------------------- 1 | use gtk::glib; 2 | use gtk; 3 | use adw; 4 | 5 | pub mod main; 6 | pub mod sidebar; 7 | 8 | glib::wrapper! { 9 | pub struct PaneMain(ObjectSubclass) 10 | @extends 11 | adw::NavigationPage, gtk::Widget, 12 | @implements 13 | gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; 14 | } 15 | 16 | glib::wrapper! { 17 | pub struct PaneSidebar(ObjectSubclass) 18 | @extends 19 | adw::NavigationPage, gtk::Widget, 20 | @implements 21 | gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; 22 | } 23 | 24 | 25 | impl PaneMain { 26 | pub fn new() -> Self { 27 | glib::Object::new() 28 | } 29 | } 30 | 31 | impl PaneSidebar { 32 | pub fn new() -> Self { 33 | glib::Object::new() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_23.08.yml: -------------------------------------------------------------------------------- 1 | id: org.freedesktop.Platform.VulkanLayer.lsfgvk 2 | default-branch: develop 3 | 4 | runtime: org.freedesktop.Platform 5 | runtime-version: '23.08' 6 | sdk: org.freedesktop.Sdk 7 | branch: '23.08' 8 | build-extension: true 9 | 10 | sdk-extensions: 11 | - org.freedesktop.Sdk.Extension.llvm18 12 | 13 | build-options: 14 | prefix: /usr/lib/extensions/vulkan/lsfgvk 15 | append-path: /usr/lib/sdk/llvm18/bin 16 | prepend-ld-library-path: /usr/lib/sdk/llvm18/lib 17 | 18 | modules: 19 | - name: lsfg-vk 20 | buildsystem: cmake-ninja 21 | config-opts: 22 | - -DCMAKE_BUILD_TYPE=Release 23 | - -DCMAKE_C_COMPILER=clang 24 | - -DCMAKE_CXX_COMPILER=clang++ 25 | - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On 26 | sources: 27 | - type: dir 28 | path: .. 29 | - type: patch 30 | path: VkLayer_LS_frame_generation.patch 31 | -------------------------------------------------------------------------------- /flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_24.08.yml: -------------------------------------------------------------------------------- 1 | id: org.freedesktop.Platform.VulkanLayer.lsfgvk 2 | default-branch: develop 3 | 4 | runtime: org.freedesktop.Platform 5 | runtime-version: '24.08' 6 | sdk: org.freedesktop.Sdk 7 | branch: '24.08' 8 | build-extension: true 9 | 10 | sdk-extensions: 11 | - org.freedesktop.Sdk.Extension.llvm20 12 | 13 | build-options: 14 | prefix: /usr/lib/extensions/vulkan/lsfgvk 15 | append-path: /usr/lib/sdk/llvm20/bin 16 | prepend-ld-library-path: /usr/lib/sdk/llvm20/lib 17 | 18 | modules: 19 | - name: lsfg-vk 20 | buildsystem: cmake-ninja 21 | config-opts: 22 | - -DCMAKE_BUILD_TYPE=Release 23 | - -DCMAKE_C_COMPILER=clang 24 | - -DCMAKE_CXX_COMPILER=clang++ 25 | - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On 26 | sources: 27 | - type: dir 28 | path: .. 29 | - type: patch 30 | path: VkLayer_LS_frame_generation.patch 31 | -------------------------------------------------------------------------------- /flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_25.08.yml: -------------------------------------------------------------------------------- 1 | id: org.freedesktop.Platform.VulkanLayer.lsfgvk 2 | default-branch: develop 3 | 4 | runtime: org.freedesktop.Platform 5 | runtime-version: '25.08' 6 | sdk: org.freedesktop.Sdk 7 | branch: '25.08' 8 | build-extension: true 9 | 10 | sdk-extensions: 11 | - org.freedesktop.Sdk.Extension.llvm21 12 | 13 | build-options: 14 | prefix: /usr/lib/extensions/vulkan/lsfgvk 15 | append-path: /usr/lib/sdk/llvm21/bin 16 | prepend-ld-library-path: /usr/lib/sdk/llvm21/lib 17 | 18 | modules: 19 | - name: lsfg-vk 20 | buildsystem: cmake-ninja 21 | config-opts: 22 | - -DCMAKE_BUILD_TYPE=Release 23 | - -DCMAKE_C_COMPILER=clang 24 | - -DCMAKE_CXX_COMPILER=clang++ 25 | - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On 26 | sources: 27 | - type: dir 28 | path: .. 29 | - type: patch 30 | path: VkLayer_LS_frame_generation.patch 31 | -------------------------------------------------------------------------------- /ui/rsc/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 24 | -------------------------------------------------------------------------------- /ui/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, OnceLock, RwLock}; 2 | 3 | use gtk::{gio, prelude::*}; 4 | use adw; 5 | 6 | mod ui; 7 | mod wrapper; 8 | mod config; 9 | mod utils; 10 | 11 | const APP_ID: &str = "gay.pancake.lsfg-vk-ui"; 12 | 13 | #[derive(Debug)] 14 | struct State { 15 | selected_game: Option 16 | } 17 | 18 | static STATE: OnceLock>> = OnceLock::new(); 19 | 20 | fn main() { 21 | gio::resources_register_include!("lsfg-vk.gresource") 22 | .expect("Failed to register resources"); 23 | config::load_config() 24 | .expect("Failed to load configuration"); 25 | 26 | // prepare the application state 27 | STATE.set(Arc::new(RwLock::new(State { 28 | selected_game: None 29 | }))).expect("Failed to set application state"); 30 | 31 | // start the application 32 | let app = adw::Application::builder() 33 | .application_id(APP_ID) 34 | .build(); 35 | app.connect_activate(ui::build); 36 | app.run(); 37 | } 38 | -------------------------------------------------------------------------------- /ui/rsc/lsfg-vk.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | entry/entry.ui 5 | pane/main.ui 6 | pane/sidebar.ui 7 | popup/process.ui 8 | popup/process_entry.ui 9 | pref/dropdown.ui 10 | pref/number.ui 11 | pref/entry.ui 12 | pref/slider.ui 13 | pref/switch.ui 14 | window.ui 15 | 16 | 17 | -------------------------------------------------------------------------------- /ui/src/wrapper/popup.rs: -------------------------------------------------------------------------------- 1 | use gtk::glib; 2 | use gtk; 3 | use adw; 4 | 5 | pub mod process; 6 | pub mod process_entry; 7 | 8 | glib::wrapper! { 9 | pub struct ProcessPicker(ObjectSubclass) 10 | @extends 11 | adw::ApplicationWindow, adw::Window, 12 | gtk::ApplicationWindow, gtk::Window, gtk::Widget, 13 | @implements 14 | gtk::gio::ActionGroup, gtk::gio::ActionMap, 15 | gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, 16 | gtk::Native, gtk::Root, gtk::ShortcutManager; 17 | } 18 | 19 | glib::wrapper! { 20 | pub struct ProcessEntry(ObjectSubclass) 21 | @extends 22 | gtk::ListBoxRow, gtk::Widget, 23 | @implements 24 | gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; 25 | } 26 | 27 | 28 | impl ProcessPicker { 29 | pub fn new() -> Self { 30 | glib::Object::new() 31 | } 32 | } 33 | 34 | impl ProcessEntry { 35 | pub fn new() -> Self { 36 | glib::Object::new() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ui/src/wrapper/pane/sidebar.rs: -------------------------------------------------------------------------------- 1 | use gtk::glib; 2 | use gtk::subclass::prelude::*; 3 | use adw::subclass::prelude::*; 4 | 5 | #[derive(gtk::CompositeTemplate, Default)] 6 | #[template(resource = "/gay/pancake/lsfg-vk/pane/sidebar.ui")] 7 | pub struct PaneSidebar { 8 | #[template_child] 9 | pub profiles: TemplateChild, 10 | #[template_child] 11 | pub create: TemplateChild 12 | } 13 | 14 | #[glib::object_subclass] 15 | impl ObjectSubclass for PaneSidebar { 16 | const NAME: &'static str = "LSPaneSidebar"; 17 | type Type = super::PaneSidebar; 18 | type ParentType = adw::NavigationPage; 19 | 20 | fn class_init(klass: &mut Self::Class) { 21 | klass.bind_template(); 22 | } 23 | 24 | fn instance_init(obj: &glib::subclass::InitializingObject) { 25 | obj.init_template(); 26 | } 27 | } 28 | 29 | impl ObjectImpl for PaneSidebar { 30 | fn constructed(&self) { 31 | self.parent_constructed(); 32 | } 33 | } 34 | 35 | impl WidgetImpl for PaneSidebar {} 36 | impl NavigationPageImpl for PaneSidebar {} 37 | -------------------------------------------------------------------------------- /framegen/include/core/instance.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace LSFG::Core { 8 | 9 | /// 10 | /// C++ wrapper class for a Vulkan instance. 11 | /// 12 | /// This class manages the lifetime of a Vulkan instance. 13 | /// 14 | class Instance { 15 | public: 16 | /// 17 | /// Create the instance. 18 | /// 19 | /// @throws LSFG::vulkan_error if object creation fails. 20 | /// 21 | Instance(); 22 | 23 | /// Get the Vulkan handle. 24 | [[nodiscard]] auto handle() const { return this->instance ? *this->instance : VK_NULL_HANDLE; } 25 | 26 | /// Trivially copyable, moveable and destructible 27 | Instance(const Instance&) noexcept = default; 28 | Instance& operator=(const Instance&) noexcept = default; 29 | Instance(Instance&&) noexcept = default; 30 | Instance& operator=(Instance&&) noexcept = default; 31 | ~Instance() = default; 32 | private: 33 | std::shared_ptr instance; 34 | }; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/mini/commandpool.cpp: -------------------------------------------------------------------------------- 1 | #include "mini/commandpool.hpp" 2 | #include "common/exception.hpp" 3 | #include "layer.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | using namespace Mini; 11 | 12 | CommandPool::CommandPool(VkDevice device, uint32_t graphicsFamilyIdx) { 13 | // create command pool 14 | const VkCommandPoolCreateInfo desc{ 15 | .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 16 | .queueFamilyIndex = graphicsFamilyIdx 17 | }; 18 | VkCommandPool commandPoolHandle{}; 19 | auto res = Layer::ovkCreateCommandPool(device, &desc, nullptr, &commandPoolHandle); 20 | if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE) 21 | throw LSFG::vulkan_error(res, "Unable to create command pool"); 22 | 23 | // store command pool in shared ptr 24 | this->commandPool = std::shared_ptr( 25 | new VkCommandPool(commandPoolHandle), 26 | [dev = device](VkCommandPool* commandPoolHandle) { 27 | Layer::ovkDestroyCommandPool(dev, *commandPoolHandle, nullptr); 28 | } 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /ui/src/wrapper/popup/process_entry.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use gtk::glib; 4 | use gtk::subclass::prelude::*; 5 | use gtk::prelude::*; 6 | 7 | #[derive(gtk::CompositeTemplate, glib::Properties, Default)] 8 | #[properties(wrapper_type = super::ProcessEntry)] 9 | #[template(resource = "/gay/pancake/lsfg-vk/popup/process_entry.ui")] 10 | pub struct ProcessEntry { 11 | #[property(get, set)] 12 | exe: RefCell, 13 | } 14 | 15 | #[glib::object_subclass] 16 | impl ObjectSubclass for ProcessEntry { 17 | const NAME: &'static str = "LSProcessEntry"; 18 | type Type = super::ProcessEntry; 19 | type ParentType = gtk::ListBoxRow; 20 | 21 | fn class_init(klass: &mut Self::Class) { 22 | klass.bind_template(); 23 | } 24 | 25 | fn instance_init(obj: &glib::subclass::InitializingObject) { 26 | obj.init_template(); 27 | } 28 | } 29 | 30 | #[glib::derived_properties] 31 | impl ObjectImpl for ProcessEntry { 32 | fn constructed(&self) { 33 | self.parent_constructed(); 34 | } 35 | } 36 | 37 | impl WidgetImpl for ProcessEntry {} 38 | impl ListBoxRowImpl for ProcessEntry {} 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## MIT License 2 | 3 | Copyright (c) 2025 lsfg-vk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /framegen/src/core/commandpool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "core/commandpool.hpp" 5 | #include "core/device.hpp" 6 | #include "common/exception.hpp" 7 | 8 | #include 9 | 10 | using namespace LSFG::Core; 11 | 12 | CommandPool::CommandPool(const Core::Device& device) { 13 | // create command pool 14 | const VkCommandPoolCreateInfo desc{ 15 | .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 16 | .queueFamilyIndex = device.getComputeFamilyIdx() 17 | }; 18 | VkCommandPool commandPoolHandle{}; 19 | auto res = vkCreateCommandPool(device.handle(), &desc, nullptr, &commandPoolHandle); 20 | if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE) 21 | throw LSFG::vulkan_error(res, "Unable to create command pool"); 22 | 23 | // store command pool in shared ptr 24 | this->commandPool = std::shared_ptr( 25 | new VkCommandPool(commandPoolHandle), 26 | [dev = device.handle()](VkCommandPool* commandPoolHandle) { 27 | vkDestroyCommandPool(dev, *commandPoolHandle, nullptr); 28 | } 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /ui/src/wrapper/entry/entry.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use gtk::glib; 4 | use gtk::subclass::prelude::*; 5 | use gtk::prelude::*; 6 | 7 | #[derive(gtk::CompositeTemplate, glib::Properties, Default)] 8 | #[properties(wrapper_type = super::Entry)] 9 | #[template(resource = "/gay/pancake/lsfg-vk/entry/entry.ui")] 10 | pub struct Entry { 11 | #[property(get, set)] 12 | exe: RefCell, 13 | 14 | #[template_child] 15 | pub delete: TemplateChild, 16 | } 17 | 18 | #[glib::object_subclass] 19 | impl ObjectSubclass for Entry { 20 | const NAME: &'static str = "LSEntry"; 21 | type Type = super::Entry; 22 | type ParentType = gtk::ListBoxRow; 23 | 24 | fn class_init(klass: &mut Self::Class) { 25 | klass.bind_template(); 26 | } 27 | 28 | fn instance_init(obj: &glib::subclass::InitializingObject) { 29 | obj.init_template(); 30 | } 31 | } 32 | 33 | #[glib::derived_properties] 34 | impl ObjectImpl for Entry { 35 | fn constructed(&self) { 36 | self.parent_constructed(); 37 | } 38 | } 39 | 40 | impl WidgetImpl for Entry {} 41 | impl ListBoxRowImpl for Entry {} 42 | -------------------------------------------------------------------------------- /framegen/LICENSE.md: -------------------------------------------------------------------------------- 1 | ## MIT License 2 | 3 | Copyright (c) 2025 lsfg-vk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ui/rsc/entry/entry.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 33 | 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug (if lsfg-vk does work, but not as expected) 4 | title: "[BUG] Explain your bug" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what's happening is and, if not immediately obvious, what's supposed to happen instead. 12 | 13 | Before reporting, make sure you have read through this wiki page: 14 | https://github.com/PancakeTAS/lsfg-vk/wiki/Quirks 15 | 16 | Make sure this bug hasn't already been reported. Comment your findings on an existing issue if you find one! 17 | 18 | **To Reproduce** 19 | Steps to reproduce the behavior: 20 | 1. Open '...' 21 | 2. Do '...' 22 | 3. Notice '...' 23 | 24 | **Screenshots/Videos** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **System information** 28 | What Linux distro are you on? What driver version are you using? What's in your machine? 29 | Anything that could be relevant. 30 | 31 | **Verbose log messages** 32 | Follow this wiki page to get log message. You may skip this step if you think it isn't relevant: 33 | https://github.com/PancakeTAS/lsfg-vk/wiki/How-to-ask-for-help 34 | -------------------------------------------------------------------------------- /flatpak/gay.pancake.lsfg-vk-ui.yml: -------------------------------------------------------------------------------- 1 | id: gay.pancake.lsfg-vk-ui 2 | command: lsfg-vk-ui 3 | default-branch: develop 4 | 5 | runtime: org.gnome.Platform 6 | runtime-version: '49' 7 | sdk: org.gnome.Sdk 8 | 9 | finish-args: 10 | - --share=ipc 11 | - --socket=x11 12 | - --socket=wayland 13 | - --device=dri 14 | - --filesystem=xdg-config 15 | 16 | sdk-extensions: 17 | - org.freedesktop.Sdk.Extension.rust-stable 18 | 19 | build-options: 20 | append-path: /usr/lib/sdk/rust-stable/bin 21 | build-args: 22 | - --share=network 23 | 24 | modules: 25 | - name: lsfg-vk-ui 26 | buildsystem: simple 27 | build-commands: 28 | - cargo build --release --locked 29 | - install -Dm755 ./target/release/lsfg-vk-ui /app/bin/lsfg-vk-ui 30 | - install -Dm644 ./rsc/gay.pancake.lsfg-vk-ui.desktop /app/share/applications/gay.pancake.lsfg-vk-ui.desktop 31 | - install -Dm644 ./rsc/icon.png /app/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png 32 | - install -Dm644 ./gay.pancake.lsfg-vk-ui.metainfo.xml /app/share/metainfo/gay.pancake.lsfg-vk-ui.metainfo.xml 33 | sources: 34 | - type: dir 35 | path: ../ui 36 | - type: file 37 | path: gay.pancake.lsfg-vk-ui.metainfo.xml 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/compatibility_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Compatibility report 3 | about: Report a bug (if lsfg-vk does not work, or poorly works on a certain game or app) 4 | title: "[COMPATIBILITY] Explain your bug" 5 | labels: compatibility 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what's happening is and, if not immediately obvious, what's supposed to happen instead. 12 | 13 | Before reporting, make sure you have read through this wiki page: 14 | https://github.com/PancakeTAS/lsfg-vk/wiki/Quirks 15 | 16 | Make sure this bug hasn't already been reported. Comment your findings on an existing issue if you find one! 17 | 18 | **To Reproduce** 19 | Steps to reproduce the behavior: 20 | 1. Open '...' 21 | 2. Do '...' 22 | 3. Notice '...' 23 | 24 | **Screenshots/Videos** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **System information** 28 | What Linux distro are you on? What driver version are you using? What's in your machine? 29 | Anything that could be relevant. 30 | 31 | **Verbose log messages** 32 | Follow this wiki page to get log message. You may skip this step if you think it isn't relevant: 33 | https://github.com/PancakeTAS/lsfg-vk/wiki/How-to-ask-for-help 34 | -------------------------------------------------------------------------------- /ui/src/wrapper/popup/process.rs: -------------------------------------------------------------------------------- 1 | use gtk::subclass::prelude::*; 2 | use adw::subclass::prelude::*; 3 | use gtk::{glib, CompositeTemplate}; 4 | 5 | #[derive(CompositeTemplate, Default)] 6 | #[template(resource = "/gay/pancake/lsfg-vk/popup/process.ui")] 7 | pub struct ProcessPicker { 8 | #[template_child] 9 | pub processes: TemplateChild, 10 | #[template_child] 11 | pub close: TemplateChild, 12 | } 13 | 14 | #[glib::object_subclass] 15 | impl ObjectSubclass for ProcessPicker { 16 | const NAME: &'static str = "LSProcessPicker"; 17 | type Type = super::ProcessPicker; 18 | type ParentType = adw::ApplicationWindow; 19 | 20 | fn class_init(klass: &mut Self::Class) { 21 | klass.bind_template(); 22 | } 23 | 24 | fn instance_init(obj: &glib::subclass::InitializingObject) { 25 | obj.init_template(); 26 | } 27 | } 28 | 29 | impl ObjectImpl for ProcessPicker { 30 | fn constructed(&self) { 31 | self.parent_constructed(); 32 | } 33 | } 34 | 35 | impl WidgetImpl for ProcessPicker {} 36 | impl WindowImpl for ProcessPicker {} 37 | impl ApplicationWindowImpl for ProcessPicker {} 38 | impl AdwWindowImpl for ProcessPicker {} 39 | impl AdwApplicationWindowImpl for ProcessPicker {} 40 | -------------------------------------------------------------------------------- /flatpak/gay.pancake.lsfg-vk-ui.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | gay.pancake.lsfg-vk-ui 4 | 5 | lsfg-vk-ui 6 | Lossless Scaling Frame Generation on Linux (user interface only) 7 | 8 | PancakeTAS 9 | 10 | 11 | https://github.com/PancakeTAS/lsfg-vk 12 | https://github.com/PancakeTAS/lsfg-vk/issues 13 | https://github.com/PancakeTAS/lsfg-vk/wiki 14 | https://github.com/PancakeTAS/lsfg-vk/wiki 15 | 16 | MIT 17 | MIT 18 | 19 | 20 |

The lsfg-vk project brings Lossless Scaling's frame generation to Linux users by acting as a Vulkan layer inbetween your game and your graphics card.

21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | gay.pancake.lsfg-vk-ui.desktop 29 | 30 |
31 | -------------------------------------------------------------------------------- /framegen/include/core/commandpool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/device.hpp" 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace LSFG::Core { 10 | 11 | /// 12 | /// C++ wrapper class for a Vulkan command pool. 13 | /// 14 | /// This class manages the lifetime of a Vulkan command pool. 15 | /// 16 | class CommandPool { 17 | public: 18 | CommandPool() noexcept = default; 19 | 20 | /// 21 | /// Create the command pool. 22 | /// 23 | /// @param device Vulkan device 24 | /// 25 | /// @throws LSFG::vulkan_error if object creation fails. 26 | /// 27 | CommandPool(const Core::Device& device); 28 | 29 | /// Get the Vulkan handle. 30 | [[nodiscard]] auto handle() const { return *this->commandPool; } 31 | 32 | /// Trivially copyable, moveable and destructible 33 | CommandPool(const CommandPool&) noexcept = default; 34 | CommandPool& operator=(const CommandPool&) noexcept = default; 35 | CommandPool(CommandPool&&) noexcept = default; 36 | CommandPool& operator=(CommandPool&&) noexcept = default; 37 | ~CommandPool() = default; 38 | private: 39 | std::shared_ptr commandPool; 40 | }; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /ui/src/wrapper/pref/slider.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use gtk::glib; 4 | use gtk::subclass::prelude::*; 5 | use adw::subclass::prelude::*; 6 | use adw::prelude::*; 7 | 8 | #[derive(gtk::CompositeTemplate, glib::Properties, Default)] 9 | #[properties(wrapper_type = super::PrefSlider)] 10 | #[template(resource = "/gay/pancake/lsfg-vk/pref/slider.ui")] 11 | pub struct PrefSlider { 12 | #[property(get, set)] 13 | opt_name: RefCell, 14 | #[property(get, set)] 15 | opt_subtitle: RefCell, 16 | 17 | #[template_child] 18 | pub slider: TemplateChild, 19 | } 20 | 21 | #[glib::object_subclass] 22 | impl ObjectSubclass for PrefSlider { 23 | const NAME: &'static str = "LSPrefSlider"; 24 | type Type = super::PrefSlider; 25 | type ParentType = adw::PreferencesRow; 26 | 27 | fn class_init(klass: &mut Self::Class) { 28 | klass.bind_template(); 29 | } 30 | 31 | fn instance_init(obj: &glib::subclass::InitializingObject) { 32 | obj.init_template(); 33 | } 34 | } 35 | 36 | #[glib::derived_properties] 37 | impl ObjectImpl for PrefSlider { 38 | fn constructed(&self) { 39 | self.parent_constructed(); 40 | } 41 | } 42 | 43 | impl WidgetImpl for PrefSlider {} 44 | impl ListBoxRowImpl for PrefSlider {} 45 | impl PreferencesRowImpl for PrefSlider {} 46 | -------------------------------------------------------------------------------- /ui/src/wrapper/pref/number.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use gtk::glib; 4 | use gtk::subclass::prelude::*; 5 | use adw::subclass::prelude::*; 6 | use adw::prelude::*; 7 | 8 | #[derive(gtk::CompositeTemplate, glib::Properties, Default)] 9 | #[properties(wrapper_type = super::PrefNumber)] 10 | #[template(resource = "/gay/pancake/lsfg-vk/pref/number.ui")] 11 | pub struct PrefNumber { 12 | #[property(get, set)] 13 | opt_name: RefCell, 14 | #[property(get, set)] 15 | opt_subtitle: RefCell, 16 | 17 | #[template_child] 18 | pub number: TemplateChild, 19 | } 20 | 21 | #[glib::object_subclass] 22 | impl ObjectSubclass for PrefNumber { 23 | const NAME: &'static str = "LSPrefNumber"; 24 | type Type = super::PrefNumber; 25 | type ParentType = adw::PreferencesRow; 26 | 27 | fn class_init(klass: &mut Self::Class) { 28 | klass.bind_template(); 29 | } 30 | 31 | fn instance_init(obj: &glib::subclass::InitializingObject) { 32 | obj.init_template(); 33 | } 34 | } 35 | 36 | #[glib::derived_properties] 37 | impl ObjectImpl for PrefNumber { 38 | fn constructed(&self) { 39 | self.parent_constructed(); 40 | } 41 | } 42 | 43 | impl WidgetImpl for PrefNumber {} 44 | impl ListBoxRowImpl for PrefNumber {} 45 | impl PreferencesRowImpl for PrefNumber {} 46 | -------------------------------------------------------------------------------- /package/rpm.spec: -------------------------------------------------------------------------------- 1 | Name: lsfg-vk 2 | Summary: Lossless Scaling Frame Generation on Linux via DXVK/Vulkan 3 | Version: ${RPM_VERSION} 4 | Release: 1%{?dist} 5 | URL: https://discord.gg/losslessscaling 6 | Packager: "PancakeTAS " 7 | License: MIT 8 | Group: Applications/System 9 | BuildArch: x86_64 10 | 11 | Requires: glibc >= 2.38 12 | Requires: vulkan-loader 13 | Requires: libadwaita 14 | Requires: gtk4 15 | 16 | %global __strip /bin/true 17 | 18 | %description 19 | Lossless Scaling Frame Generation on Linux via DXVK/Vulkan. 20 | 21 | %install 22 | install -Dm755 %{_sourcedir}/lsfg-vk-ui %{buildroot}%{_bindir}/lsfg-vk-ui 23 | install -Dm755 %{_sourcedir}/liblsfg-vk.so %{buildroot}%{_libdir}/liblsfg-vk.so 24 | install -Dm644 %{_sourcedir}/VkLayer_LS_frame_generation.json %{buildroot}%{_datadir}/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json 25 | install -Dm644 %{_sourcedir}/lsfg-vk-ui.desktop %{buildroot}%{_datadir}/applications/lsfg-vk-ui.desktop 26 | install -Dm644 %{_sourcedir}/gay.pancake.lsfg-vk-ui.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png 27 | 28 | %files 29 | %{_bindir}/lsfg-vk-ui 30 | %{_libdir}/liblsfg-vk.so 31 | %{_datadir}/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json 32 | %{_datadir}/applications/lsfg-vk-ui.desktop 33 | %{_datadir}/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png 34 | -------------------------------------------------------------------------------- /framegen/README.md: -------------------------------------------------------------------------------- 1 | ## lsfg-vk-framegen 2 | Lossless Scaling Frame Generation 3 | 4 | This is a subproject of lsfg-vk and contains the dedicated Vulkan logic for generating frames. 5 | 6 | The project is intentionally structured as a fully external project, such that it can be integrated into other applications. 7 | 8 | ### Interface 9 | 10 | Interfacing with lsfg-vk-framegen is done via `lsfg_x_x.hpp` header. The internal Vulkan instance is created using `LSFG_X_X::initialize()` and requires a specific deviceUUID, as well as parts of the lsfg-vk configuration, including a function loading SPIR-V shaders by name. Cleanup is done via `LSFG_X_X::finalize()` after which `LSFG_X_X::initialize()` may be called again. Please note that the initialization process is expensive and may take a while. It is recommended to call this function once during the applications lifetime. 11 | 12 | Once the format and extent of the requested images is determined, `LSFG_X_X::createContext()` should be called to initialize a frame generation context. The Vulkan images are created from backing memory, which is passed through the file descriptor arguments. A context can be destroyed using `LSFG_X_X::deleteContext()`. 13 | 14 | Presenting the context can be done via `LSFG_X_X::presentContext()`. Before calling the function a second time, make sure the outgoing semaphores have been signaled. 15 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: 2 | # enable basic checks 3 | - "clang-analyzer-*" 4 | # configure performance checks 5 | - "performance-*" 6 | - "-performance-enum-size" 7 | # configure readability and bugprone checks 8 | - "readability-*" 9 | - "bugprone-*" 10 | - "misc-*" 11 | - "-readability-braces-around-statements" 12 | - "-readability-function-cognitive-complexity" 13 | - "-readability-identifier-length" 14 | - "-readability-implicit-bool-conversion" 15 | - "-readability-magic-numbers" 16 | - "-readability-math-missing-parentheses" 17 | - "-readability-named-parameter" 18 | - "-bugprone-easily-swappable-parameters" 19 | # configure modernization 20 | - "modernize-*" 21 | - "-modernize-use-trailing-return-type" 22 | # configure cppcoreguidelines 23 | - "cppcoreguidelines-*" 24 | - "-cppcoreguidelines-avoid-magic-numbers" 25 | - "-cppcoreguidelines-pro-type-reinterpret-cast" # allows reinterpret_cast 26 | - "-cppcoreguidelines-avoid-non-const-global-variables" 27 | - "-cppcoreguidelines-pro-type-union-access" 28 | # disable slow and pointless checks 29 | - "-modernize-use-std-numbers" 30 | - "-modernize-type-traits" 31 | - "-cppcoreguidelines-owning-memory" 32 | - "-cppcoreguidelines-macro-to-enum" 33 | - "-readability-container-contains" 34 | - "-bugprone-reserved-identifier" 35 | - "-bugprone-stringview-nullptr" 36 | - "-bugprone-standalone-empty" 37 | - "-misc-unused-using-decls" 38 | -------------------------------------------------------------------------------- /framegen/include/core/descriptorpool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/device.hpp" 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace LSFG::Core { 10 | 11 | /// 12 | /// C++ wrapper class for a Vulkan descriptor pool. 13 | /// 14 | /// This class manages the lifetime of a Vulkan descriptor pool. 15 | /// 16 | class DescriptorPool { 17 | public: 18 | DescriptorPool() noexcept = default; 19 | 20 | /// 21 | /// Create the descriptor pool. 22 | /// 23 | /// @param device Vulkan device 24 | /// 25 | /// @throws LSFG::vulkan_error if object creation fails. 26 | /// 27 | DescriptorPool(const Core::Device& device); 28 | 29 | /// Get the Vulkan handle. 30 | [[nodiscard]] auto handle() const { return *this->descriptorPool; } 31 | 32 | /// Trivially copyable, moveable and destructible 33 | DescriptorPool(const DescriptorPool&) noexcept = default; 34 | DescriptorPool& operator=(const DescriptorPool&) noexcept = default; 35 | DescriptorPool(DescriptorPool&&) noexcept = default; 36 | DescriptorPool& operator=(DescriptorPool&&) noexcept = default; 37 | ~DescriptorPool() = default; 38 | private: 39 | std::shared_ptr descriptorPool; 40 | }; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /framegen/.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: 2 | # enable basic checks 3 | - "clang-analyzer-*" 4 | # configure performance checks 5 | - "performance-*" 6 | - "-performance-enum-size" 7 | # configure readability and bugprone checks 8 | - "readability-*" 9 | - "bugprone-*" 10 | - "misc-*" 11 | - "-readability-braces-around-statements" 12 | - "-readability-function-cognitive-complexity" 13 | - "-readability-identifier-length" 14 | - "-readability-implicit-bool-conversion" 15 | - "-readability-magic-numbers" 16 | - "-readability-math-missing-parentheses" 17 | - "-readability-named-parameter" 18 | - "-bugprone-easily-swappable-parameters" 19 | # configure modernization 20 | - "modernize-*" 21 | - "-modernize-use-trailing-return-type" 22 | # configure cppcoreguidelines 23 | - "cppcoreguidelines-*" 24 | - "-cppcoreguidelines-avoid-magic-numbers" 25 | - "-cppcoreguidelines-pro-type-reinterpret-cast" # allows reinterpret_cast 26 | - "-cppcoreguidelines-avoid-non-const-global-variables" 27 | - "-cppcoreguidelines-pro-type-union-access" 28 | # disable slow and pointless checks 29 | - "-modernize-use-std-numbers" 30 | - "-modernize-type-traits" 31 | - "-cppcoreguidelines-owning-memory" 32 | - "-cppcoreguidelines-macro-to-enum" 33 | - "-readability-container-contains" 34 | - "-bugprone-reserved-identifier" 35 | - "-bugprone-stringview-nullptr" 36 | - "-bugprone-standalone-empty" 37 | - "-misc-unused-using-decls" 38 | -------------------------------------------------------------------------------- /include/mini/commandpool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace Mini { 9 | 10 | /// 11 | /// C++ wrapper class for a Vulkan command pool. 12 | /// 13 | /// This class manages the lifetime of a Vulkan command pool. 14 | /// 15 | class CommandPool { 16 | public: 17 | CommandPool() noexcept = default; 18 | 19 | /// 20 | /// Create the command pool. 21 | /// 22 | /// @param device Vulkan device 23 | /// @param graphicsFamilyIdx Index of the graphics queue family 24 | /// 25 | /// @throws LSFG::vulkan_error if object creation fails. 26 | /// 27 | CommandPool(VkDevice device, uint32_t graphicsFamilyIdx); 28 | 29 | /// Get the Vulkan handle. 30 | [[nodiscard]] auto handle() const { return *this->commandPool; } 31 | 32 | /// Trivially copyable, moveable and destructible 33 | CommandPool(const CommandPool&) noexcept = default; 34 | CommandPool& operator=(const CommandPool&) noexcept = default; 35 | CommandPool(CommandPool&&) noexcept = default; 36 | CommandPool& operator=(CommandPool&&) noexcept = default; 37 | ~CommandPool() = default; 38 | private: 39 | std::shared_ptr commandPool; 40 | }; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /ui/src/wrapper/pref/switch.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use gtk::glib; 4 | use gtk::subclass::prelude::*; 5 | use adw::subclass::prelude::*; 6 | use adw::prelude::*; 7 | 8 | #[derive(gtk::CompositeTemplate, glib::Properties, Default)] 9 | #[properties(wrapper_type = super::PrefSwitch)] 10 | #[template(resource = "/gay/pancake/lsfg-vk/pref/switch.ui")] 11 | pub struct PrefSwitch { 12 | #[property(get, set)] 13 | opt_name: RefCell, 14 | #[property(get, set)] 15 | opt_subtitle: RefCell, 16 | #[property(get, set)] 17 | default_state: RefCell, 18 | 19 | #[template_child] 20 | pub switch: TemplateChild, 21 | } 22 | 23 | #[glib::object_subclass] 24 | impl ObjectSubclass for PrefSwitch { 25 | const NAME: &'static str = "LSPrefSwitch"; 26 | type Type = super::PrefSwitch; 27 | type ParentType = adw::PreferencesRow; 28 | 29 | fn class_init(klass: &mut Self::Class) { 30 | klass.bind_template(); 31 | } 32 | 33 | fn instance_init(obj: &glib::subclass::InitializingObject) { 34 | obj.init_template(); 35 | } 36 | } 37 | 38 | #[glib::derived_properties] 39 | impl ObjectImpl for PrefSwitch { 40 | fn constructed(&self) { 41 | self.parent_constructed(); 42 | } 43 | } 44 | 45 | impl WidgetImpl for PrefSwitch {} 46 | impl ListBoxRowImpl for PrefSwitch {} 47 | impl PreferencesRowImpl for PrefSwitch {} 48 | -------------------------------------------------------------------------------- /.github/workflows/package_ci.yml: -------------------------------------------------------------------------------- 1 | name: (CI) Package lsfg-vk 2 | run-name: ${{ github.event.workflow_run.head_commit.message }} 3 | 4 | on: 5 | workflow_run: 6 | workflows: ["(CI) Build lsfg-vk"] 7 | types: 8 | - completed 9 | 10 | jobs: 11 | package: 12 | runs-on: ubuntu-latest 13 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | with: 18 | ref: ${{ github.event.workflow_run.head_sha }} 19 | fetch-depth: 0 20 | - name: Download lsfg-vk artifacts 21 | uses: actions/download-artifact@v4 22 | with: 23 | name: lsfg-vk 24 | path: . 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | run-id: ${{ github.event.workflow_run.id }} 27 | - name: Package lsfg-vk for various distros 28 | run: | 29 | chmod +x ./package/package.sh 30 | ./package/package.sh 31 | - name: Upload lsfg-vk for dpkg 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: lsfg-vk.dpkg 35 | path: | 36 | *.deb 37 | - name: Upload lsfg-vk for rpm 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: lsfg-vk.rpm 41 | path: | 42 | *.rpm 43 | - name: Upload lsfg-vk for alpm 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: lsfg-vk.alpm 47 | path: | 48 | *.zst 49 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: Package lsfg-vk 2 | run-name: ${{ github.event.workflow_run.head_commit.message }} 3 | 4 | on: 5 | workflow_run: 6 | workflows: ["Build lsfg-vk"] 7 | types: 8 | - completed 9 | 10 | jobs: 11 | package: 12 | runs-on: ubuntu-latest 13 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | with: 18 | ref: ${{ github.event.workflow_run.head_sha }} 19 | fetch-depth: 0 20 | - name: Download lsfg-vk artifacts 21 | uses: actions/download-artifact@v4 22 | with: 23 | name: lsfg-vk 24 | path: . 25 | github-token: ${{ secrets.GITHUB_TOKEN }} 26 | run-id: ${{ github.event.workflow_run.id }} 27 | - name: Package lsfg-vk for various distros 28 | run: | 29 | export VERSION=1.0.0 30 | chmod +x ./package/package.sh 31 | ./package/package.sh 32 | - name: Upload lsfg-vk for dpkg 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: lsfg-vk.dpkg 36 | path: | 37 | *.deb 38 | - name: Upload lsfg-vk for rpm 39 | uses: actions/upload-artifact@v4 40 | with: 41 | name: lsfg-vk.rpm 42 | path: | 43 | *.rpm 44 | - name: Upload lsfg-vk for alpm 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: lsfg-vk.alpm 48 | path: | 49 | *.zst 50 | -------------------------------------------------------------------------------- /ui/src/ui.rs: -------------------------------------------------------------------------------- 1 | use adw::{self, subclass::prelude::ObjectSubclassIsExt}; 2 | use gtk::prelude::{WidgetExt, EditableExt, GtkWindowExt}; 3 | 4 | use crate::config; 5 | use crate::wrapper; 6 | 7 | pub mod entry_handler; 8 | pub mod main_handler; 9 | pub mod sidebar_handler; 10 | 11 | pub fn build(app: &adw::Application) { 12 | // create the main window 13 | let window = wrapper::Window::new(app); 14 | window.set_application(Some(app)); 15 | let imp = window.imp(); 16 | 17 | // load profiles from configuration 18 | let config = config::get_config().unwrap(); 19 | for game in config.game.iter() { 20 | let entry = wrapper::entry::Entry::new(); 21 | entry.set_exe(game.exe.clone()); 22 | entry_handler::add_entry(entry, imp.sidebar.imp().profiles.clone()); 23 | } 24 | 25 | if let Some(dll_path) = config.global.dll { 26 | imp.main.imp().dll.imp().entry.set_text(&dll_path); 27 | } 28 | imp.main.imp().no_fp16.imp().switch.set_active(config.global.no_fp16); 29 | 30 | // register handlers on sidebar pane. 31 | sidebar_handler::register_signals(&imp.sidebar, imp.main.clone()); 32 | 33 | // register handlers on main pane. 34 | main_handler::register_signals(imp.sidebar.clone(), &imp.main); 35 | 36 | // activate the first profile if available 37 | if let Some(entry) = imp.sidebar.imp().profiles.row_at_index(0) { 38 | entry.activate(); 39 | } 40 | 41 | // present the window 42 | window.present(); 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/flatpak.yml: -------------------------------------------------------------------------------- 1 | name: Build lsfg-vk for Flatpak 2 | 3 | on: 4 | push: 5 | branches: ["release"] 6 | 7 | jobs: 8 | flatpak-extensions: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | version: ["23.08", "24.08", "25.08"] 13 | container: 14 | image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-${{ matrix.version }} 15 | options: --privileged 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | - name: Build Flatpak extension (${{ matrix.version }}) 20 | uses: flatpak/flatpak-github-actions/flatpak-builder@v6 21 | with: 22 | bundle: "org.freedesktop.Platform.VulkanLayer.lsfg_vk_${{ matrix.version }}.flatpak" 23 | manifest-path: "flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_${{ matrix.version }}.yml" 24 | branch: "${{ matrix.version }}" 25 | cache: false 26 | flatpak-ui: 27 | runs-on: ubuntu-latest 28 | container: 29 | image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48 30 | options: --privileged 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4 34 | - name: Build Flatpak 35 | uses: flatpak/flatpak-github-actions/flatpak-builder@v6 36 | with: 37 | bundle: "gay.pancake.lsfg-vk-ui.flatpak" 38 | manifest-path: "flatpak/gay.pancake.lsfg-vk-ui.yml" 39 | branch: "release" 40 | cache: false 41 | -------------------------------------------------------------------------------- /.github/workflows/flatpak_ci.yml: -------------------------------------------------------------------------------- 1 | name: (CI) Build lsfg-vk for Flatpak 2 | 3 | on: 4 | push: 5 | branches: ["develop"] 6 | 7 | jobs: 8 | flatpak-extensions: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | version: ["23.08", "24.08", "25.08"] 13 | container: 14 | image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-${{ matrix.version }} 15 | options: --privileged 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | - name: Build Flatpak extension (${{ matrix.version }}) 20 | uses: flatpak/flatpak-github-actions/flatpak-builder@v6 21 | with: 22 | bundle: "org.freedesktop.Platform.VulkanLayer.lsfg_vk_${{ matrix.version }}.flatpak" 23 | manifest-path: "flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_${{ matrix.version }}.yml" 24 | branch: "${{ matrix.version }}" 25 | cache: false 26 | flatpak-ui: 27 | runs-on: ubuntu-latest 28 | container: 29 | image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48 30 | options: --privileged 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4 34 | - name: Build Flatpak 35 | uses: flatpak/flatpak-github-actions/flatpak-builder@v6 36 | with: 37 | bundle: "gay.pancake.lsfg-vk-ui.flatpak" 38 | manifest-path: "flatpak/gay.pancake.lsfg-vk-ui.yml" 39 | branch: "develop" 40 | cache: false 41 | -------------------------------------------------------------------------------- /framegen/src/pool/shaderpool.cpp: -------------------------------------------------------------------------------- 1 | #include "pool/shaderpool.hpp" 2 | #include "core/shadermodule.hpp" 3 | #include "core/device.hpp" 4 | #include "core/pipeline.hpp" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace LSFG; 15 | using namespace LSFG::Pool; 16 | 17 | Core::ShaderModule ShaderPool::getShader( 18 | const Core::Device& device, const std::string& name, 19 | const std::vector>& types) { 20 | auto it = shaders.find(name); 21 | if (it != shaders.end()) 22 | return it->second; 23 | 24 | // grab the shader 25 | auto bytecode = this->source(name, this->fp16); 26 | if (bytecode.empty()) 27 | throw std::runtime_error("Shader code is empty: " + name); 28 | 29 | // create the shader module 30 | Core::ShaderModule shader(device, bytecode, types); 31 | shaders[name] = shader; 32 | return shader; 33 | } 34 | 35 | Core::Pipeline ShaderPool::getPipeline( 36 | const Core::Device& device, const std::string& name) { 37 | auto it = pipelines.find(name); 38 | if (it != pipelines.end()) 39 | return it->second; 40 | 41 | // grab the shader module 42 | auto shader = this->getShader(device, name, {}); 43 | 44 | // create the pipeline 45 | Core::Pipeline pipeline(device, shader); 46 | pipelines[name] = pipeline; 47 | return pipeline; 48 | } 49 | -------------------------------------------------------------------------------- /ui/src/wrapper/pref/dropdown.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use gtk::glib; 4 | use gtk::subclass::prelude::*; 5 | use adw::subclass::prelude::*; 6 | use adw::prelude::*; 7 | 8 | #[derive(gtk::CompositeTemplate, glib::Properties, Default)] 9 | #[properties(wrapper_type = super::PrefDropdown)] 10 | #[template(resource = "/gay/pancake/lsfg-vk/pref/dropdown.ui")] 11 | pub struct PrefDropdown { 12 | #[property(get, set)] 13 | opt_name: RefCell, 14 | #[property(get, set)] 15 | opt_subtitle: RefCell, 16 | #[property(get, set)] 17 | default_selection: RefCell, 18 | #[property(get, set)] 19 | options: RefCell, 20 | 21 | #[template_child] 22 | pub dropdown: TemplateChild, 23 | } 24 | 25 | #[glib::object_subclass] 26 | impl ObjectSubclass for PrefDropdown { 27 | const NAME: &'static str = "LSPrefDropdown"; 28 | type Type = super::PrefDropdown; 29 | type ParentType = adw::PreferencesRow; 30 | 31 | fn class_init(klass: &mut Self::Class) { 32 | klass.bind_template(); 33 | } 34 | 35 | fn instance_init(obj: &glib::subclass::InitializingObject) { 36 | obj.init_template(); 37 | } 38 | } 39 | 40 | #[glib::derived_properties] 41 | impl ObjectImpl for PrefDropdown { 42 | fn constructed(&self) { 43 | self.parent_constructed(); 44 | } 45 | } 46 | 47 | impl WidgetImpl for PrefDropdown {} 48 | impl ListBoxRowImpl for PrefDropdown {} 49 | impl PreferencesRowImpl for PrefDropdown {} 50 | -------------------------------------------------------------------------------- /framegen/include/core/sampler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/device.hpp" 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace LSFG::Core { 10 | 11 | /// 12 | /// C++ wrapper class for a Vulkan sampler. 13 | /// 14 | /// This class manages the lifetime of a Vulkan sampler. 15 | /// 16 | class Sampler { 17 | public: 18 | Sampler() noexcept = default; 19 | 20 | /// 21 | /// Create the sampler. 22 | /// 23 | /// @param device Vulkan device 24 | /// @param mode Address mode for the sampler. 25 | /// @param compare Compare operation for the sampler. 26 | /// @param isWhite Whether the border color is white. 27 | /// 28 | /// @throws LSFG::vulkan_error if object creation fails. 29 | /// 30 | Sampler(const Core::Device& device, 31 | VkSamplerAddressMode mode, 32 | VkCompareOp compare, 33 | bool isWhite); 34 | 35 | /// Get the Vulkan handle. 36 | [[nodiscard]] auto handle() const { return *this->sampler; } 37 | 38 | /// Trivially copyable, moveable and destructible 39 | Sampler(const Sampler&) noexcept = default; 40 | Sampler& operator=(const Sampler&) noexcept = default; 41 | Sampler(Sampler&&) noexcept = default; 42 | Sampler& operator=(Sampler&&) noexcept = default; 43 | ~Sampler() = default; 44 | private: 45 | std::shared_ptr sampler; 46 | }; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /include/mini/semaphore.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Mini { 8 | 9 | /// 10 | /// C++ wrapper class for a Vulkan semaphore. 11 | /// 12 | /// This class manages the lifetime of a Vulkan semaphore. 13 | /// 14 | class Semaphore { 15 | public: 16 | Semaphore() noexcept = default; 17 | 18 | /// 19 | /// Create the semaphore. 20 | /// 21 | /// @param device Vulkan device 22 | /// 23 | /// @throws LSFG::vulkan_error if object creation fails. 24 | /// 25 | Semaphore(VkDevice device); 26 | 27 | /// 28 | /// Import a semaphore. 29 | /// 30 | /// @param device Vulkan device 31 | /// @param fd File descriptor to import the semaphore from. 32 | /// 33 | /// @throws LSFG::vulkan_error if object creation fails. 34 | /// 35 | Semaphore(VkDevice device, int* fd); 36 | 37 | /// Get the Vulkan handle. 38 | [[nodiscard]] auto handle() const { return *this->semaphore; } 39 | 40 | // Trivially copyable, moveable and destructible 41 | Semaphore(const Semaphore&) noexcept = default; 42 | Semaphore& operator=(const Semaphore&) noexcept = default; 43 | Semaphore(Semaphore&&) noexcept = default; 44 | Semaphore& operator=(Semaphore&&) noexcept = default; 45 | ~Semaphore() = default; 46 | private: 47 | std::shared_ptr semaphore; 48 | }; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /framegen/src/core/sampler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "core/sampler.hpp" 5 | #include "core/device.hpp" 6 | #include "common/exception.hpp" 7 | 8 | #include 9 | 10 | using namespace LSFG::Core; 11 | 12 | Sampler::Sampler(const Core::Device& device, 13 | VkSamplerAddressMode mode, 14 | VkCompareOp compare, 15 | bool isWhite) { 16 | // create sampler 17 | const VkSamplerCreateInfo desc{ 18 | .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, 19 | .magFilter = VK_FILTER_LINEAR, 20 | .minFilter = VK_FILTER_LINEAR, 21 | .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, 22 | .addressModeU = mode, 23 | .addressModeV = mode, 24 | .addressModeW = mode, 25 | .compareOp = compare, 26 | .maxLod = VK_LOD_CLAMP_NONE, 27 | .borderColor = 28 | isWhite ? VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE 29 | : VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK 30 | }; 31 | VkSampler samplerHandle{}; 32 | auto res = vkCreateSampler(device.handle(), &desc, nullptr, &samplerHandle); 33 | if (res != VK_SUCCESS || samplerHandle == VK_NULL_HANDLE) 34 | throw LSFG::vulkan_error(res, "Unable to create sampler"); 35 | 36 | // store sampler in shared ptr 37 | this->sampler = std::shared_ptr( 38 | new VkSampler(samplerHandle), 39 | [dev = device.handle()](VkSampler* samplerHandle) { 40 | vkDestroySampler(dev, *samplerHandle, nullptr); 41 | } 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /ui/src/wrapper/pane/main.rs: -------------------------------------------------------------------------------- 1 | use gtk::glib; 2 | use gtk::subclass::prelude::*; 3 | use adw::subclass::prelude::*; 4 | use crate::wrapper::pref::*; 5 | 6 | #[derive(gtk::CompositeTemplate, Default)] 7 | #[template(resource = "/gay/pancake/lsfg-vk/pane/main.ui")] 8 | pub struct PaneMain { 9 | #[template_child] 10 | pub dll: TemplateChild, 11 | #[template_child] 12 | pub no_fp16: TemplateChild, 13 | #[template_child] 14 | pub profile_name: TemplateChild, 15 | #[template_child] 16 | pub multiplier: TemplateChild, 17 | #[template_child] 18 | pub flow_scale: TemplateChild, 19 | #[template_child] 20 | pub performance_mode: TemplateChild, 21 | #[template_child] 22 | pub hdr_mode: TemplateChild, 23 | #[template_child] 24 | pub experimental_present_mode: TemplateChild 25 | } 26 | 27 | #[glib::object_subclass] 28 | impl ObjectSubclass for PaneMain { 29 | const NAME: &'static str = "LSPaneMain"; 30 | type Type = super::PaneMain; 31 | type ParentType = adw::NavigationPage; 32 | 33 | fn class_init(klass: &mut Self::Class) { 34 | klass.bind_template(); 35 | } 36 | 37 | fn instance_init(obj: &glib::subclass::InitializingObject) { 38 | obj.init_template(); 39 | } 40 | } 41 | 42 | impl ObjectImpl for PaneMain { 43 | fn constructed(&self) { 44 | self.parent_constructed(); 45 | } 46 | } 47 | 48 | impl WidgetImpl for PaneMain {} 49 | impl NavigationPageImpl for PaneMain {} 50 | -------------------------------------------------------------------------------- /ui/src/wrapper/pref/entry.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use gtk::glib; 4 | use gtk::subclass::prelude::*; 5 | use adw::subclass::prelude::*; 6 | use adw::prelude::*; 7 | 8 | #[derive(gtk::CompositeTemplate, glib::Properties, Default)] 9 | #[properties(wrapper_type = super::PrefEntry)] 10 | #[template(resource = "/gay/pancake/lsfg-vk/pref/entry.ui")] 11 | pub struct PrefEntry { 12 | #[property(get, set)] 13 | opt_name: RefCell, 14 | #[property(get, set)] 15 | opt_subtitle: RefCell, 16 | #[property(get, set)] 17 | default_text: RefCell, 18 | #[property(get, set)] 19 | tooltip_text: RefCell, 20 | #[property(get, set)] 21 | icon_name: RefCell, 22 | 23 | #[template_child] 24 | pub entry: TemplateChild, 25 | #[template_child] 26 | pub btn: TemplateChild, 27 | } 28 | 29 | #[glib::object_subclass] 30 | impl ObjectSubclass for PrefEntry { 31 | const NAME: &'static str = "LSPrefEntry"; 32 | type Type = super::PrefEntry; 33 | type ParentType = adw::PreferencesRow; 34 | 35 | fn class_init(klass: &mut Self::Class) { 36 | klass.bind_template(); 37 | } 38 | 39 | fn instance_init(obj: &glib::subclass::InitializingObject) { 40 | obj.init_template(); 41 | } 42 | } 43 | 44 | #[glib::derived_properties] 45 | impl ObjectImpl for PrefEntry { 46 | fn constructed(&self) { 47 | self.parent_constructed(); 48 | } 49 | } 50 | 51 | impl WidgetImpl for PrefEntry {} 52 | impl ListBoxRowImpl for PrefEntry {} 53 | impl PreferencesRowImpl for PrefEntry {} 54 | -------------------------------------------------------------------------------- /framegen/src/core/fence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "core/fence.hpp" 5 | #include "core/device.hpp" 6 | #include "common/exception.hpp" 7 | 8 | #include 9 | #include 10 | 11 | using namespace LSFG::Core; 12 | 13 | Fence::Fence(const Core::Device& device) { 14 | // create fence 15 | const VkFenceCreateInfo desc{ 16 | .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO 17 | }; 18 | VkFence fenceHandle{}; 19 | auto res = vkCreateFence(device.handle(), &desc, nullptr, &fenceHandle); 20 | if (res != VK_SUCCESS || fenceHandle == VK_NULL_HANDLE) 21 | throw LSFG::vulkan_error(res, "Unable to create fence"); 22 | 23 | // store fence in shared ptr 24 | this->fence = std::shared_ptr( 25 | new VkFence(fenceHandle), 26 | [dev = device.handle()](VkFence* fenceHandle) { 27 | vkDestroyFence(dev, *fenceHandle, nullptr); 28 | } 29 | ); 30 | } 31 | 32 | void Fence::reset(const Core::Device& device) const { 33 | VkFence fenceHandle = this->handle(); 34 | auto res = vkResetFences(device.handle(), 1, &fenceHandle); 35 | if (res != VK_SUCCESS) 36 | throw LSFG::vulkan_error(res, "Unable to reset fence"); 37 | } 38 | 39 | bool Fence::wait(const Core::Device& device, uint64_t timeout) const { 40 | VkFence fenceHandle = this->handle(); 41 | auto res = vkWaitForFences(device.handle(), 1, &fenceHandle, VK_TRUE, timeout); 42 | if (res != VK_SUCCESS && res != VK_TIMEOUT) 43 | throw LSFG::vulkan_error(res, "Unable to wait for fence"); 44 | 45 | return res == VK_SUCCESS; 46 | } 47 | -------------------------------------------------------------------------------- /framegen/src/core/instance.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "core/instance.hpp" 5 | #include "common/exception.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace LSFG::Core; 12 | 13 | const std::vector requiredExtensions = { 14 | 15 | }; 16 | 17 | Instance::Instance() { 18 | volkInitialize(); 19 | 20 | // create Vulkan instance 21 | const VkApplicationInfo appInfo{ 22 | .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, 23 | .pApplicationName = "lsfg-vk-base", 24 | .applicationVersion = VK_MAKE_VERSION(0, 0, 1), 25 | .pEngineName = "lsfg-vk-base", 26 | .engineVersion = VK_MAKE_VERSION(0, 0, 1), 27 | .apiVersion = VK_API_VERSION_1_3 28 | }; 29 | const VkInstanceCreateInfo createInfo{ 30 | .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 31 | .pApplicationInfo = &appInfo, 32 | .enabledExtensionCount = static_cast(requiredExtensions.size()), 33 | .ppEnabledExtensionNames = requiredExtensions.data() 34 | }; 35 | VkInstance instanceHandle{}; 36 | auto res = vkCreateInstance(&createInfo, nullptr, &instanceHandle); 37 | if (res != VK_SUCCESS) 38 | throw LSFG::vulkan_error(res, "Failed to create Vulkan instance"); 39 | 40 | volkLoadInstance(instanceHandle); 41 | 42 | // store in shared ptr 43 | this->instance = std::shared_ptr( 44 | new VkInstance(instanceHandle), 45 | [](VkInstance* instance) { 46 | vkDestroyInstance(*instance, nullptr); 47 | } 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /ui/src/utils.rs: -------------------------------------------------------------------------------- 1 | use procfs::{process, ProcResult}; 2 | 3 | pub fn find_vulkan_processes() -> ProcResult> { 4 | let mut processes = Vec::new(); 5 | let apps = process::all_processes()?; 6 | for app in apps { 7 | let Ok(prc) = app else { continue; }; 8 | 9 | // ensure vulkan is loaded 10 | let Ok(maps) = proc_maps::get_process_maps(prc.pid()) else { 11 | continue; 12 | }; 13 | let result = maps.iter() 14 | .filter_map(|map| map.filename()) 15 | .map(|filename| filename.to_string_lossy().to_string()) 16 | .any(|filename| filename.to_lowercase().contains("vulkan")); 17 | if !result { 18 | continue; 19 | } 20 | 21 | // find executed binary 22 | let mut exe = prc.exe()?.to_string_lossy().to_string(); 23 | 24 | // replace binary with exe for wine apps 25 | if exe.contains("wine") || exe.contains("proton") { 26 | let result = maps.iter() 27 | .filter_map(|map| map.filename()) 28 | .map(|filename| filename.to_string_lossy().to_string()) 29 | .find(|filename| filename.ends_with(".exe")); 30 | 31 | if let Some(exe_name) = result { 32 | exe = exe_name; 33 | } 34 | } 35 | 36 | // split off last part of the path 37 | exe = exe.split('/').last().unwrap_or(&exe).to_string(); 38 | 39 | // format process information 40 | let pid = prc.pid(); 41 | let process_info = format!("PID {}: {}", pid, exe); 42 | processes.push((process_info, exe)); 43 | } 44 | 45 | Ok(processes) 46 | } 47 | -------------------------------------------------------------------------------- /ui/rsc/popup/process.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 39 | 40 | -------------------------------------------------------------------------------- /framegen/src/core/descriptorpool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "core/descriptorpool.hpp" 5 | #include "core/device.hpp" 6 | #include "common/exception.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace LSFG::Core; 13 | 14 | DescriptorPool::DescriptorPool(const Core::Device& device) { 15 | // create descriptor pool 16 | const std::array pools{{ // arbitrary limits 17 | { .type = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = 4096 }, 18 | { .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = 4096 }, 19 | { .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 4096 }, 20 | { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 4096 } 21 | }}; 22 | const VkDescriptorPoolCreateInfo desc{ 23 | .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, 24 | .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 25 | .maxSets = 16384, 26 | .poolSizeCount = static_cast(pools.size()), 27 | .pPoolSizes = pools.data() 28 | }; 29 | VkDescriptorPool poolHandle{}; 30 | auto res = vkCreateDescriptorPool(device.handle(), &desc, nullptr, &poolHandle); 31 | if (res != VK_SUCCESS || poolHandle == VK_NULL_HANDLE) 32 | throw LSFG::vulkan_error(res, "Unable to create descriptor pool"); 33 | 34 | // store pool in shared ptr 35 | this->descriptorPool = std::shared_ptr( 36 | new VkDescriptorPool(poolHandle), 37 | [dev = device.handle()](VkDescriptorPool* poolHandle) { 38 | vkDestroyDescriptorPool(dev, *poolHandle, nullptr); 39 | } 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | if(NOT LSFGVK_EXCESS_DEBUG) 4 | set(CMAKE_C_VISIBILITY_PRESET "hidden") 5 | set(CMAKE_CXX_VISIBILITY_PRESET "hidden") 6 | endif() 7 | 8 | if(LSFGVK_EXCESS_DEBUG) 9 | add_compile_definitions(LSFGVK_EXCESS_DEBUG) 10 | endif() 11 | 12 | project(lsfg-vk-test 13 | DESCRIPTION "Test: lsfg-vk" 14 | LANGUAGES CXX) 15 | 16 | file(GLOB SOURCES 17 | "src/*.cpp" 18 | ) 19 | 20 | add_executable(lsfg-vk-test ${SOURCES}) 21 | 22 | # target 23 | set_target_properties(lsfg-vk-test PROPERTIES 24 | CXX_STANDARD 20 25 | CXX_STANDARD_REQUIRED ON) 26 | target_link_libraries(lsfg-vk-test PUBLIC 27 | lsfg-vk lsfg-vk-framegen 28 | vulkan) 29 | 30 | # diagnostics 31 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 32 | set_target_properties(lsfg-vk-test PROPERTIES 33 | EXPORT_COMPILE_COMMANDS ON) 34 | endif() 35 | 36 | if(LSFGVK_EXCESS_DEBUG) 37 | target_compile_options(lsfg-vk-test PRIVATE 38 | -Weverything 39 | # disable compat c++ flags 40 | -Wno-pre-c++20-compat-pedantic 41 | -Wno-pre-c++17-compat 42 | -Wno-c++98-compat-pedantic 43 | -Wno-c++98-compat 44 | # disable other flags 45 | -Wno-missing-designated-field-initializers 46 | -Wno-shadow # allow shadowing 47 | -Wno-switch-enum # ignore missing cases 48 | -Wno-switch-default # ignore missing default 49 | -Wno-padded # ignore automatic padding 50 | -Wno-exit-time-destructors # allow globals 51 | -Wno-global-constructors # allow globals 52 | -Wno-cast-function-type-strict # for vulkan 53 | ) 54 | 55 | set_target_properties(lsfg-vk-test PROPERTIES 56 | CXX_CLANG_TIDY clang-tidy) 57 | endif() 58 | -------------------------------------------------------------------------------- /framegen/include/core/pipeline.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/commandbuffer.hpp" 4 | #include "core/shadermodule.hpp" 5 | #include "core/device.hpp" 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace LSFG::Core { 12 | 13 | /// 14 | /// C++ wrapper class for a Vulkan pipeline. 15 | /// 16 | /// This class manages the lifetime of a Vulkan pipeline. 17 | /// 18 | class Pipeline { 19 | public: 20 | Pipeline() noexcept = default; 21 | 22 | /// 23 | /// Create a compute pipeline. 24 | /// 25 | /// @param device Vulkan device 26 | /// @param shader Shader module to use for the pipeline. 27 | /// 28 | /// @throws LSFG::vulkan_error if object creation fails. 29 | /// 30 | Pipeline(const Core::Device& device, const ShaderModule& shader); 31 | 32 | /// 33 | /// Bind the pipeline to a command buffer. 34 | /// 35 | /// @param commandBuffer Command buffer to bind the pipeline to. 36 | /// 37 | void bind(const CommandBuffer& commandBuffer) const; 38 | 39 | /// Get the Vulkan handle. 40 | [[nodiscard]] auto handle() const { return *this->pipeline; } 41 | /// Get the pipeline layout. 42 | [[nodiscard]] auto getLayout() const { return *this->layout; } 43 | 44 | /// Trivially copyable, moveable and destructible 45 | Pipeline(const Pipeline&) noexcept = default; 46 | Pipeline& operator=(const Pipeline&) noexcept = default; 47 | Pipeline(Pipeline&&) noexcept = default; 48 | Pipeline& operator=(Pipeline&&) noexcept = default; 49 | ~Pipeline() = default; 50 | private: 51 | std::shared_ptr pipeline; 52 | std::shared_ptr layout; 53 | }; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /framegen/include/core/shadermodule.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/device.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace LSFG::Core { 14 | 15 | /// 16 | /// C++ wrapper class for a Vulkan shader module. 17 | /// 18 | /// This class manages the lifetime of a Vulkan shader module. 19 | /// 20 | class ShaderModule { 21 | public: 22 | ShaderModule() noexcept = default; 23 | 24 | /// 25 | /// Create the shader module. 26 | /// 27 | /// @param device Vulkan device 28 | /// @param code SPIR-V bytecode for the shader. 29 | /// @param descriptorTypes Descriptor types used in the shader. 30 | /// 31 | /// @throws LSFG::vulkan_error if object creation fails. 32 | /// 33 | ShaderModule(const Core::Device& device, const std::vector& code, 34 | const std::vector>& descriptorTypes); 35 | 36 | /// Get the Vulkan handle. 37 | [[nodiscard]] auto handle() const { return *this->shaderModule; } 38 | /// Get the descriptor set layout. 39 | [[nodiscard]] auto getLayout() const { return *this->descriptorSetLayout; } 40 | 41 | /// Trivially copyable, moveable and destructible 42 | ShaderModule(const ShaderModule&) noexcept = default; 43 | ShaderModule& operator=(const ShaderModule&) noexcept = default; 44 | ShaderModule(ShaderModule&&) noexcept = default; 45 | ShaderModule& operator=(ShaderModule&&) noexcept = default; 46 | ~ShaderModule() = default; 47 | private: 48 | std::shared_ptr shaderModule; 49 | std::shared_ptr descriptorSetLayout; 50 | }; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /framegen/v3.1_include/v3_1/shaders/mipmaps.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/buffer.hpp" 4 | #include "core/commandbuffer.hpp" 5 | #include "core/descriptorset.hpp" 6 | #include "core/image.hpp" 7 | #include "core/pipeline.hpp" 8 | #include "core/sampler.hpp" 9 | #include "core/shadermodule.hpp" 10 | #include "common/utils.hpp" 11 | 12 | #include 13 | #include 14 | 15 | namespace LSFG_3_1::Shaders { 16 | 17 | using namespace LSFG; 18 | 19 | /// 20 | /// Mipmaps shader. 21 | /// 22 | class Mipmaps { 23 | public: 24 | Mipmaps() = default; 25 | 26 | /// 27 | /// Initialize the shaderchain. 28 | /// 29 | /// @param inImg_0 The next frame (when fc % 2 == 0) 30 | /// @param inImg_1 The next frame (when fc % 2 == 1) 31 | /// 32 | /// @throws LSFG::vulkan_error if resource creation fails. 33 | /// 34 | Mipmaps(Vulkan& vk, Core::Image inImg_0, Core::Image inImg_1); 35 | 36 | /// 37 | /// Dispatch the shaderchain. 38 | /// 39 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); 40 | 41 | /// Get the output images. 42 | [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } 43 | 44 | /// Trivially copyable, moveable and destructible 45 | Mipmaps(const Mipmaps&) noexcept = default; 46 | Mipmaps& operator=(const Mipmaps&) noexcept = default; 47 | Mipmaps(Mipmaps&&) noexcept = default; 48 | Mipmaps& operator=(Mipmaps&&) noexcept = default; 49 | ~Mipmaps() = default; 50 | private: 51 | Core::ShaderModule shaderModule; 52 | Core::Pipeline pipeline; 53 | Core::Buffer buffer; 54 | Core::Sampler sampler; 55 | std::array descriptorSets; 56 | 57 | Core::Image inImg_0, inImg_1; 58 | std::array outImgs; 59 | }; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /framegen/v3.1p_include/v3_1p/shaders/mipmaps.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/buffer.hpp" 4 | #include "core/commandbuffer.hpp" 5 | #include "core/descriptorset.hpp" 6 | #include "core/image.hpp" 7 | #include "core/pipeline.hpp" 8 | #include "core/sampler.hpp" 9 | #include "core/shadermodule.hpp" 10 | #include "common/utils.hpp" 11 | 12 | #include 13 | #include 14 | 15 | namespace LSFG_3_1P::Shaders { 16 | 17 | using namespace LSFG; 18 | 19 | /// 20 | /// Mipmaps shader. 21 | /// 22 | class Mipmaps { 23 | public: 24 | Mipmaps() = default; 25 | 26 | /// 27 | /// Initialize the shaderchain. 28 | /// 29 | /// @param inImg_0 The next frame (when fc % 2 == 0) 30 | /// @param inImg_1 The next frame (when fc % 2 == 1) 31 | /// 32 | /// @throws LSFG::vulkan_error if resource creation fails. 33 | /// 34 | Mipmaps(Vulkan& vk, Core::Image inImg_0, Core::Image inImg_1); 35 | 36 | /// 37 | /// Dispatch the shaderchain. 38 | /// 39 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); 40 | 41 | /// Get the output images. 42 | [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } 43 | 44 | /// Trivially copyable, moveable and destructible 45 | Mipmaps(const Mipmaps&) noexcept = default; 46 | Mipmaps& operator=(const Mipmaps&) noexcept = default; 47 | Mipmaps(Mipmaps&&) noexcept = default; 48 | Mipmaps& operator=(Mipmaps&&) noexcept = default; 49 | ~Mipmaps() = default; 50 | private: 51 | Core::ShaderModule shaderModule; 52 | Core::Pipeline pipeline; 53 | Core::Buffer buffer; 54 | Core::Sampler sampler; 55 | std::array descriptorSets; 56 | 57 | Core::Image inImg_0, inImg_1; 58 | std::array outImgs; 59 | }; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /ui/build_appimage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eux 3 | 4 | # AppImage build script provided by @Samueru-sama 5 | # (with removed aarch64 supported and removed update functionality, temporarily) 6 | 7 | URUNTIME="https://github.com/VHSgunzo/uruntime/releases/latest/download/uruntime-appimage-dwarfs-x86_64" 8 | URUNTIME_LITE="https://github.com/VHSgunzo/uruntime/releases/latest/download/uruntime-appimage-dwarfs-lite-x86_64" 9 | SHARUN="https://github.com/VHSgunzo/sharun/releases/latest/download/sharun-x86_64-aio" 10 | 11 | VERSION=$(awk -F'=|"' '/^version/{print $3}' ./Cargo.toml) 12 | echo "$VERSION-dev" > ~/version 13 | 14 | # grab required resources 15 | wget -O sharun-aio "$SHARUN" 16 | chmod +x sharun-aio 17 | 18 | wget -O uruntime "$URUNTIME" 19 | wget -O uruntime-lite "$URUNTIME_LITE" 20 | chmod +x uruntime uruntime-lite 21 | 22 | # build lsfg-vk-ui 23 | echo "Building lsfg-vk-ui..." 24 | cargo build --release 25 | 26 | # deploy app directory 27 | echo "Deploying app directory..." 28 | mkdir -p AppDir 29 | cp -v rsc/*.desktop AppDir 30 | cp -v rsc/icon.png AppDir/lsfg-vk-ui.png 31 | cp -v rsc/icon.png AppDir/.DirIcon 32 | mkdir -p AppDir/shared/bin 33 | mv -v target/release/lsfg-vk-ui AppDir/shared/bin 34 | 35 | cd AppDir 36 | xvfb-run -a ../sharun-aio l -p -v -e -s -k \ 37 | shared/bin/lsfg-vk-ui \ 38 | /usr/lib/*/libdecor* \ 39 | /usr/lib/*/gdk-pixbuf-*/*/loaders/* \ 40 | /usr/lib/*/gio/modules/libdconfsettings.so 41 | ln -fv ./sharun ./AppRun 42 | ./sharun -g 43 | cd .. 44 | 45 | # create appimage 46 | echo "Generating app image..." 47 | ./uruntime --appimage-mkdwarfs -f \ 48 | --set-owner 0 --set-group 0 \ 49 | --no-history --no-create-timestamp \ 50 | --compression zstd:level=22 -S26 -B8 \ 51 | --header uruntime-lite \ 52 | -i ./AppDir -o "./lsfg-vk-ui.AppImage" 53 | 54 | # cleanup 55 | echo "Cleaning up..." 56 | rm -rf AppDir 57 | rm -f sharun-aio uruntime uruntime-lite 58 | 59 | # done 60 | echo "Done" 61 | -------------------------------------------------------------------------------- /framegen/v3.1p_include/v3_1p/shaders/alpha.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/commandbuffer.hpp" 4 | #include "core/descriptorset.hpp" 5 | #include "core/image.hpp" 6 | #include "core/pipeline.hpp" 7 | #include "core/sampler.hpp" 8 | #include "core/shadermodule.hpp" 9 | #include "common/utils.hpp" 10 | 11 | #include 12 | #include 13 | 14 | namespace LSFG_3_1P::Shaders { 15 | 16 | using namespace LSFG; 17 | 18 | /// 19 | /// Alpha shader. 20 | /// 21 | class Alpha { 22 | public: 23 | Alpha() = default; 24 | 25 | /// 26 | /// Initialize the shaderchain. 27 | /// 28 | /// @param inImg One mipmap level 29 | /// 30 | /// @throws LSFG::vulkan_error if resource creation fails. 31 | /// 32 | Alpha(Vulkan& vk, Core::Image inImg); 33 | 34 | /// 35 | /// Dispatch the shaderchain. 36 | /// 37 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); 38 | 39 | /// Get the output images 40 | [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } 41 | 42 | /// Trivially copyable, moveable and destructible 43 | Alpha(const Alpha&) noexcept = default; 44 | Alpha& operator=(const Alpha&) noexcept = default; 45 | Alpha(Alpha&&) noexcept = default; 46 | Alpha& operator=(Alpha&&) noexcept = default; 47 | ~Alpha() = default; 48 | private: 49 | std::array shaderModules; 50 | std::array pipelines; 51 | Core::Sampler sampler; 52 | std::array descriptorSets; 53 | std::array lastDescriptorSet; 54 | 55 | Core::Image inImg; 56 | Core::Image tempImg1; 57 | Core::Image tempImg2; 58 | std::array tempImgs3; 59 | std::array, 3> outImgs; 60 | }; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /framegen/include/core/fence.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/device.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace LSFG::Core { 11 | 12 | /// 13 | /// C++ wrapper class for a Vulkan fence. 14 | /// 15 | /// This class manages the lifetime of a Vulkan fence. 16 | /// 17 | class Fence { 18 | public: 19 | Fence() noexcept = default; 20 | 21 | /// 22 | /// Create the fence. 23 | /// 24 | /// @param device Vulkan device 25 | /// 26 | /// @throws LSFG::vulkan_error if object creation fails. 27 | /// 28 | Fence(const Core::Device& device); 29 | 30 | /// 31 | /// Reset the fence to an unsignaled state. 32 | /// 33 | /// @param device Vulkan device 34 | /// 35 | /// @throws LSFG::vulkan_error if resetting fails. 36 | /// 37 | void reset(const Core::Device& device) const; 38 | 39 | /// 40 | /// Wait for the fence 41 | /// 42 | /// @param device Vulkan device 43 | /// @param timeout The timeout in nanoseconds, or UINT64_MAX for no timeout. 44 | /// @returns true if the fence signaled, false if it timed out. 45 | /// 46 | /// @throws LSFG::vulkan_error if waiting fails. 47 | /// 48 | [[nodiscard]] bool wait(const Core::Device& device, uint64_t timeout = UINT64_MAX) const; 49 | 50 | /// Get the Vulkan handle. 51 | [[nodiscard]] auto handle() const { return *this->fence; } 52 | 53 | // Trivially copyable, moveable and destructible 54 | Fence(const Fence&) noexcept = default; 55 | Fence& operator=(const Fence&) noexcept = default; 56 | Fence(Fence&&) noexcept = default; 57 | Fence& operator=(Fence&&) noexcept = default; 58 | ~Fence() = default; 59 | private: 60 | std::shared_ptr fence; 61 | }; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /framegen/v3.1_include/v3_1/shaders/alpha.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/commandbuffer.hpp" 4 | #include "core/descriptorset.hpp" 5 | #include "core/image.hpp" 6 | #include "core/pipeline.hpp" 7 | #include "core/sampler.hpp" 8 | #include "core/shadermodule.hpp" 9 | #include "common/utils.hpp" 10 | 11 | #include 12 | #include 13 | 14 | namespace LSFG_3_1::Shaders { 15 | 16 | using namespace LSFG; 17 | 18 | /// 19 | /// Alpha shader. 20 | /// 21 | class Alpha { 22 | public: 23 | Alpha() = default; 24 | 25 | /// 26 | /// Initialize the shaderchain. 27 | /// 28 | /// @param inImg One mipmap level 29 | /// 30 | /// @throws LSFG::vulkan_error if resource creation fails. 31 | /// 32 | Alpha(Vulkan& vk, Core::Image inImg); 33 | 34 | /// 35 | /// Dispatch the shaderchain. 36 | /// 37 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); 38 | 39 | /// Get the output images 40 | [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } 41 | 42 | /// Trivially copyable, moveable and destructible 43 | Alpha(const Alpha&) noexcept = default; 44 | Alpha& operator=(const Alpha&) noexcept = default; 45 | Alpha(Alpha&&) noexcept = default; 46 | Alpha& operator=(Alpha&&) noexcept = default; 47 | ~Alpha() = default; 48 | private: 49 | std::array shaderModules; 50 | std::array pipelines; 51 | Core::Sampler sampler; 52 | std::array descriptorSets; 53 | std::array lastDescriptorSet; 54 | 55 | Core::Image inImg; 56 | std::array tempImgs1; 57 | std::array tempImgs2; 58 | std::array tempImgs3; 59 | std::array, 3> outImgs; 60 | }; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /ui/src/ui/entry_handler.rs: -------------------------------------------------------------------------------- 1 | use adw::subclass::prelude::ObjectSubclassIsExt; 2 | use gtk::{gio, glib::object::CastNone, prelude::{ButtonExt, ListBoxRowExt, WidgetExt}}; 3 | 4 | use crate::{config, wrapper::entry, STATE}; 5 | 6 | /// 7 | /// Register signals for removing presets when adding a new entry. 8 | /// 9 | pub fn add_entry(entry_: entry::Entry, profiles_: gtk::ListBox) { 10 | let entry = entry_.clone(); 11 | let profiles = profiles_.clone(); 12 | entry_.imp().delete.connect_clicked(move |btn| { 13 | // prompt for confirmation 14 | let dialog = gtk::AlertDialog::builder() 15 | .message("Delete Profile") 16 | .detail("Are you sure you want to delete this profile?") 17 | .buttons(vec!["Cancel".to_string(), "Delete".to_string()]) 18 | .cancel_button(0) 19 | .default_button(1) 20 | .modal(true) 21 | .build(); 22 | let window = btn.root() 23 | .and_downcast::() 24 | .expect("Button root is not a Window"); 25 | 26 | let profiles = profiles.clone(); 27 | let entry = entry.clone(); 28 | dialog.choose(Some(&window), gio::Cancellable::NONE, move |result| { 29 | if result.is_err() || result.unwrap() != 1 { 30 | return; 31 | } 32 | 33 | // remove config entry 34 | let _ = config::edit_config(|config| { 35 | config.game.remove(entry.index() as usize); 36 | }); 37 | 38 | // remove ui entry 39 | profiles.remove(&entry); 40 | 41 | // select next entry 42 | let state = STATE.get().unwrap().clone(); 43 | if let Ok(mut state) = state.write() { 44 | state.selected_game = None; 45 | } 46 | 47 | if let Some(entry) = profiles.row_at_index(0) { 48 | entry.activate(); 49 | } 50 | }); 51 | }); 52 | 53 | profiles_.append(&entry_); 54 | } 55 | -------------------------------------------------------------------------------- /ui/rsc/pref/switch.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52 | 53 | -------------------------------------------------------------------------------- /ui/rsc/pane/sidebar.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 61 | 62 | -------------------------------------------------------------------------------- /framegen/v3.1_include/v3_1/shaders/beta.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/buffer.hpp" 4 | #include "core/commandbuffer.hpp" 5 | #include "core/descriptorset.hpp" 6 | #include "core/image.hpp" 7 | #include "core/pipeline.hpp" 8 | #include "core/sampler.hpp" 9 | #include "core/shadermodule.hpp" 10 | #include "common/utils.hpp" 11 | 12 | #include 13 | #include 14 | 15 | namespace LSFG_3_1::Shaders { 16 | 17 | using namespace LSFG; 18 | 19 | /// 20 | /// Beta shader. 21 | /// 22 | class Beta { 23 | public: 24 | Beta() = default; 25 | 26 | /// 27 | /// Initialize the shaderchain. 28 | /// 29 | /// @param inImgs Three sets of four RGBA images, corresponding to a frame count % 3. 30 | /// 31 | /// @throws LSFG::vulkan_error if resource creation fails. 32 | /// 33 | Beta(Vulkan& vk, std::array, 3> inImgs); 34 | 35 | /// 36 | /// Dispatch the shaderchain. 37 | /// 38 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); 39 | 40 | /// Get the output images 41 | [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } 42 | 43 | /// Trivially copyable, moveable and destructible 44 | Beta(const Beta&) noexcept = default; 45 | Beta& operator=(const Beta&) noexcept = default; 46 | Beta(Beta&&) noexcept = default; 47 | Beta& operator=(Beta&&) noexcept = default; 48 | ~Beta() = default; 49 | private: 50 | std::array shaderModules; 51 | std::array pipelines; 52 | std::array samplers; 53 | Core::Buffer buffer; 54 | std::array firstDescriptorSet; 55 | std::array descriptorSets; 56 | 57 | std::array, 3> inImgs; 58 | std::array tempImgs1; 59 | std::array tempImgs2; 60 | std::array outImgs; 61 | }; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /framegen/v3.1p_include/v3_1p/shaders/beta.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/buffer.hpp" 4 | #include "core/commandbuffer.hpp" 5 | #include "core/descriptorset.hpp" 6 | #include "core/image.hpp" 7 | #include "core/pipeline.hpp" 8 | #include "core/sampler.hpp" 9 | #include "core/shadermodule.hpp" 10 | #include "common/utils.hpp" 11 | 12 | #include 13 | #include 14 | 15 | namespace LSFG_3_1P::Shaders { 16 | 17 | using namespace LSFG; 18 | 19 | /// 20 | /// Beta shader. 21 | /// 22 | class Beta { 23 | public: 24 | Beta() = default; 25 | 26 | /// 27 | /// Initialize the shaderchain. 28 | /// 29 | /// @param inImgs Three sets of two RGBA images, corresponding to a frame count % 3. 30 | /// 31 | /// @throws LSFG::vulkan_error if resource creation fails. 32 | /// 33 | Beta(Vulkan& vk, std::array, 3> inImgs); 34 | 35 | /// 36 | /// Dispatch the shaderchain. 37 | /// 38 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); 39 | 40 | /// Get the output images 41 | [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } 42 | 43 | /// Trivially copyable, moveable and destructible 44 | Beta(const Beta&) noexcept = default; 45 | Beta& operator=(const Beta&) noexcept = default; 46 | Beta(Beta&&) noexcept = default; 47 | Beta& operator=(Beta&&) noexcept = default; 48 | ~Beta() = default; 49 | private: 50 | std::array shaderModules; 51 | std::array pipelines; 52 | std::array samplers; 53 | Core::Buffer buffer; 54 | std::array firstDescriptorSet; 55 | std::array descriptorSets; 56 | 57 | std::array, 3> inImgs; 58 | std::array tempImgs1; 59 | std::array tempImgs2; 60 | std::array outImgs; 61 | }; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /ui/rsc/pref/dropdown.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 52 | 53 | -------------------------------------------------------------------------------- /framegen/include/core/device.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/instance.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace LSFG::Core { 11 | 12 | /// 13 | /// C++ wrapper class for a Vulkan device. 14 | /// 15 | /// This class manages the lifetime of a Vulkan device. 16 | /// 17 | class Device { 18 | public: 19 | /// 20 | /// Create the device. 21 | /// 22 | /// @param instance Vulkan instance 23 | /// @param deviceUUID The UUID of the Vulkan device to use. 24 | /// @param forceDisableFp16 Force-disable FP16 shaders. 25 | /// 26 | /// @throws LSFG::vulkan_error if object creation fails. 27 | /// 28 | Device(const Instance& instance, uint64_t deviceUUID, bool forceDisableFp16); 29 | 30 | /// Get the Vulkan handle. 31 | [[nodiscard]] auto handle() const { return *this->device; } 32 | /// Get the physical device associated with this logical device. 33 | [[nodiscard]] VkPhysicalDevice getPhysicalDevice() const { return this->physicalDevice; } 34 | /// Get the compute queue family index. 35 | [[nodiscard]] uint32_t getComputeFamilyIdx() const { return this->computeFamilyIdx; } 36 | /// Get the compute queue. 37 | [[nodiscard]] VkQueue getComputeQueue() const { return this->computeQueue; } 38 | /// Check if the device supports FP16. 39 | [[nodiscard]] bool getFP16Support() const { return this->supportsFP16; } 40 | 41 | // Trivially copyable, moveable and destructible 42 | Device(const Core::Device&) noexcept = default; 43 | Device& operator=(const Core::Device&) noexcept = default; 44 | Device(Device&&) noexcept = default; 45 | Device& operator=(Device&&) noexcept = default; 46 | ~Device() = default; 47 | private: 48 | std::shared_ptr device; 49 | VkPhysicalDevice physicalDevice{}; 50 | 51 | uint32_t computeFamilyIdx{0}; 52 | bool supportsFP16{false}; 53 | 54 | VkQueue computeQueue{}; 55 | }; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /include/config/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace Config { 11 | 12 | /// Global lsfg-vk configuration. 13 | struct GlobalConfiguration { 14 | /// Path to Lossless.dll. 15 | std::string dll; 16 | /// Whether FP16 is force-disabled 17 | bool no_fp16{false}; 18 | 19 | /// Path to the configuration file. 20 | std::filesystem::path config_file; 21 | /// File timestamp of the configuration file 22 | std::chrono::time_point timestamp; 23 | }; 24 | 25 | /// Per-application lsfg-vk configuration. 26 | struct GameConfiguration { 27 | /// The frame generation muliplier 28 | size_t multiplier{2}; 29 | /// The internal flow scale factor 30 | float flowScale{1.0F}; 31 | /// Whether performance mode is enabled 32 | bool performance{false}; 33 | /// Whether HDR is enabled 34 | bool hdr{false}; 35 | 36 | /// Experimental flag for overriding the synchronization method. 37 | VkPresentModeKHR e_present{ VK_PRESENT_MODE_FIFO_KHR }; 38 | 39 | }; 40 | 41 | /// Global configuration. 42 | extern GlobalConfiguration globalConf; 43 | /// Currently active configuration. 44 | extern std::optional currentConf; 45 | 46 | /// 47 | /// Read the configuration file while preserving the previous configuration 48 | /// in case of an error. 49 | /// 50 | /// @param file The path to the configuration file. 51 | /// @param name The preset to activate 52 | /// 53 | /// @throws std::runtime_error if an error occurs while loading the configuration file. 54 | /// 55 | void updateConfig( 56 | const std::string& file, 57 | const std::pair& name 58 | ); 59 | 60 | /// 61 | /// Check if the configuration file is still up-to-date 62 | /// 63 | /// @return Whether the configuration is up-to-date or not. 64 | /// 65 | bool checkStatus(); 66 | 67 | } 68 | -------------------------------------------------------------------------------- /ui/src/wrapper/pref.rs: -------------------------------------------------------------------------------- 1 | use gtk::glib; 2 | use gtk; 3 | use adw; 4 | 5 | pub mod dropdown; 6 | pub mod entry; 7 | pub mod number; 8 | pub mod slider; 9 | pub mod switch; 10 | 11 | glib::wrapper! { 12 | pub struct PrefDropdown(ObjectSubclass) 13 | @extends 14 | adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, 15 | @implements 16 | gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; 17 | } 18 | 19 | glib::wrapper! { 20 | pub struct PrefSwitch(ObjectSubclass) 21 | @extends 22 | adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, 23 | @implements 24 | gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; 25 | } 26 | 27 | glib::wrapper! { 28 | pub struct PrefNumber(ObjectSubclass) 29 | @extends 30 | adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, 31 | @implements 32 | gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; 33 | } 34 | 35 | glib::wrapper! { 36 | pub struct PrefSlider(ObjectSubclass) 37 | @extends 38 | adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, 39 | @implements 40 | gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; 41 | } 42 | 43 | glib::wrapper! { 44 | pub struct PrefEntry(ObjectSubclass) 45 | @extends 46 | adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, 47 | @implements 48 | gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; 49 | } 50 | 51 | impl PrefDropdown { 52 | pub fn new() -> Self { 53 | glib::Object::new() 54 | } 55 | } 56 | 57 | impl PrefSwitch { 58 | pub fn new() -> Self { 59 | glib::Object::new() 60 | } 61 | } 62 | 63 | impl PrefNumber { 64 | pub fn new() -> Self { 65 | glib::Object::new() 66 | } 67 | } 68 | 69 | impl PrefSlider { 70 | pub fn new() -> Self { 71 | glib::Object::new() 72 | } 73 | } 74 | 75 | impl PrefEntry { 76 | pub fn new() -> Self { 77 | glib::Object::new() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /framegen/src/pool/resourcepool.cpp: -------------------------------------------------------------------------------- 1 | #include "pool/resourcepool.hpp" 2 | #include "core/buffer.hpp" 3 | #include "core/device.hpp" 4 | #include "core/sampler.hpp" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | using namespace LSFG; 12 | using namespace LSFG::Pool; 13 | 14 | struct ConstantBuffer { 15 | std::array inputOffset; 16 | uint32_t firstIter; 17 | uint32_t firstIterS; 18 | uint32_t advancedColorKind; 19 | uint32_t hdrSupport; 20 | float resolutionInvScale; 21 | float timestamp; 22 | float uiThreshold; 23 | std::array pad; 24 | }; 25 | 26 | Core::Buffer ResourcePool::getBuffer( 27 | const Core::Device& device, 28 | float timestamp, bool firstIter, bool firstIterS) { 29 | uint64_t hash = 0; 30 | const union { float f; uint32_t i; } u{ 31 | .f = timestamp }; 32 | hash |= u.i; 33 | hash |= static_cast(firstIter) << 32; 34 | hash |= static_cast(firstIterS) << 33; 35 | 36 | auto it = buffers.find(hash); 37 | if (it != buffers.end()) 38 | return it->second; 39 | 40 | // create the buffer 41 | const ConstantBuffer data{ 42 | .inputOffset = { 0, 0 }, 43 | .advancedColorKind = this->isHdr ? 2U : 0U, 44 | .hdrSupport = this->isHdr, 45 | .resolutionInvScale = this->flowScale, 46 | .timestamp = timestamp, 47 | .uiThreshold = 0.5F, 48 | }; 49 | Core::Buffer buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); 50 | buffers[hash] = buffer; 51 | return buffer; 52 | } 53 | 54 | Core::Sampler ResourcePool::getSampler( 55 | const Core::Device& device, 56 | VkSamplerAddressMode type, 57 | VkCompareOp compare, 58 | bool isWhite) { 59 | uint64_t hash = 0; 60 | hash |= static_cast(type) << 0; 61 | hash |= static_cast(compare) << 8; 62 | hash |= static_cast(isWhite) << 16; 63 | 64 | auto it = samplers.find(hash); 65 | if (it != samplers.end()) 66 | return it->second; 67 | 68 | // create the sampler 69 | Core::Sampler sampler(device, type, compare, isWhite); 70 | samplers[hash] = sampler; 71 | return sampler; 72 | } 73 | -------------------------------------------------------------------------------- /framegen/include/common/exception.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace LSFG { 10 | 11 | /// Simple exception class for Vulkan errors. 12 | class vulkan_error : public std::runtime_error { 13 | public: 14 | /// 15 | /// Construct a vulkan_error with a message and a Vulkan result code. 16 | /// 17 | /// @param result The Vulkan result code associated with the error. 18 | /// @param message The error message. 19 | /// 20 | explicit vulkan_error(VkResult result, const std::string& message); 21 | 22 | /// Get the Vulkan result code associated with this error. 23 | [[nodiscard]] VkResult error() const { return this->result; } 24 | 25 | // Trivially copyable, moveable and destructible 26 | vulkan_error(const vulkan_error&) = default; 27 | vulkan_error(vulkan_error&&) = default; 28 | vulkan_error& operator=(const vulkan_error&) = default; 29 | vulkan_error& operator=(vulkan_error&&) = default; 30 | ~vulkan_error() noexcept override; 31 | private: 32 | VkResult result; 33 | }; 34 | 35 | /// Simple exception class for stacking errors. 36 | class rethrowable_error : public std::runtime_error { 37 | public: 38 | /// 39 | /// Construct a new rethrowable_error with a message. 40 | /// 41 | /// @param message The error message. 42 | /// @param exe The original exception to rethrow. 43 | /// 44 | explicit rethrowable_error(const std::string& message, 45 | const std::exception& exe); 46 | 47 | /// Get the exception as a string. 48 | [[nodiscard]] const char* what() const noexcept override { 49 | return message.c_str(); 50 | } 51 | 52 | // Trivially copyable, moveable and destructible 53 | rethrowable_error(const rethrowable_error&) = default; 54 | rethrowable_error(rethrowable_error&&) = default; 55 | rethrowable_error& operator=(const rethrowable_error&) = default; 56 | rethrowable_error& operator=(rethrowable_error&&) = default; 57 | ~rethrowable_error() noexcept override; 58 | private: 59 | std::string message; 60 | }; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /include/mini/image.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace Mini { 8 | 9 | /// 10 | /// C++ wrapper class for a Vulkan image. 11 | /// 12 | /// This class manages the lifetime of a Vulkan image. 13 | /// 14 | class Image { 15 | public: 16 | Image() noexcept = default; 17 | 18 | /// 19 | /// Create the image and export the backing fd 20 | /// 21 | /// @param device Vulkan device 22 | /// @param physicalDevice Vulkan physical device 23 | /// @param extent Extent of the image in pixels. 24 | /// @param format Vulkan format of the image 25 | /// @param usage Usage flags for the image 26 | /// @param aspectFlags Aspect flags for the image view 27 | /// @param fd Pointer to an integer where the file descriptor will be stored. 28 | /// 29 | /// @throws LSFG::vulkan_error if object creation fails. 30 | /// 31 | Image(VkDevice device, VkPhysicalDevice physicalDevice, VkExtent2D extent, VkFormat format, 32 | VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd); 33 | 34 | /// Get the Vulkan handle. 35 | [[nodiscard]] auto handle() const { return *this->image; } 36 | /// Get the Vulkan device memory handle. 37 | [[nodiscard]] auto getMemory() const { return *this->memory; } 38 | /// Get the extent of the image. 39 | [[nodiscard]] VkExtent2D getExtent() const { return this->extent; } 40 | /// Get the format of the image. 41 | [[nodiscard]] VkFormat getFormat() const { return this->format; } 42 | /// Get the aspect flags of the image. 43 | [[nodiscard]] VkImageAspectFlags getAspectFlags() const { return this->aspectFlags; } 44 | 45 | /// Trivially copyable, moveable and destructible 46 | Image(const Image&) noexcept = default; 47 | Image& operator=(const Image&) noexcept = default; 48 | Image(Image&&) noexcept = default; 49 | Image& operator=(Image&&) noexcept = default; 50 | ~Image() = default; 51 | private: 52 | std::shared_ptr image; 53 | std::shared_ptr memory; 54 | 55 | VkExtent2D extent{}; 56 | VkFormat format{}; 57 | VkImageAspectFlags aspectFlags{}; 58 | }; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /framegen/v3.1_include/v3_1/shaders/generate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/buffer.hpp" 4 | #include "core/commandbuffer.hpp" 5 | #include "core/descriptorset.hpp" 6 | #include "core/image.hpp" 7 | #include "core/pipeline.hpp" 8 | #include "core/sampler.hpp" 9 | #include "core/shadermodule.hpp" 10 | #include "common/utils.hpp" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace LSFG_3_1::Shaders { 19 | 20 | using namespace LSFG; 21 | 22 | /// 23 | /// Generate shader. 24 | /// 25 | class Generate { 26 | public: 27 | Generate() = default; 28 | 29 | /// 30 | /// Initialize the shaderchain. 31 | /// 32 | /// @param inImg1 Input image 1. 33 | /// @param inImg2 Input image 2. 34 | /// @param inImg3 Input image 3. 35 | /// @param inImg4 Input image 4. 36 | /// @param inImg5 Input image 5. 37 | /// @param fds File descriptors for the output images. 38 | /// 39 | /// @throws LSFG::vulkan_error if resource creation fails. 40 | /// 41 | Generate(Vulkan& vk, 42 | Core::Image inImg1, Core::Image inImg2, 43 | Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, 44 | const std::vector& fds, VkFormat format); 45 | 46 | /// 47 | /// Dispatch the shaderchain. 48 | /// 49 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); 50 | 51 | /// Trivially copyable, moveable and destructible 52 | Generate(const Generate&) noexcept = default; 53 | Generate& operator=(const Generate&) noexcept = default; 54 | Generate(Generate&&) noexcept = default; 55 | Generate& operator=(Generate&&) noexcept = default; 56 | ~Generate() = default; 57 | private: 58 | Core::ShaderModule shaderModule; 59 | Core::Pipeline pipeline; 60 | std::array samplers; 61 | struct GeneratePass { 62 | Core::Buffer buffer; 63 | std::array descriptorSet; 64 | }; 65 | std::vector passes; 66 | 67 | Core::Image inImg1, inImg2; 68 | Core::Image inImg3, inImg4, inImg5; 69 | std::vector outImgs; 70 | }; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /framegen/v3.1p_include/v3_1p/shaders/generate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/buffer.hpp" 4 | #include "core/commandbuffer.hpp" 5 | #include "core/descriptorset.hpp" 6 | #include "core/image.hpp" 7 | #include "core/pipeline.hpp" 8 | #include "core/sampler.hpp" 9 | #include "core/shadermodule.hpp" 10 | #include "common/utils.hpp" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace LSFG_3_1P::Shaders { 19 | 20 | using namespace LSFG; 21 | 22 | /// 23 | /// Generate shader. 24 | /// 25 | class Generate { 26 | public: 27 | Generate() = default; 28 | 29 | /// 30 | /// Initialize the shaderchain. 31 | /// 32 | /// @param inImg1 Input image 1. 33 | /// @param inImg2 Input image 2. 34 | /// @param inImg3 Input image 3. 35 | /// @param inImg4 Input image 4. 36 | /// @param inImg5 Input image 5. 37 | /// @param fds File descriptors for the output images. 38 | /// 39 | /// @throws LSFG::vulkan_error if resource creation fails. 40 | /// 41 | Generate(Vulkan& vk, 42 | Core::Image inImg1, Core::Image inImg2, 43 | Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, 44 | const std::vector& fds, VkFormat format); 45 | 46 | /// 47 | /// Dispatch the shaderchain. 48 | /// 49 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); 50 | 51 | /// Trivially copyable, moveable and destructible 52 | Generate(const Generate&) noexcept = default; 53 | Generate& operator=(const Generate&) noexcept = default; 54 | Generate(Generate&&) noexcept = default; 55 | Generate& operator=(Generate&&) noexcept = default; 56 | ~Generate() = default; 57 | private: 58 | Core::ShaderModule shaderModule; 59 | Core::Pipeline pipeline; 60 | std::array samplers; 61 | struct GeneratePass { 62 | Core::Buffer buffer; 63 | std::array descriptorSet; 64 | }; 65 | std::vector passes; 66 | 67 | Core::Image inImg1, inImg2; 68 | Core::Image inImg3, inImg4, inImg5; 69 | std::vector outImgs; 70 | }; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /ui/rsc/pref/number.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 61 | 62 | -------------------------------------------------------------------------------- /ui/rsc/pref/slider.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 61 | 62 | -------------------------------------------------------------------------------- /framegen/include/pool/shaderpool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/device.hpp" 4 | #include "core/pipeline.hpp" 5 | #include "core/shadermodule.hpp" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace LSFG::Pool { 18 | 19 | /// 20 | /// Shader pool for each Vulkan device. 21 | /// 22 | class ShaderPool { 23 | public: 24 | ShaderPool() noexcept = default; 25 | 26 | /// 27 | /// Create the shader pool. 28 | /// 29 | /// @param source Function to retrieve shader source code by name. 30 | /// @param fp16 If true, use the FP16 variant of shaders. 31 | /// 32 | /// @throws std::runtime_error if the shader pool cannot be created. 33 | /// 34 | ShaderPool( 35 | const std::function(const std::string&, bool)>& source, 36 | bool fp16) 37 | : source(source), fp16(fp16) {} 38 | 39 | /// 40 | /// Retrieve a shader module by name or create it. 41 | /// 42 | /// @param name Name of the shader module 43 | /// @param types Descriptor types for the shader module 44 | /// @return Shader module 45 | /// 46 | /// @throws LSFG::vulkan_error if the shader module cannot be created. 47 | /// 48 | Core::ShaderModule getShader( 49 | const Core::Device& device, const std::string& name, 50 | const std::vector>& types); 51 | 52 | /// 53 | /// Retrieve a pipeline shader module by name or create it. 54 | /// 55 | /// @param name Name of the shader module 56 | /// @return Pipeline shader module or empty 57 | /// 58 | /// @throws LSFG::vulkan_error if the shader module cannot be created. 59 | /// 60 | Core::Pipeline getPipeline( 61 | const Core::Device& device, const std::string& name); 62 | private: 63 | std::function(const std::string&, bool)> source; 64 | bool fp16{false}; 65 | 66 | std::unordered_map shaders; 67 | std::unordered_map pipelines; 68 | }; 69 | 70 | } 71 | -------------------------------------------------------------------------------- /framegen/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | if(NOT LSFGVK_EXCESS_DEBUG) 4 | set(CMAKE_C_VISIBILITY_PRESET "hidden") 5 | set(CMAKE_CXX_VISIBILITY_PRESET "hidden") 6 | endif() 7 | 8 | if(LSFGVK_EXCESS_DEBUG) 9 | add_compile_definitions(LSFGVK_EXCESS_DEBUG) 10 | endif() 11 | 12 | project(lsfg-vk-framegen 13 | DESCRIPTION "Lossless Scaling Frame Generation Backend" 14 | LANGUAGES C CXX) 15 | 16 | file(GLOB SOURCES 17 | "src/common/*.cpp" 18 | "src/config/*.cpp" 19 | "src/core/*.cpp" 20 | "src/pool/*.cpp" 21 | "src/*.cpp" 22 | "v3.1_src/core/*.cpp" 23 | "v3.1_src/pool/*.cpp" 24 | "v3.1_src/shaders/*.cpp" 25 | "v3.1_src/utils/*.cpp" 26 | "v3.1_src/*.cpp" 27 | "v3.1p_src/core/*.cpp" 28 | "v3.1p_src/pool/*.cpp" 29 | "v3.1p_src/shaders/*.cpp" 30 | "v3.1p_src/utils/*.cpp" 31 | "v3.1p_src/*.cpp" 32 | "src/thirdparty/*.c" 33 | ) 34 | 35 | add_library(lsfg-vk-framegen STATIC ${SOURCES}) 36 | 37 | # target 38 | set_target_properties(lsfg-vk-framegen PROPERTIES 39 | CXX_STANDARD 20 40 | CXX_STANDARD_REQUIRED ON) 41 | target_include_directories(lsfg-vk-framegen SYSTEM 42 | PUBLIC include/thirdparty) 43 | target_include_directories(lsfg-vk-framegen 44 | PUBLIC include 45 | PUBLIC public 46 | PRIVATE v3.1_include 47 | PRIVATE v3.1p_include) 48 | 49 | # diagnostics 50 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 51 | set_target_properties(lsfg-vk-framegen PROPERTIES 52 | EXPORT_COMPILE_COMMANDS ON) 53 | endif() 54 | 55 | if(LSFGVK_EXCESS_DEBUG) 56 | target_compile_options(lsfg-vk-framegen PRIVATE 57 | -Weverything 58 | # disable compat c++ flags 59 | -Wno-pre-c++20-compat-pedantic 60 | -Wno-pre-c++17-compat 61 | -Wno-c++98-compat-pedantic 62 | -Wno-c++98-compat 63 | # disable other flags 64 | -Wno-missing-designated-field-initializers 65 | -Wno-shadow # allow shadowing 66 | -Wno-switch-enum # ignore missing cases 67 | -Wno-switch-default # ignore missing default 68 | -Wno-padded # ignore automatic padding 69 | -Wno-exit-time-destructors # allow globals 70 | -Wno-global-constructors # allow globals 71 | -Wno-cast-function-type-strict # for vulkan 72 | ) 73 | 74 | set_target_properties(lsfg-vk-framegen PROPERTIES 75 | CXX_CLANG_TIDY clang-tidy) 76 | endif() 77 | -------------------------------------------------------------------------------- /framegen/include/pool/resourcepool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/device.hpp" 4 | #include "core/buffer.hpp" 5 | #include "core/sampler.hpp" 6 | 7 | #include "vulkan/vulkan_core.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace LSFG::Pool { 13 | 14 | /// 15 | /// Resource pool for each Vulkan device. 16 | /// 17 | class ResourcePool { 18 | public: 19 | ResourcePool() noexcept = default; 20 | 21 | /// 22 | /// Create the resource pool. 23 | /// 24 | /// @param isHdr HDR support stored in buffers. 25 | /// @param flowScale Scale factor stored in buffers. 26 | /// 27 | /// @throws std::runtime_error if the resource pool cannot be created. 28 | /// 29 | ResourcePool(bool isHdr, float flowScale) 30 | : isHdr(isHdr), flowScale(flowScale) {} 31 | 32 | /// 33 | /// Retrieve a buffer with given parameters or create it. 34 | /// 35 | /// @param timestamp Timestamp stored in buffer 36 | /// @param firstIter First iteration stored in buffer 37 | /// @param firstIterS First special iteration stored in buffer 38 | /// @return Created or cached buffer 39 | /// 40 | /// @throws LSFG::vulkan_error if the buffer cannot be created. 41 | /// 42 | Core::Buffer getBuffer( 43 | const Core::Device& device, 44 | float timestamp = 0.0F, bool firstIter = false, bool firstIterS = false); 45 | 46 | /// 47 | /// Retrieve a sampler by type or create it. 48 | /// 49 | /// @param type Type of the sampler 50 | /// @param compare Compare operation for the sampler 51 | /// @param isWhite Whether the sampler is white 52 | /// @return Created or cached sampler 53 | /// 54 | /// @throws LSFG::vulkan_error if the sampler cannot be created. 55 | /// 56 | Core::Sampler getSampler( 57 | const Core::Device& device, 58 | VkSamplerAddressMode type = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, 59 | VkCompareOp compare = VK_COMPARE_OP_NEVER, 60 | bool isWhite = false); 61 | 62 | private: 63 | std::unordered_map buffers; 64 | std::unordered_map samplers; 65 | bool isHdr{}; 66 | float flowScale{}; 67 | }; 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/mini/semaphore.cpp: -------------------------------------------------------------------------------- 1 | #include "mini/semaphore.hpp" 2 | #include "common/exception.hpp" 3 | #include "layer.hpp" 4 | 5 | #include 6 | 7 | #include 8 | 9 | using namespace Mini; 10 | 11 | Semaphore::Semaphore(VkDevice device) { 12 | // create semaphore 13 | const VkSemaphoreCreateInfo desc{ 14 | .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO 15 | }; 16 | VkSemaphore semaphoreHandle{}; 17 | auto res = Layer::ovkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle); 18 | if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) 19 | throw LSFG::vulkan_error(res, "Unable to create semaphore"); 20 | 21 | // store semaphore in shared ptr 22 | this->semaphore = std::shared_ptr( 23 | new VkSemaphore(semaphoreHandle), 24 | [dev = device](VkSemaphore* semaphoreHandle) { 25 | Layer::ovkDestroySemaphore(dev, *semaphoreHandle, nullptr); 26 | } 27 | ); 28 | } 29 | 30 | Semaphore::Semaphore(VkDevice device, int* fd) { 31 | // create semaphore 32 | const VkExportSemaphoreCreateInfo exportInfo{ 33 | .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, 34 | .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT 35 | }; 36 | const VkSemaphoreCreateInfo desc{ 37 | .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, 38 | .pNext = &exportInfo 39 | }; 40 | VkSemaphore semaphoreHandle{}; 41 | auto res = Layer::ovkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle); 42 | if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) 43 | throw LSFG::vulkan_error(res, "Unable to create semaphore"); 44 | 45 | // export semaphore to fd 46 | const VkSemaphoreGetFdInfoKHR fdInfo{ 47 | .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, 48 | .semaphore = semaphoreHandle, 49 | .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT 50 | }; 51 | res = Layer::ovkGetSemaphoreFdKHR(device, &fdInfo, fd); 52 | if (res != VK_SUCCESS || *fd < 0) 53 | throw LSFG::vulkan_error(res, "Unable to export semaphore to fd"); 54 | 55 | // store semaphore in shared ptr 56 | this->semaphore = std::shared_ptr( 57 | new VkSemaphore(semaphoreHandle), 58 | [dev = device](VkSemaphore* semaphoreHandle) { 59 | Layer::ovkDestroySemaphore(dev, *semaphoreHandle, nullptr); 60 | } 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /framegen/include/core/buffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/device.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace LSFG::Core { 11 | 12 | /// 13 | /// C++ wrapper class for a Vulkan buffer. 14 | /// 15 | /// This class manages the lifetime of a Vulkan buffer. 16 | /// 17 | class Buffer { 18 | public: 19 | Buffer() noexcept = default; 20 | 21 | /// 22 | /// Create the buffer. 23 | /// 24 | /// @param device Vulkan device 25 | /// @param data Initial data for the buffer, also specifies the size of the buffer. 26 | /// @param usage Usage flags for the buffer 27 | /// 28 | /// @throws LSFG::vulkan_error if object creation fails. 29 | /// 30 | template 31 | Buffer(const Core::Device& device, const T& data, VkBufferUsageFlags usage) 32 | : size(sizeof(T)) { 33 | construct(device, reinterpret_cast(&data), usage); 34 | } 35 | 36 | /// 37 | /// Create the buffer. 38 | /// 39 | /// @param device Vulkan device 40 | /// @param data Initial data for the buffer 41 | /// @param size Size of the buffer in bytes 42 | /// @param usage Usage flags for the buffer 43 | /// 44 | /// @throws LSFG::vulkan_error if object creation fails. 45 | /// 46 | Buffer(const Core::Device& device, const void* data, size_t size, VkBufferUsageFlags usage) 47 | : size(size) { 48 | construct(device, data, usage); 49 | } 50 | 51 | /// Get the Vulkan handle. 52 | [[nodiscard]] auto handle() const { return *this->buffer; } 53 | /// Get the size of the buffer. 54 | [[nodiscard]] size_t getSize() const { return this->size; } 55 | 56 | /// Trivially copyable, moveable and destructible 57 | Buffer(const Buffer&) noexcept = default; 58 | Buffer& operator=(const Buffer&) noexcept = default; 59 | Buffer(Buffer&&) noexcept = default; 60 | Buffer& operator=(Buffer&&) noexcept = default; 61 | ~Buffer() = default; 62 | private: 63 | void construct(const Core::Device& device, const void* data, VkBufferUsageFlags usage); 64 | 65 | std::shared_ptr buffer; 66 | std::shared_ptr memory; 67 | 68 | size_t size{}; 69 | }; 70 | 71 | } 72 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(CMAKE_SKIP_RPATH ON) 4 | 5 | if(NOT LSFGVK_EXCESS_DEBUG) 6 | set(CMAKE_C_VISIBILITY_PRESET "hidden") 7 | set(CMAKE_CXX_VISIBILITY_PRESET "hidden") 8 | endif() 9 | 10 | # subprojects 11 | add_compile_options(-fPIC 12 | -Wno-deprecated-declarations 13 | -Wno-unused-template) 14 | 15 | add_subdirectory(framegen) 16 | 17 | if(LSFGVK_EXCESS_DEBUG) 18 | add_subdirectory(test) 19 | endif() 20 | 21 | # main project 22 | project(lsfg-vk 23 | DESCRIPTION "Lossless Scaling Frame Generation on Linux" 24 | LANGUAGES CXX) 25 | 26 | file(GLOB SOURCES 27 | "src/config/*.cpp" 28 | "src/extract/*.cpp" 29 | "src/mini/*.cpp" 30 | "src/utils/*.cpp" 31 | "src/*.cpp" 32 | ) 33 | 34 | add_library(lsfg-vk SHARED ${SOURCES}) 35 | 36 | # target 37 | set_target_properties(lsfg-vk PROPERTIES 38 | CXX_STANDARD 20 39 | CXX_STANDARD_REQUIRED ON) 40 | target_include_directories(lsfg-vk SYSTEM 41 | PUBLIC include/thirdparty) 42 | target_include_directories(lsfg-vk 43 | PUBLIC include) 44 | target_link_libraries(lsfg-vk PUBLIC 45 | lsfg-vk-framegen) 46 | 47 | # diagnostics 48 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 49 | set_target_properties(lsfg-vk PROPERTIES 50 | EXPORT_COMPILE_COMMANDS ON) 51 | endif() 52 | 53 | if(LSFGVK_EXCESS_DEBUG) 54 | message(STATUS "LSFGVK_EXCESS_DEBUG is only compatible with clang") 55 | target_compile_options(lsfg-vk PRIVATE 56 | -Weverything 57 | # disable compat c++ flags 58 | -Wno-pre-c++20-compat-pedantic 59 | -Wno-pre-c++17-compat 60 | -Wno-c++98-compat-pedantic 61 | -Wno-c++98-compat 62 | # disable other flags 63 | -Wno-missing-designated-field-initializers 64 | -Wno-shadow # allow shadowing 65 | -Wno-switch-enum # ignore missing cases 66 | -Wno-switch-default # ignore missing default 67 | -Wno-padded # ignore automatic padding 68 | -Wno-exit-time-destructors # allow globals 69 | -Wno-global-constructors # allow globals 70 | -Wno-cast-function-type-strict # for vulkan 71 | ) 72 | 73 | set_target_properties(lsfg-vk PROPERTIES 74 | CXX_CLANG_TIDY clang-tidy) 75 | endif() 76 | 77 | # install 78 | install(FILES "${CMAKE_BINARY_DIR}/liblsfg-vk.so" 79 | DESTINATION lib) 80 | install(FILES "${CMAKE_SOURCE_DIR}/VkLayer_LS_frame_generation.json" 81 | DESTINATION share/vulkan/implicit_layer.d) 82 | -------------------------------------------------------------------------------- /ui/rsc/pref/entry.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 61 | 62 | -------------------------------------------------------------------------------- /ui/src/wrapper.rs: -------------------------------------------------------------------------------- 1 | use gtk::glib; 2 | use gtk; 3 | use adw; 4 | use gtk::glib::types::StaticTypeExt; 5 | 6 | pub mod entry; 7 | pub mod pane; 8 | pub mod pref; 9 | pub mod popup; 10 | 11 | pub mod imp { 12 | use gtk::subclass::prelude::*; 13 | use adw::subclass::prelude::*; 14 | use crate::wrapper::pane::*; 15 | use gtk::{glib, CompositeTemplate}; 16 | 17 | #[derive(CompositeTemplate, Default)] 18 | #[template(resource = "/gay/pancake/lsfg-vk/window.ui")] 19 | pub struct Window { 20 | #[template_child] 21 | pub main: TemplateChild, 22 | #[template_child] 23 | pub sidebar: TemplateChild, 24 | } 25 | 26 | #[glib::object_subclass] 27 | impl ObjectSubclass for Window { 28 | const NAME: &'static str = "LSApplicationWindow"; 29 | type Type = super::Window; 30 | type ParentType = adw::ApplicationWindow; 31 | 32 | fn class_init(klass: &mut Self::Class) { 33 | klass.bind_template(); 34 | } 35 | 36 | fn instance_init(obj: &glib::subclass::InitializingObject) { 37 | obj.init_template(); 38 | } 39 | } 40 | 41 | impl ObjectImpl for Window { 42 | fn constructed(&self) { 43 | self.parent_constructed(); 44 | } 45 | } 46 | 47 | impl WidgetImpl for Window {} 48 | impl WindowImpl for Window {} 49 | impl ApplicationWindowImpl for Window {} 50 | impl AdwWindowImpl for Window {} 51 | impl AdwApplicationWindowImpl for Window {} 52 | } 53 | 54 | glib::wrapper! { 55 | pub struct Window(ObjectSubclass) 56 | @extends 57 | adw::ApplicationWindow, adw::Window, 58 | gtk::ApplicationWindow, gtk::Window, gtk::Widget, 59 | @implements 60 | gtk::gio::ActionGroup, gtk::gio::ActionMap, 61 | gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, 62 | gtk::Native, gtk::Root, gtk::ShortcutManager; 63 | } 64 | 65 | impl Window { 66 | pub fn new(app: &adw::Application) -> Self { 67 | pref::PrefDropdown::ensure_type(); 68 | pref::PrefEntry::ensure_type(); 69 | pref::PrefNumber::ensure_type(); 70 | pref::PrefSlider::ensure_type(); 71 | pref::PrefSwitch::ensure_type(); 72 | pane::PaneMain::ensure_type(); 73 | pane::PaneSidebar::ensure_type(); 74 | 75 | glib::Object::builder() 76 | .property("application", app) 77 | .build() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /framegen/src/core/pipeline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "core/pipeline.hpp" 5 | #include "core/device.hpp" 6 | #include "core/shadermodule.hpp" 7 | #include "core/commandbuffer.hpp" 8 | #include "common/exception.hpp" 9 | 10 | #include 11 | 12 | using namespace LSFG::Core; 13 | 14 | Pipeline::Pipeline(const Core::Device& device, const ShaderModule& shader) { 15 | // create pipeline layout 16 | VkDescriptorSetLayout shaderLayout = shader.getLayout(); 17 | const VkPipelineLayoutCreateInfo layoutDesc{ 18 | .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 19 | .setLayoutCount = 1, 20 | .pSetLayouts = &shaderLayout, 21 | }; 22 | VkPipelineLayout layoutHandle{}; 23 | auto res = vkCreatePipelineLayout(device.handle(), &layoutDesc, nullptr, &layoutHandle); 24 | if (res != VK_SUCCESS || !layoutHandle) 25 | throw LSFG::vulkan_error(res, "Failed to create pipeline layout"); 26 | 27 | // create pipeline 28 | const VkPipelineShaderStageCreateInfo shaderStageInfo{ 29 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 30 | .stage = VK_SHADER_STAGE_COMPUTE_BIT, 31 | .module = shader.handle(), 32 | .pName = "main", 33 | }; 34 | const VkComputePipelineCreateInfo pipelineDesc{ 35 | .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, 36 | .stage = shaderStageInfo, 37 | .layout = layoutHandle, 38 | }; 39 | VkPipeline pipelineHandle{}; 40 | res = vkCreateComputePipelines(device.handle(), 41 | VK_NULL_HANDLE, 1, &pipelineDesc, nullptr, &pipelineHandle); 42 | if (res != VK_SUCCESS || !pipelineHandle) 43 | throw LSFG::vulkan_error(res, "Failed to create compute pipeline"); 44 | 45 | // store layout and pipeline in shared ptr 46 | this->layout = std::shared_ptr( 47 | new VkPipelineLayout(layoutHandle), 48 | [dev = device.handle()](VkPipelineLayout* layout) { 49 | vkDestroyPipelineLayout(dev, *layout, nullptr); 50 | } 51 | ); 52 | this->pipeline = std::shared_ptr( 53 | new VkPipeline(pipelineHandle), 54 | [dev = device.handle()](VkPipeline* pipeline) { 55 | vkDestroyPipeline(dev, *pipeline, nullptr); 56 | } 57 | ); 58 | } 59 | 60 | void Pipeline::bind(const CommandBuffer& commandBuffer) const { 61 | vkCmdBindPipeline(commandBuffer.handle(), VK_PIPELINE_BIND_POINT_COMPUTE, *this->pipeline); 62 | } 63 | -------------------------------------------------------------------------------- /framegen/v3.1_include/v3_1/shaders/gamma.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/buffer.hpp" 4 | #include "core/commandbuffer.hpp" 5 | #include "core/descriptorset.hpp" 6 | #include "core/image.hpp" 7 | #include "core/pipeline.hpp" 8 | #include "core/sampler.hpp" 9 | #include "core/shadermodule.hpp" 10 | #include "common/utils.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace LSFG_3_1::Shaders { 18 | 19 | using namespace LSFG; 20 | 21 | /// 22 | /// Gamma shader. 23 | /// 24 | class Gamma { 25 | public: 26 | Gamma() = default; 27 | 28 | /// 29 | /// Initialize the shaderchain. 30 | /// 31 | /// @param inImgs1 Three sets of four RGBA images, corresponding to a frame count % 3. 32 | /// @param inImg2 Second Input image 33 | /// @param optImg Optional image for non-first passes. 34 | /// 35 | /// @throws LSFG::vulkan_error if resource creation fails. 36 | /// 37 | Gamma(Vulkan& vk, std::array, 3> inImgs1, 38 | Core::Image inImg2, std::optional optImg); 39 | 40 | /// 41 | /// Dispatch the shaderchain. 42 | /// 43 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); 44 | 45 | /// Get the output image 46 | [[nodiscard]] const auto& getOutImage() const { return this->outImg; } 47 | 48 | /// Trivially copyable, moveable and destructible 49 | Gamma(const Gamma&) noexcept = default; 50 | Gamma& operator=(const Gamma&) noexcept = default; 51 | Gamma(Gamma&&) noexcept = default; 52 | Gamma& operator=(Gamma&&) noexcept = default; 53 | ~Gamma() = default; 54 | private: 55 | std::array shaderModules; 56 | std::array pipelines; 57 | std::array samplers; 58 | struct GammaPass { 59 | Core::Buffer buffer; 60 | std::array firstDescriptorSet; 61 | std::array descriptorSets; 62 | }; 63 | std::vector passes; 64 | 65 | std::array, 3> inImgs1; 66 | Core::Image inImg2; 67 | std::optional optImg; 68 | std::array tempImgs1; 69 | std::array tempImgs2; 70 | Core::Image outImg; 71 | }; 72 | 73 | } 74 | -------------------------------------------------------------------------------- /framegen/v3.1p_include/v3_1p/shaders/gamma.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/buffer.hpp" 4 | #include "core/commandbuffer.hpp" 5 | #include "core/descriptorset.hpp" 6 | #include "core/image.hpp" 7 | #include "core/pipeline.hpp" 8 | #include "core/sampler.hpp" 9 | #include "core/shadermodule.hpp" 10 | #include "common/utils.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace LSFG_3_1P::Shaders { 18 | 19 | using namespace LSFG; 20 | 21 | /// 22 | /// Gamma shader. 23 | /// 24 | class Gamma { 25 | public: 26 | Gamma() = default; 27 | 28 | /// 29 | /// Initialize the shaderchain. 30 | /// 31 | /// @param inImgs1 Three sets of two RGBA images, corresponding to a frame count % 3. 32 | /// @param inImg2 Second Input image 33 | /// @param optImg Optional image for non-first passes. 34 | /// 35 | /// @throws LSFG::vulkan_error if resource creation fails. 36 | /// 37 | Gamma(Vulkan& vk, std::array, 3> inImgs1, 38 | Core::Image inImg2, std::optional optImg); 39 | 40 | /// 41 | /// Dispatch the shaderchain. 42 | /// 43 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); 44 | 45 | /// Get the output image 46 | [[nodiscard]] const auto& getOutImage() const { return this->outImg; } 47 | 48 | /// Trivially copyable, moveable and destructible 49 | Gamma(const Gamma&) noexcept = default; 50 | Gamma& operator=(const Gamma&) noexcept = default; 51 | Gamma(Gamma&&) noexcept = default; 52 | Gamma& operator=(Gamma&&) noexcept = default; 53 | ~Gamma() = default; 54 | private: 55 | std::array shaderModules; 56 | std::array pipelines; 57 | std::array samplers; 58 | struct GammaPass { 59 | Core::Buffer buffer; 60 | std::array firstDescriptorSet; 61 | std::array descriptorSets; 62 | }; 63 | std::vector passes; 64 | 65 | std::array, 3> inImgs1; 66 | Core::Image inImg2; 67 | std::optional optImg; 68 | std::array tempImgs1; 69 | std::array tempImgs2; 70 | Core::Image outImg; 71 | }; 72 | 73 | } 74 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build lsfg-vk 2 | 3 | on: 4 | push: 5 | branches: ["release"] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | # prepare system 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | - name: Install build dependencies 15 | uses: awalsh128/cache-apt-pkgs-action@latest 16 | with: 17 | packages: git wget xvfb 18 | clang clang-tools llvm rustup 19 | cmake ninja-build pkg-config 20 | libdecor-0-0 libvulkan-dev 21 | libgtk-4-dev libadwaita-1-dev 22 | version: 1.0 23 | execute_install_scripts: true 24 | - name: Install rust dependency 25 | run: | 26 | rustup default stable 27 | # build the project 28 | - name: Configure with CMake and Ninja 29 | run: | 30 | cmake -B build -G Ninja \ 31 | -DCMAKE_BUILD_TYPE=Release \ 32 | -DCMAKE_INSTALL_PREFIX=./target \ 33 | -DCMAKE_C_COMPILER=clang \ 34 | -DCMAKE_CXX_COMPILER=clang++ \ 35 | -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On 36 | - name: Build with Ninja 37 | run: | 38 | ninja -C build 39 | - name: Install with CMake 40 | run: | 41 | cmake --install build --strip 42 | - name: Build lsfg-vk-ui 43 | run: | 44 | pushd ui 45 | chmod +x ./build_appimage.sh 46 | ./build_appimage.sh 47 | popd 48 | - name: Install lsfg-vk-ui 49 | run: | 50 | mkdir -p target/{bin,share/applications,share/icons/hicolor/256x256/apps} 51 | mv ui/lsfg-vk-ui.AppImage target/bin/lsfg-vk-ui 52 | cp ui/rsc/gay.pancake.lsfg-vk-ui.desktop target/share/applications/lsfg-vk-ui.desktop 53 | cp ui/rsc/icon.png target/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png 54 | # upload all files 55 | - name: Upload lsfg-vk artifact 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: lsfg-vk 59 | path: | 60 | target/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json 61 | target/share/applications/lsfg-vk-ui.desktop 62 | target/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png 63 | target/lib/liblsfg-vk.so 64 | target/bin/lsfg-vk-ui 65 | - name: Upload lsfg-vk artifact (without UI) 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: lsfg-vk_noui 69 | path: | 70 | target/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json 71 | target/lib/liblsfg-vk.so 72 | -------------------------------------------------------------------------------- /.github/workflows/build_ci.yml: -------------------------------------------------------------------------------- 1 | name: (CI) Build lsfg-vk 2 | 3 | on: 4 | push: 5 | branches: ["develop"] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | # prepare system 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | - name: Install build dependencies 15 | uses: awalsh128/cache-apt-pkgs-action@latest 16 | with: 17 | packages: git wget xvfb 18 | clang clang-tools llvm rustup 19 | cmake ninja-build pkg-config 20 | libdecor-0-0 libvulkan-dev 21 | libgtk-4-dev libadwaita-1-dev 22 | version: 1.0 23 | execute_install_scripts: true 24 | - name: Install rust dependency 25 | run: | 26 | rustup default stable 27 | # build the project 28 | - name: Configure with CMake and Ninja 29 | run: | 30 | cmake -B build -G Ninja \ 31 | -DCMAKE_BUILD_TYPE=Release \ 32 | -DCMAKE_INSTALL_PREFIX=./target \ 33 | -DCMAKE_C_COMPILER=clang \ 34 | -DCMAKE_CXX_COMPILER=clang++ \ 35 | -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On 36 | - name: Build with Ninja 37 | run: | 38 | ninja -C build 39 | - name: Install with CMake 40 | run: | 41 | cmake --install build --strip 42 | - name: Build lsfg-vk-ui 43 | run: | 44 | pushd ui 45 | chmod +x ./build_appimage.sh 46 | ./build_appimage.sh 47 | popd 48 | - name: Install lsfg-vk-ui 49 | run: | 50 | mkdir -p target/{bin,share/applications,share/icons/hicolor/256x256/apps} 51 | mv ui/lsfg-vk-ui.AppImage target/bin/lsfg-vk-ui 52 | cp ui/rsc/gay.pancake.lsfg-vk-ui.desktop target/share/applications/lsfg-vk-ui.desktop 53 | cp ui/rsc/icon.png target/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png 54 | # upload all files 55 | - name: Upload lsfg-vk artifact 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: lsfg-vk 59 | path: | 60 | target/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json 61 | target/share/applications/lsfg-vk-ui.desktop 62 | target/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png 63 | target/lib/liblsfg-vk.so 64 | target/bin/lsfg-vk-ui 65 | - name: Upload lsfg-vk artifact (without UI) 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: lsfg-vk_noui 69 | path: | 70 | target/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json 71 | target/lib/liblsfg-vk.so 72 | -------------------------------------------------------------------------------- /ui/src/config/structs.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | // multiplier 4 | #[derive(Debug, Clone, Deserialize, Serialize)] 5 | pub struct Multiplier(i64); 6 | impl Default for Multiplier { 7 | fn default() -> Self { Multiplier(2) } 8 | } 9 | impl From for Multiplier { 10 | fn from(value: i64) -> Self { Multiplier(value) } 11 | } 12 | impl Into for Multiplier { 13 | fn into(self) -> f64 { self.0 as f64 } 14 | } 15 | 16 | // flow scale 17 | #[derive(Debug, Clone, Deserialize, Serialize)] 18 | pub struct FlowScale(f64); 19 | impl Default for FlowScale { 20 | fn default() -> Self { FlowScale(1.0) } 21 | } 22 | impl From for FlowScale { 23 | fn from(value: f64) -> Self { FlowScale(value) } 24 | } 25 | impl Into for FlowScale { 26 | fn into(self) -> f64 { self.0 } 27 | } 28 | 29 | // present mode 30 | #[derive(Debug, Clone, Deserialize, Serialize)] 31 | pub enum PresentMode { 32 | #[serde(rename = "fifo", alias = "vsync")] 33 | Vsync, 34 | #[serde(rename = "immediate")] 35 | Immediate, 36 | #[serde(rename = "mailbox")] 37 | Mailbox, 38 | } 39 | impl Default for PresentMode { 40 | fn default() -> Self { PresentMode::Vsync } 41 | } 42 | impl From for PresentMode { 43 | fn from(value: i64) -> Self { 44 | match value { 45 | 0 => PresentMode::Vsync, 46 | 1 => PresentMode::Mailbox, 47 | 2 => PresentMode::Immediate, 48 | _ => PresentMode::Vsync, 49 | } 50 | } 51 | } 52 | impl Into for PresentMode { 53 | fn into(self) -> u32 { 54 | match self { 55 | PresentMode::Vsync => 0, 56 | PresentMode::Mailbox => 1, 57 | PresentMode::Immediate => 2, 58 | } 59 | } 60 | } 61 | 62 | /// Global configuration for the application 63 | #[derive(Debug, Default, Clone, Deserialize, Serialize)] 64 | pub struct TomlGlobal { 65 | pub dll: Option, 66 | #[serde(default)] 67 | pub no_fp16: bool 68 | } 69 | 70 | /// Game-specific configuration 71 | #[derive(Debug, Default, Clone, Deserialize, Serialize)] 72 | pub struct TomlGame { 73 | pub exe: String, 74 | 75 | #[serde(default)] 76 | pub multiplier: Multiplier, 77 | #[serde(default)] 78 | pub flow_scale: FlowScale, 79 | #[serde(default)] 80 | pub performance_mode: bool, 81 | #[serde(default)] 82 | pub hdr_mode: bool, 83 | #[serde(default)] 84 | pub experimental_present_mode: PresentMode 85 | } 86 | 87 | /// Main configuration structure 88 | #[derive(Debug, Default, Clone, Deserialize, Serialize)] 89 | pub struct TomlConfig { 90 | pub version: i64, 91 | #[serde(default)] 92 | pub global: TomlGlobal, 93 | #[serde(default)] 94 | pub game: Vec 95 | } 96 | -------------------------------------------------------------------------------- /framegen/v3.1_src/shaders/mipmaps.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "v3_1/shaders/mipmaps.hpp" 5 | #include "common/utils.hpp" 6 | #include "core/image.hpp" 7 | #include "core/commandbuffer.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace LSFG_3_1::Shaders; 14 | 15 | Mipmaps::Mipmaps(Vulkan& vk, 16 | Core::Image inImg_0, Core::Image inImg_1) 17 | : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) { 18 | // create resources 19 | this->shaderModule = vk.shaders.getShader(vk.device, "mipmaps", 20 | { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, 21 | { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, 22 | { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, 23 | { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); 24 | this->pipeline = vk.shaders.getPipeline(vk.device, "mipmaps"); 25 | this->buffer = vk.resources.getBuffer(vk.device); 26 | this->sampler = vk.resources.getSampler(vk.device); 27 | for (size_t i = 0; i < 2; i++) 28 | this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModule); 29 | 30 | // create outputs 31 | const VkExtent2D flowExtent{ 32 | .width = static_cast( 33 | static_cast(this->inImg_0.getExtent().width) / vk.flowScale), 34 | .height = static_cast( 35 | static_cast(this->inImg_0.getExtent().height) / vk.flowScale) 36 | }; 37 | for (size_t i = 0; i < 7; i++) 38 | this->outImgs.at(i) = Core::Image(vk.device, 39 | { flowExtent.width >> i, flowExtent.height >> i }, 40 | VK_FORMAT_R8_UNORM); 41 | 42 | // hook up shaders 43 | for (size_t fc = 0; fc < 2; fc++) 44 | this->descriptorSets.at(fc).update(vk.device) 45 | .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) 46 | .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) 47 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg_0 : this->inImg_1) 48 | .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) 49 | .build(); 50 | } 51 | 52 | void Mipmaps::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { 53 | // first pass 54 | const auto flowExtent = this->outImgs.at(0).getExtent(); 55 | const uint32_t threadsX = (flowExtent.width + 63) >> 6; 56 | const uint32_t threadsY = (flowExtent.height + 63) >> 6; 57 | 58 | Utils::BarrierBuilder(buf) 59 | .addW2R((frameCount % 2 == 0) ? this->inImg_0 : this->inImg_1) 60 | .addR2W(this->outImgs) 61 | .build(); 62 | 63 | this->pipeline.bind(buf); 64 | this->descriptorSets.at(frameCount % 2).bind(buf, this->pipeline); 65 | buf.dispatch(threadsX, threadsY, 1); 66 | } 67 | -------------------------------------------------------------------------------- /framegen/v3.1p_src/shaders/mipmaps.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "v3_1p/shaders/mipmaps.hpp" 5 | #include "common/utils.hpp" 6 | #include "core/image.hpp" 7 | #include "core/commandbuffer.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace LSFG_3_1P::Shaders; 14 | 15 | Mipmaps::Mipmaps(Vulkan& vk, 16 | Core::Image inImg_0, Core::Image inImg_1) 17 | : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) { 18 | // create resources 19 | this->shaderModule = vk.shaders.getShader(vk.device, "mipmaps", 20 | { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, 21 | { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, 22 | { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, 23 | { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); 24 | this->pipeline = vk.shaders.getPipeline(vk.device, "mipmaps"); 25 | this->buffer = vk.resources.getBuffer(vk.device); 26 | this->sampler = vk.resources.getSampler(vk.device); 27 | for (size_t i = 0; i < 2; i++) 28 | this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModule); 29 | 30 | // create outputs 31 | const VkExtent2D flowExtent{ 32 | .width = static_cast( 33 | static_cast(this->inImg_0.getExtent().width) / vk.flowScale), 34 | .height = static_cast( 35 | static_cast(this->inImg_0.getExtent().height) / vk.flowScale) 36 | }; 37 | for (size_t i = 0; i < 7; i++) 38 | this->outImgs.at(i) = Core::Image(vk.device, 39 | { flowExtent.width >> i, flowExtent.height >> i }, 40 | VK_FORMAT_R8_UNORM); 41 | 42 | // hook up shaders 43 | for (size_t fc = 0; fc < 2; fc++) 44 | this->descriptorSets.at(fc).update(vk.device) 45 | .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) 46 | .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) 47 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg_0 : this->inImg_1) 48 | .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) 49 | .build(); 50 | } 51 | 52 | void Mipmaps::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { 53 | // first pass 54 | const auto flowExtent = this->outImgs.at(0).getExtent(); 55 | const uint32_t threadsX = (flowExtent.width + 63) >> 6; 56 | const uint32_t threadsY = (flowExtent.height + 63) >> 6; 57 | 58 | Utils::BarrierBuilder(buf) 59 | .addW2R((frameCount % 2 == 0) ? this->inImg_0 : this->inImg_1) 60 | .addR2W(this->outImgs) 61 | .build(); 62 | 63 | this->pipeline.bind(buf); 64 | this->descriptorSets.at(frameCount % 2).bind(buf, this->pipeline); 65 | buf.dispatch(threadsX, threadsY, 1); 66 | } 67 | -------------------------------------------------------------------------------- /ui/src/ui/sidebar_handler.rs: -------------------------------------------------------------------------------- 1 | use adw::subclass::prelude::ObjectSubclassIsExt; 2 | use gtk::prelude::{ButtonExt, EditableExt, ListBoxRowExt, RangeExt, WidgetExt}; 3 | 4 | use crate::{config, ui::entry_handler, wrapper::{pane, entry}, STATE}; 5 | 6 | /// 7 | /// Register signals for adding and selecting presets. 8 | /// 9 | pub fn register_signals(sidebar_: &pane::PaneSidebar, main: pane::PaneMain) { 10 | // activate signal 11 | let state = STATE.get().unwrap().clone(); 12 | sidebar_.imp().profiles.connect_row_activated(move |_, entry| { 13 | // find config entry by index 14 | let index = entry.index() as usize; 15 | let config = config::get_config(); 16 | if config.is_err() { 17 | return; 18 | } 19 | let config = config.unwrap(); 20 | let conf = config.game[index].clone(); 21 | 22 | // update main pane 23 | let main = main.imp(); 24 | let exe = main.profile_name.imp(); 25 | let multiplier = main.multiplier.imp(); 26 | let flow_scale = main.flow_scale.imp(); 27 | let performance_mode = main.performance_mode.imp(); 28 | let hdr_mode = main.hdr_mode.imp(); 29 | let experimental_present_mode = main.experimental_present_mode.imp(); 30 | 31 | // (lock state early, so the ui update doesn't override the config) 32 | if let Ok(mut state) = state.write() { 33 | exe.entry.set_text(&conf.exe); 34 | multiplier.number.set_value(conf.multiplier.into()); 35 | flow_scale.slider.set_value(Into::::into(conf.flow_scale) * 100.0); 36 | performance_mode.switch.set_active(conf.performance_mode); 37 | hdr_mode.switch.set_active(conf.hdr_mode); 38 | experimental_present_mode.dropdown.set_selected(conf.experimental_present_mode.into()); 39 | 40 | // update state 41 | state.selected_game = Some(index); 42 | } 43 | }); 44 | 45 | // create signal 46 | let sidebar = sidebar_.clone(); 47 | sidebar_.imp().create.connect_clicked(move |_| { 48 | // ensure no config entry with the same name exist 49 | let config = config::get_config().unwrap(); 50 | if config.game.iter().any(|e| e.exe == "new profile") { 51 | return; 52 | } 53 | 54 | // create config entry 55 | let mut conf_entry = config::TomlGame::default(); 56 | conf_entry.exe = "new profile".to_string(); 57 | let _ = config::edit_config(|config| { 58 | config.game.push(conf_entry.clone()); 59 | }); 60 | 61 | // add entry to sidebar 62 | let entry = entry::Entry::new(); 63 | entry.set_exe(conf_entry.exe); 64 | entry_handler::add_entry(entry.clone(), sidebar.imp().profiles.clone()); 65 | 66 | // select the new entry 67 | entry.activate(); 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lsfg-vk 2 | 3 | Lossless Scaling is a Windows-exclusive app bringing frame generation (among other features) to every single game or app. 4 | 5 | **lsfg-vk** brings this frame generation to Linux users by acting as a Vulkan layer inbetween your game and your graphics card. 6 | 7 | ## Getting Started 8 | 9 | For comprehensive instructions on setting up, configuring, and using **lsfg-vk**, please refer to the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki). 10 | 11 | ### Installation 12 | 13 | **lsfg-vk** is available as pre-built packages for various Linux distributions. 14 | 15 | 1. Visit the [Releases page](https://github.com/PancakeTAS/lsfg-vk/releases) to download the package for your Linux distribution. 16 | 2. Follow the step-by-step instructions in the [Installation Guide](https://github.com/PancakeTAS/lsfg-vk/wiki/Installation-Guide) for your specific distro. 17 | 18 | ### Configuration and Usage 19 | 20 | After installation, you can open the graphical configuration editor **lsfg-vk-ui** from your application menu or by typing `lsfg-vk-ui` in your console. 21 | 22 | * For detailed instructions on setting up your preferences, visit the [Configuring lsfg-vk](https://github.com/PancakeTAS/lsfg-vk/wiki/Configuring-lsfg%E2%80%90vk) page. 23 | * Learn how to use **lsfg-vk**'s integrated benchmark on the [Using lsfg-vk's integrated benchmark](https://github.com/PancakeTAS/lsfg-vk/wiki/Using-lsfg%E2%80%90vk's-integrated-benchmark) page. 24 | 25 | ## Building from Source 26 | 27 | If you prefer to build **lsfg-vk** yourself for development, debugging, or to use the latest and greatest features, a detailed build guide is available in the [Build Guide](https://github.com/PancakeTAS/lsfg-vk/wiki/Building-from-source) page. 28 | 29 | ## Support and Troubleshooting 30 | 31 | If you encounter any issues or need assistance, please consult the following resources: 32 | 33 | * **Quirks:** Before reporting any issues refer to the [Quirks](https://github.com/PancakeTAS/lsfg-vk/wiki/Quirks) page. 34 | * **How to Ask for Help:** When creating a report, refer to the [How to ask for help](https://github.com/PancakeTAS/lsfg-vk/wiki/How-to-ask-for-help) page. 35 | * **Discord:** Join the [Lossless Scaling Discord server](https://discord.gg/losslessscaling) for help (Steam verification required). 36 | 37 | ## Credits 38 | 39 | Most of the project has still only been written by me, PancakeTAS, but I couldn't have done it without the help of these people: 40 | 41 | * **0xNULLderef:** Teaching me how to reverse engineer software. 42 | * **Caliel666:** Writing the initial draft of the user interface. 43 | * **Samueru-sama:** Helping with various things XDG as well as app images and testing. 44 | * Other contributors: Thank you for your contributions! 45 | 46 | I'd also like to thank every single person sponsoring this project. Thanks to you I'll be able to invest more time into this and hopefully bring some cool new features to everyone. 47 | -------------------------------------------------------------------------------- /framegen/include/core/semaphore.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/device.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace LSFG::Core { 12 | 13 | /// 14 | /// C++ wrapper class for a Vulkan semaphore. 15 | /// 16 | /// This class manages the lifetime of a Vulkan semaphore. 17 | /// 18 | class Semaphore { 19 | public: 20 | Semaphore() noexcept = default; 21 | 22 | /// 23 | /// Create the semaphore. 24 | /// 25 | /// @param device Vulkan device 26 | /// @param initial Optional initial value for creating a timeline semaphore. 27 | /// 28 | /// @throws LSFG::vulkan_error if object creation fails. 29 | /// 30 | Semaphore(const Core::Device& device, std::optional initial = std::nullopt); 31 | 32 | /// 33 | /// Import a semaphore. 34 | /// 35 | /// @param device Vulkan device 36 | /// @param fd File descriptor to import the semaphore from. 37 | /// 38 | /// @throws LSFG::vulkan_error if object creation fails. 39 | /// 40 | Semaphore(const Core::Device& device, int fd); 41 | 42 | /// 43 | /// Signal the semaphore to a specific value. 44 | /// 45 | /// @param device Vulkan device 46 | /// @param value The value to signal the semaphore to. 47 | /// 48 | /// @throws std::logic_error if the semaphore is not a timeline semaphore. 49 | /// @throws LSFG::vulkan_error if signaling fails. 50 | /// 51 | void signal(const Core::Device& device, uint64_t value) const; 52 | 53 | /// 54 | /// Wait for the semaphore to reach a specific value. 55 | /// 56 | /// @param device Vulkan device 57 | /// @param value The value to wait for. 58 | /// @param timeout The timeout in nanoseconds, or UINT64_MAX for no timeout. 59 | /// @returns true if the semaphore reached the value, false if it timed out. 60 | /// 61 | /// @throws std::logic_error if the semaphore is not a timeline semaphore. 62 | /// @throws LSFG::vulkan_error if waiting fails. 63 | /// 64 | [[nodiscard]] bool wait(const Core::Device& device, uint64_t value, uint64_t timeout = UINT64_MAX) const; 65 | 66 | /// Get the Vulkan handle. 67 | [[nodiscard]] auto handle() const { return *this->semaphore; } 68 | 69 | // Trivially copyable, moveable and destructible 70 | Semaphore(const Semaphore&) noexcept = default; 71 | Semaphore& operator=(const Semaphore&) noexcept = default; 72 | Semaphore(Semaphore&&) noexcept = default; 73 | Semaphore& operator=(Semaphore&&) noexcept = default; 74 | ~Semaphore() = default; 75 | private: 76 | std::shared_ptr semaphore; 77 | bool isTimeline{}; 78 | }; 79 | 80 | } 81 | -------------------------------------------------------------------------------- /framegen/public/lsfg_3_1.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace LSFG_3_1 { 11 | 12 | /// 13 | /// Initialize the LSFG library. 14 | /// 15 | /// @param deviceUUID The UUID of the Vulkan device to use. 16 | /// @param isHdr Whether the images are in HDR format. 17 | /// @param flowScale Internal flow scale factor. 18 | /// @param generationCount Number of frames to generate. 19 | /// @param forceDisableFp16 Whether to force-disable FP16 optimizations. 20 | /// @param loader Function to load shader source code by name. 21 | /// 22 | /// @throws LSFG::vulkan_error if Vulkan objects fail to initialize. 23 | /// 24 | [[gnu::visibility("default")]] 25 | void initialize(uint64_t deviceUUID, 26 | bool isHdr, float flowScale, uint64_t generationCount, 27 | bool forceDisableFp16, 28 | const std::function(const std::string&, bool)>& loader); 29 | 30 | #ifdef LSFGVK_EXCESS_DEBUG 31 | /// 32 | /// Initialize the renderdoc API. 33 | /// 34 | /// @throws LSFG::vulkan_error if the renderdoc API cannot be initialized. 35 | /// 36 | [[gnu::visibility("default")]] 37 | void initializeRenderDoc(); 38 | #endif // LSFGVK_EXCESS_DEBUG 39 | 40 | /// 41 | /// Create a new LSFG context on a swapchain. 42 | /// 43 | /// @param in0 File descriptor for the first input image. 44 | /// @param in1 File descriptor for the second input image. 45 | /// @param outN File descriptor for each output image. This defines the LSFG level. 46 | /// @param extent The size of the images 47 | /// @param format The format of the images. 48 | /// @return A unique identifier for the created context. 49 | /// 50 | /// @throws LSFG::vulkan_error if the context cannot be created. 51 | /// 52 | [[gnu::visibility("default")]] 53 | int32_t createContext( 54 | int in0, int in1, const std::vector& outN, 55 | VkExtent2D extent, VkFormat format); 56 | 57 | /// 58 | /// Present a context. 59 | /// 60 | /// @param id Unique identifier of the context to present. 61 | /// @param inSem Semaphore to wait on before starting the generation. 62 | /// @param outSem Semaphores to signal once each output image is ready. 63 | /// 64 | /// @throws LSFG::vulkan_error if the context cannot be presented. 65 | /// 66 | [[gnu::visibility("default")]] 67 | void presentContext(int32_t id, int inSem, const std::vector& outSem); 68 | 69 | /// 70 | /// Delete an LSFG context. 71 | /// 72 | /// @param id Unique identifier of the context to delete. 73 | /// 74 | [[gnu::visibility("default")]] 75 | void deleteContext(int32_t id); 76 | 77 | /// 78 | /// Deinitialize the LSFG library. 79 | /// 80 | [[gnu::visibility("default")]] 81 | void finalize(); 82 | 83 | } 84 | -------------------------------------------------------------------------------- /framegen/public/lsfg_3_1p.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace LSFG_3_1P { 11 | 12 | /// 13 | /// Initialize the LSFG library. 14 | /// 15 | /// @param deviceUUID The UUID of the Vulkan device to use. 16 | /// @param isHdr Whether the images are in HDR format. 17 | /// @param flowScale Internal flow scale factor. 18 | /// @param generationCount Number of frames to generate. 19 | /// @param forceDisableFp16 Whether to force-disable FP16 optimizations. 20 | /// @param loader Function to load shader source code by name. 21 | /// 22 | /// @throws LSFG::vulkan_error if Vulkan objects fail to initialize. 23 | /// 24 | [[gnu::visibility("default")]] 25 | void initialize(uint64_t deviceUUID, 26 | bool isHdr, float flowScale, uint64_t generationCount, 27 | bool forceDisableFp16, 28 | const std::function(const std::string&, bool)>& loader); 29 | 30 | #ifdef LSFGVK_EXCESS_DEBUG 31 | /// 32 | /// Initialize the renderdoc API. 33 | /// 34 | /// @throws LSFG::vulkan_error if the renderdoc API cannot be initialized. 35 | /// 36 | [[gnu::visibility("default")]] 37 | void initializeRenderDoc(); 38 | #endif // LSFGVK_EXCESS_DEBUG 39 | 40 | /// 41 | /// Create a new LSFG context on a swapchain. 42 | /// 43 | /// @param in0 File descriptor for the first input image. 44 | /// @param in1 File descriptor for the second input image. 45 | /// @param outN File descriptor for each output image. This defines the LSFG level. 46 | /// @param extent The size of the images 47 | /// @param format The format of the images. 48 | /// @return A unique identifier for the created context. 49 | /// 50 | /// @throws LSFG::vulkan_error if the context cannot be created. 51 | /// 52 | [[gnu::visibility("default")]] 53 | int32_t createContext( 54 | int in0, int in1, const std::vector& outN, 55 | VkExtent2D extent, VkFormat format); 56 | 57 | /// 58 | /// Present a context. 59 | /// 60 | /// @param id Unique identifier of the context to present. 61 | /// @param inSem Semaphore to wait on before starting the generation. 62 | /// @param outSem Semaphores to signal once each output image is ready. 63 | /// 64 | /// @throws LSFG::vulkan_error if the context cannot be presented. 65 | /// 66 | [[gnu::visibility("default")]] 67 | void presentContext(int32_t id, int inSem, const std::vector& outSem); 68 | 69 | /// 70 | /// Delete an LSFG context. 71 | /// 72 | /// @param id Unique identifier of the context to delete. 73 | /// 74 | [[gnu::visibility("default")]] 75 | void deleteContext(int32_t id); 76 | 77 | /// 78 | /// Deinitialize the LSFG library. 79 | /// 80 | [[gnu::visibility("default")]] 81 | void finalize(); 82 | 83 | } 84 | -------------------------------------------------------------------------------- /framegen/v3.1_include/v3_1/shaders/delta.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/buffer.hpp" 4 | #include "core/commandbuffer.hpp" 5 | #include "core/descriptorset.hpp" 6 | #include "core/image.hpp" 7 | #include "core/pipeline.hpp" 8 | #include "core/sampler.hpp" 9 | #include "core/shadermodule.hpp" 10 | #include "common/utils.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace LSFG_3_1::Shaders { 18 | 19 | using namespace LSFG; 20 | 21 | /// 22 | /// Delta shader. 23 | /// 24 | class Delta { 25 | public: 26 | Delta() = default; 27 | 28 | /// 29 | /// Initialize the shaderchain. 30 | /// 31 | /// @param inImgs1 Three sets of four RGBA images, corresponding to a frame count % 3. 32 | /// @param inImg2 Second Input image 33 | /// @param optImg1 Optional image for non-first passes. 34 | /// @param optImg2 Second optional image for non-first passes. 35 | /// @param optImg3 Third optional image for non-first passes. 36 | /// 37 | /// @throws LSFG::vulkan_error if resource creation fails. 38 | /// 39 | Delta(Vulkan& vk, std::array, 3> inImgs1, 40 | Core::Image inImg2, 41 | std::optional optImg1, 42 | std::optional optImg2, 43 | std::optional optImg3); 44 | 45 | /// 46 | /// Dispatch the shaderchain. 47 | /// 48 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); 49 | 50 | /// Get the first output image 51 | [[nodiscard]] const auto& getOutImage1() const { return this->outImg1; } 52 | /// Get the second output image 53 | [[nodiscard]] const auto& getOutImage2() const { return this->outImg2; } 54 | 55 | /// Trivially copyable, moveable and destructible 56 | Delta(const Delta&) noexcept = default; 57 | Delta& operator=(const Delta&) noexcept = default; 58 | Delta(Delta&&) noexcept = default; 59 | Delta& operator=(Delta&&) noexcept = default; 60 | ~Delta() = default; 61 | private: 62 | std::array shaderModules; 63 | std::array pipelines; 64 | std::array samplers; 65 | struct DeltaPass { 66 | Core::Buffer buffer; 67 | std::array firstDescriptorSet; 68 | std::array descriptorSets; 69 | std::array sixthDescriptorSet; 70 | }; 71 | std::vector passes; 72 | 73 | std::array, 3> inImgs1; 74 | Core::Image inImg2; 75 | std::optional optImg1, optImg2, optImg3; 76 | std::array tempImgs1; 77 | std::array tempImgs2; 78 | Core::Image outImg1, outImg2; 79 | }; 80 | 81 | } 82 | -------------------------------------------------------------------------------- /framegen/v3.1p_include/v3_1p/shaders/delta.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/buffer.hpp" 4 | #include "core/commandbuffer.hpp" 5 | #include "core/descriptorset.hpp" 6 | #include "core/image.hpp" 7 | #include "core/pipeline.hpp" 8 | #include "core/sampler.hpp" 9 | #include "core/shadermodule.hpp" 10 | #include "common/utils.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace LSFG_3_1P::Shaders { 18 | 19 | using namespace LSFG; 20 | 21 | /// 22 | /// Delta shader. 23 | /// 24 | class Delta { 25 | public: 26 | Delta() = default; 27 | 28 | /// 29 | /// Initialize the shaderchain. 30 | /// 31 | /// @param inImgs1 Three sets of two RGBA images, corresponding to a frame count % 3. 32 | /// @param inImg2 Second Input image 33 | /// @param optImg1 Optional image for non-first passes. 34 | /// @param optImg2 Second optional image for non-first passes. 35 | /// @param optImg3 Third optional image for non-first passes. 36 | /// 37 | /// @throws LSFG::vulkan_error if resource creation fails. 38 | /// 39 | Delta(Vulkan& vk, std::array, 3> inImgs1, 40 | Core::Image inImg2, 41 | std::optional optImg1, 42 | std::optional optImg2, 43 | std::optional optImg3); 44 | 45 | /// 46 | /// Dispatch the shaderchain. 47 | /// 48 | void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); 49 | 50 | /// Get the first output image 51 | [[nodiscard]] const auto& getOutImage1() const { return this->outImg1; } 52 | /// Get the second output image 53 | [[nodiscard]] const auto& getOutImage2() const { return this->outImg2; } 54 | 55 | /// Trivially copyable, moveable and destructible 56 | Delta(const Delta&) noexcept = default; 57 | Delta& operator=(const Delta&) noexcept = default; 58 | Delta(Delta&&) noexcept = default; 59 | Delta& operator=(Delta&&) noexcept = default; 60 | ~Delta() = default; 61 | private: 62 | std::array shaderModules; 63 | std::array pipelines; 64 | std::array samplers; 65 | struct DeltaPass { 66 | Core::Buffer buffer; 67 | std::array firstDescriptorSet; 68 | std::array descriptorSets; 69 | std::array sixthDescriptorSet; 70 | }; 71 | std::vector passes; 72 | 73 | std::array, 3> inImgs1; 74 | Core::Image inImg2; 75 | std::optional optImg1, optImg2, optImg3; 76 | std::array tempImgs1; 77 | std::array tempImgs2; 78 | Core::Image outImg1, outImg2; 79 | }; 80 | 81 | } 82 | -------------------------------------------------------------------------------- /framegen/v3.1_include/v3_1/context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/image.hpp" 4 | #include "core/semaphore.hpp" 5 | #include "core/fence.hpp" 6 | #include "core/commandbuffer.hpp" 7 | #include "shaders/alpha.hpp" 8 | #include "shaders/beta.hpp" 9 | #include "shaders/delta.hpp" 10 | #include "shaders/gamma.hpp" 11 | #include "shaders/generate.hpp" 12 | #include "shaders/mipmaps.hpp" 13 | #include "common/utils.hpp" 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace LSFG_3_1 { 22 | 23 | using namespace LSFG; 24 | 25 | class Context { 26 | public: 27 | /// 28 | /// Create a context 29 | /// 30 | /// @param vk The Vulkan instance to use. 31 | /// @param in0 File descriptor for the first input image. 32 | /// @param in1 File descriptor for the second input image. 33 | /// @param outN File descriptors for the output images. 34 | /// @param extent The size of the images. 35 | /// @param format The format of the images. 36 | /// 37 | /// @throws LSFG::vulkan_error if the context fails to initialize. 38 | /// 39 | Context(Vulkan& vk, 40 | int in0, int in1, const std::vector& outN, 41 | VkExtent2D extent, VkFormat format); 42 | 43 | /// 44 | /// Present on the context. 45 | /// 46 | /// @param inSem Semaphore to wait on before starting the generation. 47 | /// @param outSem Semaphores to signal after each generation is done. 48 | /// 49 | /// @throws LSFG::vulkan_error if the context fails to present. 50 | /// 51 | void present(Vulkan& vk, 52 | int inSem, const std::vector& outSem); 53 | 54 | // Trivially copyable, moveable and destructible 55 | Context(const Context&) = default; 56 | Context& operator=(const Context&) = default; 57 | Context(Context&&) = default; 58 | Context& operator=(Context&&) = default; 59 | ~Context() = default; 60 | private: 61 | Core::Image inImg_0, inImg_1; // inImg_0 is next when fc % 2 == 0 62 | uint64_t frameIdx{0}; 63 | 64 | struct RenderData { 65 | Core::Semaphore inSemaphore; // signaled when input is ready 66 | std::vector internalSemaphores; // signaled when first step is done 67 | std::vector outSemaphores; // signaled when each pass is done 68 | std::vector completionFences; // fence for completion of each pass 69 | 70 | Core::CommandBuffer cmdBuffer1; 71 | std::vector cmdBuffers2; // command buffers for second step 72 | 73 | bool shouldWait{false}; 74 | }; 75 | std::array data; 76 | 77 | Shaders::Mipmaps mipmaps; 78 | std::array alpha; 79 | Shaders::Beta beta; 80 | std::array gamma; 81 | std::array delta; 82 | Shaders::Generate generate; 83 | }; 84 | 85 | } 86 | -------------------------------------------------------------------------------- /framegen/v3.1p_include/v3_1p/context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core/image.hpp" 4 | #include "core/semaphore.hpp" 5 | #include "core/fence.hpp" 6 | #include "core/commandbuffer.hpp" 7 | #include "shaders/alpha.hpp" 8 | #include "shaders/beta.hpp" 9 | #include "shaders/delta.hpp" 10 | #include "shaders/gamma.hpp" 11 | #include "shaders/generate.hpp" 12 | #include "shaders/mipmaps.hpp" 13 | #include "common/utils.hpp" 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace LSFG_3_1P { 22 | 23 | using namespace LSFG; 24 | 25 | class Context { 26 | public: 27 | /// 28 | /// Create a context 29 | /// 30 | /// @param vk The Vulkan instance to use. 31 | /// @param in0 File descriptor for the first input image. 32 | /// @param in1 File descriptor for the second input image. 33 | /// @param outN File descriptors for the output images. 34 | /// @param extent The size of the images. 35 | /// @param format The format of the images. 36 | /// 37 | /// @throws LSFG::vulkan_error if the context fails to initialize. 38 | /// 39 | Context(Vulkan& vk, 40 | int in0, int in1, const std::vector& outN, 41 | VkExtent2D extent, VkFormat format); 42 | 43 | /// 44 | /// Present on the context. 45 | /// 46 | /// @param inSem Semaphore to wait on before starting the generation. 47 | /// @param outSem Semaphores to signal after each generation is done. 48 | /// 49 | /// @throws LSFG::vulkan_error if the context fails to present. 50 | /// 51 | void present(Vulkan& vk, 52 | int inSem, const std::vector& outSem); 53 | 54 | // Trivially copyable, moveable and destructible 55 | Context(const Context&) = default; 56 | Context& operator=(const Context&) = default; 57 | Context(Context&&) = default; 58 | Context& operator=(Context&&) = default; 59 | ~Context() = default; 60 | private: 61 | Core::Image inImg_0, inImg_1; // inImg_0 is next when fc % 2 == 0 62 | uint64_t frameIdx{0}; 63 | 64 | struct RenderData { 65 | Core::Semaphore inSemaphore; // signaled when input is ready 66 | std::vector internalSemaphores; // signaled when first step is done 67 | std::vector outSemaphores; // signaled when each pass is done 68 | std::vector completionFences; // fence for completion of each pass 69 | 70 | Core::CommandBuffer cmdBuffer1; 71 | std::vector cmdBuffers2; // command buffers for second step 72 | 73 | bool shouldWait{false}; 74 | }; 75 | std::array data; 76 | 77 | Shaders::Mipmaps mipmaps; 78 | std::array alpha; 79 | Shaders::Beta beta; 80 | std::array gamma; 81 | std::array delta; 82 | Shaders::Generate generate; 83 | }; 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "config/config.hpp" 2 | #include "extract/extract.hpp" 3 | #include "utils/benchmark.hpp" 4 | #include "utils/utils.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace { 15 | [[gnu::constructor]] 16 | [[gnu::visibility("default")]] 17 | void lsfgvk_init() { 18 | std::cerr << std::unitbuf; 19 | 20 | // read configuration 21 | try { 22 | Config::updateConfig(Utils::getConfigFile(), Utils::getProcessName()); 23 | } catch (const std::exception& e) { 24 | std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, IGNORING:\n"; 25 | std::cerr << "- " << e.what() << '\n'; 26 | return; 27 | } 28 | 29 | // exit silently if not enabled 30 | if (!Config::currentConf.has_value()) 31 | return; 32 | 33 | // load shaders 34 | try { 35 | Extract::extractShaders(); 36 | } catch (const std::exception& e) { 37 | std::cerr << "lsfg-vk: An error occurred while trying to extract the shaders, exiting:\n"; 38 | std::cerr << "- " << e.what() << '\n'; 39 | exit(EXIT_FAILURE); 40 | } 41 | std::cerr << "lsfg-vk: Shaders extracted successfully.\n"; 42 | 43 | // run benchmark if requested 44 | const char* benchmark_flag = std::getenv("LSFG_BENCHMARK"); 45 | if (!benchmark_flag) 46 | return; 47 | 48 | const std::string resolution(benchmark_flag); 49 | uint32_t width{}; 50 | uint32_t height{}; 51 | try { 52 | const size_t x = resolution.find('x'); 53 | if (x == std::string::npos) 54 | throw std::runtime_error("Unable to find 'x' in benchmark string"); 55 | 56 | const std::string width_str = resolution.substr(0, x); 57 | const std::string height_str = resolution.substr(x + 1); 58 | if (width_str.empty() || height_str.empty()) 59 | throw std::runtime_error("Invalid resolution"); 60 | 61 | const int32_t w = std::stoi(width_str); 62 | const int32_t h = std::stoi(height_str); 63 | if (w < 0 || h < 0) 64 | throw std::runtime_error("Resolution cannot be negative"); 65 | 66 | width = static_cast(w); 67 | height = static_cast(h); 68 | } catch (const std::exception& e) { 69 | std::cerr << "lsfg-vk: An error occurred while trying to parse the resolution, exiting:\n"; 70 | std::cerr << "- " << e.what() << '\n'; 71 | exit(EXIT_FAILURE); 72 | } 73 | 74 | std::thread benchmark([width, height]() { 75 | try { 76 | Benchmark::run(width, height); 77 | } catch (const std::exception& e) { 78 | std::cerr << "lsfg-vk: An error occurred during the benchmark:\n"; 79 | std::cerr << "- " << e.what() << '\n'; 80 | exit(EXIT_FAILURE); 81 | } 82 | }); 83 | benchmark.detach(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /package/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -z "$VERSION" ]; then 3 | set -eux 4 | export VERSION="1.1.0" 5 | export ALPM_VERSION=$(git describe --long --tags | sed 's/\([^-]*-\)g/r\1/;s/-/./g' | sed "s/.\..\../$VERSION/") 6 | export DPKG_VERSION=$(git describe --long --tags | sed 's/\([^-]*-\)g/r\1/;s/-/./g' | sed "s/^v.\..\..\.r[1-9]\+./$VERSION~git$(date '+%Y%m%d')./") 7 | export RPM_VERSION=$(git describe --long --tags | sed 's/\([^-]*-\)g/r\1/;s/-/./g' | sed "s/v.\..\...r/$VERSION^/") 8 | else 9 | set -eux 10 | export ALPM_VERSION="$VERSION" 11 | export DPKG_VERSION="$VERSION" 12 | export RPM_VERSION="$VERSION" 13 | fi 14 | 15 | # set permission bits 16 | chmod 755 bin/lsfg-vk-ui 17 | chmod 755 lib/liblsfg-vk.so 18 | chmod 644 share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json 19 | chmod 644 share/applications/lsfg-vk-ui.desktop 20 | chmod 644 share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png 21 | 22 | # build alpm package 23 | echo "Building ALPM package..." 24 | 25 | mkdir -pv alpm 26 | envsubst < package/alpm.PKGINFO > alpm/.PKGINFO 27 | 28 | mkdir -pv alpm/usr/{bin,lib,share/vulkan/implicit_layer.d,share/applications,share/icons/hicolor/256x256/apps} 29 | cp -v bin/lsfg-vk-ui alpm/usr/bin/lsfg-vk-ui 30 | cp -v lib/liblsfg-vk.so alpm/usr/lib/liblsfg-vk.so 31 | cp -v share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json \ 32 | alpm/usr/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json 33 | cp -v share/applications/lsfg-vk-ui.desktop \ 34 | alpm/usr/share/applications/lsfg-vk-ui.desktop 35 | cp -v share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png \ 36 | alpm/usr/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png 37 | 38 | tar -cvzf "lsfg-vk-$VERSION.x86_64.tar.zst" -C alpm \ 39 | .PKGINFO usr 40 | 41 | # build dpkg package 42 | echo "Building DEB package..." 43 | 44 | mkdir -pv deb/DEBIAN 45 | envsubst < package/dpkg.control > deb/DEBIAN/control 46 | 47 | mkdir -pv deb/usr/{bin,lib,share/vulkan/implicit_layer.d,share/applications,share/icons/hicolor/256x256/apps} 48 | cp -v bin/lsfg-vk-ui deb/usr/bin/lsfg-vk-ui 49 | cp -v lib/liblsfg-vk.so deb/usr/lib/liblsfg-vk.so 50 | cp -v share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json \ 51 | deb/usr/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json 52 | cp -v share/applications/lsfg-vk-ui.desktop \ 53 | deb/usr/share/applications/lsfg-vk-ui.desktop 54 | cp -v share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png \ 55 | deb/usr/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png 56 | 57 | dpkg-deb --root-owner-group --build deb "lsfg-vk-$VERSION.x86_64.deb" 58 | 59 | # build rpm package 60 | echo "Building RPM package..." 61 | 62 | mkdir -pv rpm 63 | envsubst < package/rpm.spec > rpm/lsfg-vk.spec 64 | 65 | mkdir -pv rpm/SOURCES 66 | cp -v bin/lsfg-vk-ui rpm/SOURCES 67 | cp -v lib/liblsfg-vk.so rpm/SOURCES 68 | cp -v share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json \ 69 | rpm/SOURCES 70 | cp -v share/applications/lsfg-vk-ui.desktop \ 71 | rpm/SOURCES/lsfg-vk-ui.desktop 72 | cp -v share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png \ 73 | rpm/SOURCES/gay.pancake.lsfg-vk-ui.png 74 | 75 | rpmbuild -bb rpm/lsfg-vk.spec --define "_topdir $(pwd)/rpm" 76 | mv -v "rpm/RPMS/x86_64/lsfg-vk-$RPM_VERSION-1.x86_64.rpm" "lsfg-vk-$VERSION.x86_64.rpm" 77 | 78 | # cleanup 79 | rm -rf alpm deb rpm 80 | -------------------------------------------------------------------------------- /include/mini/commandbuffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mini/commandpool.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace Mini { 11 | 12 | /// State of the command buffer. 13 | enum class CommandBufferState { 14 | /// Command buffer is not initialized or has been destroyed. 15 | Invalid, 16 | /// Command buffer has been created. 17 | Empty, 18 | /// Command buffer recording has started. 19 | Recording, 20 | /// Command buffer recording has ended. 21 | Full, 22 | /// Command buffer has been submitted to a queue. 23 | Submitted 24 | }; 25 | 26 | /// 27 | /// C++ wrapper class for a Vulkan command buffer. 28 | /// 29 | /// This class manages the lifetime of a Vulkan command buffer. 30 | /// 31 | class CommandBuffer { 32 | public: 33 | CommandBuffer() noexcept = default; 34 | 35 | /// 36 | /// Create the command buffer. 37 | /// 38 | /// @param device Vulkan device 39 | /// @param pool Vulkan command pool 40 | /// 41 | /// @throws LSFG::vulkan_error if object creation fails. 42 | /// 43 | CommandBuffer(VkDevice device, const CommandPool& pool); 44 | 45 | /// 46 | /// Begin recording commands in the command buffer. 47 | /// 48 | /// @throws std::logic_error if the command buffer is in Empty state 49 | /// @throws LSFG::vulkan_error if beginning the command buffer fails. 50 | /// 51 | void begin(); 52 | 53 | /// 54 | /// End recording commands in the command buffer. 55 | /// 56 | /// @throws std::logic_error if the command buffer is not in Recording state 57 | /// @throws LSFG::vulkan_error if ending the command buffer fails. 58 | /// 59 | void end(); 60 | 61 | /// 62 | /// Submit the command buffer to a queue. 63 | /// 64 | /// @param queue Vulkan queue to submit to 65 | /// @param waitSemaphores Semaphores to wait on before executing the command buffer 66 | /// @param signalSemaphores Semaphores to signal after executing the command buffer 67 | /// 68 | /// @throws std::logic_error if the command buffer is not in Full state. 69 | /// @throws LSFG::vulkan_error if submission fails. 70 | /// 71 | void submit(VkQueue queue, 72 | const std::vector& waitSemaphores = {}, 73 | const std::vector& signalSemaphores = {}); 74 | 75 | /// Get the state of the command buffer. 76 | [[nodiscard]] CommandBufferState getState() const { return *this->state; } 77 | /// Get the Vulkan handle. 78 | [[nodiscard]] auto handle() const { return *this->commandBuffer; } 79 | 80 | /// Trivially copyable, moveable and destructible 81 | CommandBuffer(const CommandBuffer&) noexcept = default; 82 | CommandBuffer& operator=(const CommandBuffer&) noexcept = default; 83 | CommandBuffer(CommandBuffer&&) noexcept = default; 84 | CommandBuffer& operator=(CommandBuffer&&) noexcept = default; 85 | ~CommandBuffer() = default; 86 | private: 87 | std::shared_ptr state; 88 | std::shared_ptr commandBuffer; 89 | }; 90 | 91 | } 92 | -------------------------------------------------------------------------------- /include/context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hooks.hpp" 4 | #include "mini/commandbuffer.hpp" 5 | #include "mini/commandpool.hpp" 6 | #include "mini/image.hpp" 7 | #include "mini/semaphore.hpp" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /// 17 | /// This class is the frame generation context. There should be one instance per swapchain. 18 | /// 19 | class LsContext { 20 | public: 21 | /// 22 | /// Create the swapchain context. 23 | /// 24 | /// @param info The device information to use. 25 | /// @param swapchain The Vulkan swapchain to use. 26 | /// @param extent The extent of the swapchain images. 27 | /// @param swapchainImages The swapchain images to use. 28 | /// 29 | /// @throws LSFG::vulkan_error if any Vulkan call fails. 30 | /// 31 | LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, 32 | VkExtent2D extent, const std::vector& swapchainImages); 33 | 34 | /// 35 | /// Custom present logic. 36 | /// 37 | /// @param info The device information to use. 38 | /// @param pNext Unknown pointer set in the present info structure. 39 | /// @param queue The Vulkan queue to present the frame on. 40 | /// @param gameRenderSemaphores The semaphores to wait on before presenting. 41 | /// @param presentIdx The index of the swapchain image to present. 42 | /// @return The result of the Vulkan present operation, which can be VK_SUCCESS or VK_SUBOPTIMAL_KHR. 43 | /// 44 | /// @throws LSFG::vulkan_error if any Vulkan call fails. 45 | /// 46 | VkResult present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue, 47 | const std::vector& gameRenderSemaphores, uint32_t presentIdx); 48 | 49 | // Non-copyable, trivially moveable and destructible 50 | LsContext(const LsContext&) = delete; 51 | LsContext& operator=(const LsContext&) = delete; 52 | LsContext(LsContext&&) = default; 53 | LsContext& operator=(LsContext&&) = default; 54 | ~LsContext() = default; 55 | private: 56 | VkSwapchainKHR swapchain; 57 | std::vector swapchainImages; 58 | VkExtent2D extent; 59 | 60 | std::shared_ptr lsfgCtxId; // lsfg context id 61 | Mini::Image frame_0, frame_1; // frames shared with lsfg. write to frame_0 when fc % 2 == 0 62 | std::vector out_n; // output images shared with lsfg, indexed by framegen id 63 | 64 | Mini::CommandPool cmdPool; 65 | uint64_t frameIdx{0}; 66 | 67 | struct RenderPassInfo { 68 | Mini::CommandBuffer preCopyBuf; // copy from swapchain image to frame_0/frame_1 69 | std::array preCopySemaphores; // signal when preCopyBuf is done 70 | 71 | std::vector renderSemaphores; // signal when lsfg is done with frame n 72 | 73 | std::vector acquireSemaphores; // signal for swapchain image n 74 | 75 | std::vector postCopyBufs; // copy from out_n to swapchain image 76 | std::vector postCopySemaphores; // signal when postCopyBuf is done 77 | std::vector prevPostCopySemaphores; // signal for previous postCopyBuf 78 | }; // data for a single render pass 79 | std::array passInfos; // allocate 8 because why not 80 | }; 81 | -------------------------------------------------------------------------------- /framegen/src/core/buffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "core/buffer.hpp" 5 | #include "core/device.hpp" 6 | #include "common/exception.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace LSFG::Core; 14 | 15 | void Buffer::construct(const Core::Device& device, const void* data, VkBufferUsageFlags usage) { 16 | // create buffer 17 | const VkBufferCreateInfo desc{ 18 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 19 | .size = this->size, 20 | .usage = usage, 21 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE 22 | }; 23 | VkBuffer bufferHandle{}; 24 | auto res = vkCreateBuffer(device.handle(), &desc, nullptr, &bufferHandle); 25 | if (res != VK_SUCCESS || bufferHandle == VK_NULL_HANDLE) 26 | throw LSFG::vulkan_error(res, "Failed to create Vulkan buffer"); 27 | 28 | // find memory type 29 | VkPhysicalDeviceMemoryProperties memProps; 30 | vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps); 31 | 32 | VkMemoryRequirements memReqs; 33 | vkGetBufferMemoryRequirements(device.handle(), bufferHandle, &memReqs); 34 | 35 | #pragma clang diagnostic push 36 | #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" 37 | std::optional memType{}; 38 | for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { 39 | if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN 40 | (memProps.memoryTypes[i].propertyFlags & 41 | (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))) { 42 | memType.emplace(i); 43 | break; 44 | } // NOLINTEND 45 | } 46 | if (!memType.has_value()) 47 | throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for buffer"); 48 | #pragma clang diagnostic pop 49 | 50 | // allocate and bind memory 51 | const VkMemoryAllocateInfo allocInfo{ 52 | .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 53 | .allocationSize = memReqs.size, 54 | .memoryTypeIndex = memType.value() 55 | }; 56 | VkDeviceMemory memoryHandle{}; 57 | res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle); 58 | if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) 59 | throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan buffer"); 60 | 61 | res = vkBindBufferMemory(device.handle(), bufferHandle, memoryHandle, 0); 62 | if (res != VK_SUCCESS) 63 | throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan buffer"); 64 | 65 | // upload data to buffer 66 | uint8_t* buf{}; 67 | res = vkMapMemory(device.handle(), memoryHandle, 0, this->size, 0, reinterpret_cast(&buf)); 68 | if (res != VK_SUCCESS || buf == nullptr) 69 | throw LSFG::vulkan_error(res, "Failed to map memory for Vulkan buffer"); 70 | std::copy_n(reinterpret_cast(data), this->size, buf); 71 | vkUnmapMemory(device.handle(), memoryHandle); 72 | 73 | // store buffer and memory in shared ptr 74 | this->buffer = std::shared_ptr( 75 | new VkBuffer(bufferHandle), 76 | [dev = device.handle()](VkBuffer* img) { 77 | vkDestroyBuffer(dev, *img, nullptr); 78 | } 79 | ); 80 | this->memory = std::shared_ptr( 81 | new VkDeviceMemory(memoryHandle), 82 | [dev = device.handle()](VkDeviceMemory* mem) { 83 | vkFreeMemory(dev, *mem, nullptr); 84 | } 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /framegen/v3.1_src/shaders/generate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "v3_1/shaders/generate.hpp" 5 | #include "common/utils.hpp" 6 | #include "core/commandbuffer.hpp" 7 | #include "core/image.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace LSFG_3_1::Shaders; 15 | 16 | Generate::Generate(Vulkan& vk, 17 | Core::Image inImg1, Core::Image inImg2, 18 | Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, 19 | const std::vector& fds, VkFormat format) 20 | : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)), 21 | inImg3(std::move(inImg3)), inImg4(std::move(inImg4)), 22 | inImg5(std::move(inImg5)) { 23 | // create resources 24 | this->shaderModule = vk.shaders.getShader(vk.device, "generate", 25 | { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, 26 | { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, 27 | { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, 28 | { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); 29 | this->pipeline = vk.shaders.getPipeline(vk.device, "generate"); 30 | this->samplers.at(0) = vk.resources.getSampler(vk.device); 31 | this->samplers.at(1) = vk.resources.getSampler(vk.device, 32 | VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS); 33 | 34 | // create internal images/outputs 35 | const VkExtent2D extent = this->inImg1.getExtent(); 36 | for (size_t i = 0; i < vk.generationCount; i++) 37 | this->outImgs.emplace_back(vk.device, extent, format, 38 | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 39 | VK_IMAGE_ASPECT_COLOR_BIT, fds.empty() ? -1 : fds.at(i)); 40 | 41 | // hook up shaders 42 | for (size_t i = 0; i < vk.generationCount; i++) { 43 | auto& pass = this->passes.emplace_back(); 44 | pass.buffer = vk.resources.getBuffer(vk.device, 45 | static_cast(i + 1) / static_cast(vk.generationCount + 1)); 46 | for (size_t j = 0; j < 2; j++) { 47 | pass.descriptorSet.at(j) = Core::DescriptorSet(vk.device, vk.descriptorPool, 48 | this->shaderModule); 49 | pass.descriptorSet.at(j).update(vk.device) 50 | .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) 51 | .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers) 52 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg2 : this->inImg1) 53 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg1 : this->inImg2) 54 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) 55 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg4) 56 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg5) 57 | .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i)) 58 | .build(); 59 | } 60 | } 61 | } 62 | 63 | void Generate::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { 64 | auto& pass = this->passes.at(pass_idx); 65 | 66 | // first pass 67 | const auto extent = this->inImg1.getExtent(); 68 | const uint32_t threadsX = (extent.width + 15) >> 4; 69 | const uint32_t threadsY = (extent.height + 15) >> 4; 70 | 71 | Utils::BarrierBuilder(buf) 72 | .addW2R(this->inImg1) 73 | .addW2R(this->inImg2) 74 | .addW2R(this->inImg3) 75 | .addW2R(this->inImg4) 76 | .addW2R(this->inImg5) 77 | .addR2W(this->outImgs.at(pass_idx)) 78 | .build(); 79 | 80 | this->pipeline.bind(buf); 81 | pass.descriptorSet.at(frameCount % 2).bind(buf, this->pipeline); 82 | buf.dispatch(threadsX, threadsY, 1); 83 | } 84 | -------------------------------------------------------------------------------- /framegen/v3.1p_src/shaders/generate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "v3_1p/shaders/generate.hpp" 5 | #include "common/utils.hpp" 6 | #include "core/commandbuffer.hpp" 7 | #include "core/image.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace LSFG_3_1P::Shaders; 15 | 16 | Generate::Generate(Vulkan& vk, 17 | Core::Image inImg1, Core::Image inImg2, 18 | Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, 19 | const std::vector& fds, VkFormat format) 20 | : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)), 21 | inImg3(std::move(inImg3)), inImg4(std::move(inImg4)), 22 | inImg5(std::move(inImg5)) { 23 | // create resources 24 | this->shaderModule = vk.shaders.getShader(vk.device, "generate", 25 | { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, 26 | { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, 27 | { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, 28 | { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); 29 | this->pipeline = vk.shaders.getPipeline(vk.device, "generate"); 30 | this->samplers.at(0) = vk.resources.getSampler(vk.device); 31 | this->samplers.at(1) = vk.resources.getSampler(vk.device, 32 | VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS); 33 | 34 | // create internal images/outputs 35 | const VkExtent2D extent = this->inImg1.getExtent(); 36 | for (size_t i = 0; i < vk.generationCount; i++) 37 | this->outImgs.emplace_back(vk.device, extent, format, 38 | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 39 | VK_IMAGE_ASPECT_COLOR_BIT, fds.empty() ? -1 : fds.at(i)); 40 | 41 | // hook up shaders 42 | for (size_t i = 0; i < vk.generationCount; i++) { 43 | auto& pass = this->passes.emplace_back(); 44 | pass.buffer = vk.resources.getBuffer(vk.device, 45 | static_cast(i + 1) / static_cast(vk.generationCount + 1)); 46 | for (size_t j = 0; j < 2; j++) { 47 | pass.descriptorSet.at(j) = Core::DescriptorSet(vk.device, vk.descriptorPool, 48 | this->shaderModule); 49 | pass.descriptorSet.at(j).update(vk.device) 50 | .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) 51 | .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers) 52 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg2 : this->inImg1) 53 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg1 : this->inImg2) 54 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) 55 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg4) 56 | .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg5) 57 | .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i)) 58 | .build(); 59 | } 60 | } 61 | } 62 | 63 | void Generate::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { 64 | auto& pass = this->passes.at(pass_idx); 65 | 66 | // first pass 67 | const auto extent = this->inImg1.getExtent(); 68 | const uint32_t threadsX = (extent.width + 15) >> 4; 69 | const uint32_t threadsY = (extent.height + 15) >> 4; 70 | 71 | Utils::BarrierBuilder(buf) 72 | .addW2R(this->inImg1) 73 | .addW2R(this->inImg2) 74 | .addW2R(this->inImg3) 75 | .addW2R(this->inImg4) 76 | .addW2R(this->inImg5) 77 | .addR2W(this->outImgs.at(pass_idx)) 78 | .build(); 79 | 80 | this->pipeline.bind(buf); 81 | pass.descriptorSet.at(frameCount % 2).bind(buf, this->pipeline); 82 | buf.dispatch(threadsX, threadsY, 1); 83 | } 84 | --------------------------------------------------------------------------------