diff --git a/.clang-tidy b/.clang-tidy index b8e891c..b4010e0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -8,13 +8,13 @@ Checks: - "readability-*" - "bugprone-*" - "misc-*" -- "-misc-include-cleaner" - "-readability-braces-around-statements" - "-readability-function-cognitive-complexity" - "-readability-identifier-length" - "-readability-implicit-bool-conversion" - "-readability-magic-numbers" - "-readability-math-missing-parentheses" +- "-readability-named-parameter" - "-bugprone-easily-swappable-parameters" # configure modernization - "modernize-*" @@ -24,3 +24,14 @@ Checks: - "-cppcoreguidelines-avoid-magic-numbers" - "-cppcoreguidelines-pro-type-reinterpret-cast" # allows reinterpret_cast - "-cppcoreguidelines-avoid-non-const-global-variables" +- "-cppcoreguidelines-pro-type-union-access" +# disable slow and pointless checks +- "-modernize-use-std-numbers" +- "-modernize-type-traits" +- "-cppcoreguidelines-owning-memory" +- "-cppcoreguidelines-macro-to-enum" +- "-readability-container-contains" +- "-bugprone-reserved-identifier" +- "-bugprone-stringview-nullptr" +- "-bugprone-standalone-empty" +- "-misc-unused-using-decls" diff --git a/.gitattributes b/.gitattributes index 5f24d27..8d476d4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ *.cpp diff=cpp eol=lf *.hpp diff=cpp eol=lf *.md diff=markdown eol=lf -*.cs binary diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..3de5e09 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [PancakeTAS] +ko_fi: pancaketas diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..7f078fb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Report a bug (if lsfg-vk does work, but not as expected) +title: "[BUG] Explain your bug" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what's happening is and, if not immediately obvious, what's supposed to happen instead. + +Before reporting, make sure you have read through these two wiki pages and tried all the options: +https://github.com/PancakeTAS/lsfg-vk/wiki/Quirks +https://github.com/PancakeTAS/lsfg-vk/wiki/Known-incompatibilities + +Make sure this bug hasn't already been reported. Comment your findings on an existing issue if you find one! + +**To Reproduce** +Steps to reproduce the behavior: +1. Open '...' +2. Do '...' +3. Notice '...' + +**Screenshots/Videos** +If applicable, add screenshots to help explain your problem. + +**System information** +What Linux distro are you on? What driver version are you using? What's in your machine? +Anything that could be relevant. + +**Verbose log messages** +Follow this wiki page to get log message. You may skip this step if you think it isn't relevant: +https://github.com/PancakeTAS/lsfg-vk/wiki/How-to-ask-for-help diff --git a/.github/ISSUE_TEMPLATE/compatibility_report.md b/.github/ISSUE_TEMPLATE/compatibility_report.md new file mode 100644 index 0000000..b62269f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/compatibility_report.md @@ -0,0 +1,34 @@ +--- +name: Compatibility report +about: Report a bug (if lsfg-vk does not work, or poorly works on a certain game or app) +title: "[COMPATIBILITY] Explain your bug" +labels: compatibility +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what's happening is and, if not immediately obvious, what's supposed to happen instead. + +Before reporting, make sure you have read through these two wiki pages and tried all the options: +https://github.com/PancakeTAS/lsfg-vk/wiki/Quirks +https://github.com/PancakeTAS/lsfg-vk/wiki/Known-incompatibilities + +Make sure this bug hasn't already been reported. Comment your findings on an existing issue if you find one! + +**To Reproduce** +Steps to reproduce the behavior: +1. Open '...' +2. Do '...' +3. Notice '...' + +**Screenshots/Videos** +If applicable, add screenshots to help explain your problem. + +**System information** +What Linux distro are you on? What driver version are you using? What's in your machine? +Anything that could be relevant. + +**Verbose log messages** +Follow this wiki page to get log message. You may skip this step if you think it isn't relevant: +https://github.com/PancakeTAS/lsfg-vk/wiki/How-to-ask-for-help diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..0004994 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature request +about: Request a new feature (DO NOT REQUEST ANYTHING UNRELATED TO FRAME GENERATION) +title: "[COMPATIBILITY] Explain your bug" +labels: feature +assignees: '' + +--- + +**Describe the feature** +A short and fitting description of what you want to see in the project. + +If someone already reported a variation of this feature, comment on it. If the issue has been closed, do not resubmit the feature request. + +**Example usecase/scenario** +When would users want this feature? diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..135daa7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,69 @@ +name: Build lsfg-vk + +on: + push: + branches: ["release"] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + steps: + # prepare system + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Install build dependencies + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: git wget xvfb + clang clang-tools llvm rustup + cmake ninja-build pkg-config + libvulkan-dev + libgtk-4-dev libadwaita-1-dev + version: 1.0 + execute_install_scripts: true + - name: Install rust dependency + run: | + rustup default stable + # build the project + - name: Configure with CMake and Ninja + run: | + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=./build-release \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On + - name: Build with Ninja + run: | + ninja -C build + - name: Install with CMake + run: | + cmake --install build --strip + - name: Build lsfg-vk-ui with appimage.sh + run: | + pushd ui + chmod +x ../scripts/build/appimage.sh + ../scripts/build/appimage.sh + popd + - name: Install lsfg-vk-ui + run: | + mkdir -p build-release/{bin,share/applications,share/icons/hicolor/256x256/apps} + mv ui/lsfg-vk-ui.AppImage build-release/bin/lsfg-vk-ui + cp ui/rsc/gay.pancake.lsfg-vk-ui.desktop build-release/share/applications/lsfg-vk-ui.desktop + cp ui/rsc/icon.png build-release/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png + # upload all files + - name: Upload lsfg-vk artifact + uses: actions/upload-artifact@v4 + with: + name: lsfg-vk_TEST + path: | + build-release/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json + build-release/share/applications/lsfg-vk-ui.desktop + build-release/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png + build-release/lib/liblsfg-vk.so + build-release/bin/lsfg-vk-ui diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml new file mode 100644 index 0000000..f71d65e --- /dev/null +++ b/.github/workflows/flatpak.yml @@ -0,0 +1,24 @@ +name: Build lsfg-vk Flatpak extensions + +on: + push: + branches: ["release"] + +jobs: + flatpak: + runs-on: ubuntu-latest + strategy: + matrix: + version: ["23.08", "24.08"] + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-${{ matrix.version }} + options: --privileged + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build Flatpak extension (${{ matrix.version }}) + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: "org.freedesktop.Platform.VulkanLayer.lsfg_vk_TEST_${{ matrix.version }}.flatpak" + manifest-path: "scripts/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_${{ matrix.version }}.yml" + verbose: true diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 0000000..ea76947 --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,47 @@ +name: Package lsfg-vk + +on: + workflow_run: + workflows: ["Build lsfg-vk"] + types: + - completed + branches: ["release"] + +jobs: + package: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + # prepare system + - name: Checkout repository + uses: actions/checkout@v4 + - name: Download lsfg-vk artifacts + uses: actions/download-artifact@v4 + with: + name: lsfg-vk_TEST + path: . + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + - name: Package lsfg-vk for various distros + run: | + export VERSION=$(grep -oP ' VERSION\s+\K[\d.]+' CMakeLists.txt) + chmod +x scripts/package/package.sh + bash ./scripts/package/package.sh + - name: Upload lsfg-vk for dpkg + uses: actions/upload-artifact@v4 + with: + name: lsfg-vk.dpkg_TEST + path: | + *.deb + - name: Upload lsfg-vk for rpm + uses: actions/upload-artifact@v4 + with: + name: lsfg-vk.rpm_TEST + path: | + *.rpm + - name: Upload lsfg-vk for alpm + uses: actions/upload-artifact@v4 + with: + name: lsfg-vk.alpm_TEST + path: | + *.zst diff --git a/.gitignore b/.gitignore index eb55056..682effd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ # cmake files /build +# cargo files +/ui/target + # ide/lsp files +/.zed /.vscode /.clangd /.cache diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..389035d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "thirdparty/pe-parse"] + path = thirdparty/pe-parse + url = https://github.com/trailofbits/pe-parse +[submodule "thirdparty/dxbc"] + path = thirdparty/dxbc + url = https://github.com/PancakeTAS/dxbc.git +[submodule "thirdparty/toml11"] + path = thirdparty/toml11 + url = https://github.com/ToruNiina/toml11 +[submodule "thirdparty/volk"] + path = thirdparty/volk + url = https://github.com/zeux/volk diff --git a/CMakeLists.txt b/CMakeLists.txt index dd4393a..b9ad34f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,48 +1,79 @@ -cmake_minimum_required(VERSION 3.29) +cmake_minimum_required(VERSION 3.10) -project(lsfg-vk-base-base - VERSION 0.0.1 - DESCRIPTION "lsfg-vk-base: LSFG on Linux through Vulkan" - LANGUAGES CXX) +set(CMAKE_SKIP_RPATH ON) -# cmake options +# subprojects +add_compile_options(-fPIC + -Wno-deprecated-declarations + -Wno-unused-template) -set(CMAKE_CXX_COMPILER clang++) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_CLANG_TIDY clang-tidy) - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +add_subdirectory(thirdparty/dxbc EXCLUDE_FROM_ALL) +add_subdirectory(thirdparty/pe-parse/pe-parser-library EXCLUDE_FROM_ALL) +add_subdirectory(thirdparty/toml11 EXCLUDE_FROM_ALL) +add_subdirectory(thirdparty/volk EXCLUDE_FROM_ALL) +add_subdirectory(framegen) # main project +project(lsfg-vk + VERSION 0.9.0 + DESCRIPTION "Lossless Scaling Frame Generation on Linux" + LANGUAGES CXX) file(GLOB SOURCES - "src/core/*.cpp" + "src/config/*.cpp" + "src/extract/*.cpp" + "src/mini/*.cpp" "src/utils/*.cpp" "src/*.cpp" ) -add_executable(lsfg-vk-base ${SOURCES}) +add_library(lsfg-vk SHARED ${SOURCES}) -target_include_directories(lsfg-vk-base - PUBLIC include) -target_link_libraries(lsfg-vk-base - PUBLIC vulkan) -target_compile_options(lsfg-vk-base PRIVATE - -Weverything - # disable compat c++ flags - -Wno-pre-c++20-compat-pedantic - -Wno-pre-c++17-compat - -Wno-c++98-compat-pedantic - -Wno-c++98-compat - # disable other flags - -Wno-missing-designated-field-initializers - -Wno-shadow # allow shadowing - -Wno-switch-enum # ignore missing cases - -Wno-switch-default # ignore missing default - -Wno-padded # ignore automatic padding - -Wno-exit-time-destructors # allow globals - -Wno-global-constructors - # required for vulkan - -Wno-cast-function-type-strict -) +# target +set_target_properties(lsfg-vk PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON) +target_include_directories(lsfg-vk + PRIVATE include) +target_link_libraries(lsfg-vk PRIVATE + pe-parse dxbc toml11 + lsfg-vk-framegen) + +get_target_property(TOML11_INCLUDE_DIRS toml11 INTERFACE_INCLUDE_DIRECTORIES) +target_include_directories(lsfg-vk SYSTEM PRIVATE ${TOML11_INCLUDE_DIRS}) + +# diagnostics +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set_target_properties(lsfg-vk PROPERTIES + EXPORT_COMPILE_COMMANDS ON) +endif() + +if(LSFGVK_EXCESS_DEBUG) + message(STATUS "LSFGVK_EXCESS_DEBUG is only compatible with clang") + target_compile_options(lsfg-vk PRIVATE + -Weverything + # disable compat c++ flags + -Wno-pre-c++20-compat-pedantic + -Wno-pre-c++17-compat + -Wno-c++98-compat-pedantic + -Wno-c++98-compat + # disable other flags + -Wno-missing-designated-field-initializers + -Wno-shadow # allow shadowing + -Wno-switch-enum # ignore missing cases + -Wno-switch-default # ignore missing default + -Wno-padded # ignore automatic padding + -Wno-exit-time-destructors # allow globals + -Wno-global-constructors # allow globals + -Wno-cast-function-type-strict # for vulkan + ) + + set_target_properties(lsfg-vk PROPERTIES + CXX_CLANG_TIDY clang-tidy) +endif() + +# install +install(FILES "${CMAKE_BINARY_DIR}/liblsfg-vk.so" + DESTINATION lib) +install(FILES "${CMAKE_SOURCE_DIR}/VkLayer_LS_frame_generation.json" + DESTINATION share/vulkan/implicit_layer.d) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b5c8a3e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +## MIT License + +Copyright (c) 2025 lsfg-vk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2825a0b --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# lsfg-vk +Lossless Scaling is a Windows-exclusive app with the goal of bringing frame generation (among other features) to every single game or app. + +lsfg-vk brings this frame generation to Linux users by acting as a Vulkan layer inbetween your game and your graphics card. + +>[!TIP] +> **This is a pre-release**. We are still ironing out the last few issues before finally releasing a first version, so beware of any issues you encounter on the way and report them via GitHub issues or the Discord. + +## Installation + +lsfg-vk can run on a variety of Linux distributions: +- Click [here](https://github.com/PancakeTAS/lsfg-vk/releases) to download lsfg-vk for your distribution +- Follow [this guide](https://github.com/PancakeTAS/lsfg-vk/wiki/Installation-Guide) if you any more help. + +Once installed, open up the lsfg-vk Configuration Window which should hopefully appear in your application menu. + +Please see the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki) for more information and join the [Discord](https://discord.gg/losslessscaling) for help (needs Steam verification). + +## Credits +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: +- [0xNULLderef](https://github.com/0xNULLderef): Teaching me how to reverse engineer software. +- [Caliel666](https://github.com/Caliel666): Writing the initial draft of the user interface. +- [Samueru-sama](https://github.com/Samueru-sama): Helping with various things XDG as well as app images and testing. +- Other contributors: Thank you for your contribution! + +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. diff --git a/VkLayer_LS_frame_generation.json b/VkLayer_LS_frame_generation.json new file mode 100644 index 0000000..8e8be3c --- /dev/null +++ b/VkLayer_LS_frame_generation.json @@ -0,0 +1,18 @@ +{ + "file_format_version": "1.0.0", + "layer": { + "name": "VK_LAYER_LS_frame_generation", + "type": "GLOBAL", + "api_version": "1.4.313", + "library_path": "liblsfg-vk.so", + "implementation_version": "1", + "description": "Lossless Scaling frame generation layer", + "functions": { + "vkGetInstanceProcAddr": "layer_vkGetInstanceProcAddr", + "vkGetDeviceProcAddr": "layer_vkGetDeviceProcAddr" + }, + "disable_environment": { + "DISABLE_LSFG": "1" + } + } +} diff --git a/framegen/.clang-tidy b/framegen/.clang-tidy new file mode 100644 index 0000000..b4010e0 --- /dev/null +++ b/framegen/.clang-tidy @@ -0,0 +1,37 @@ +Checks: +# enable basic checks +- "clang-analyzer-*" +# configure performance checks +- "performance-*" +- "-performance-enum-size" +# configure readability and bugprone checks +- "readability-*" +- "bugprone-*" +- "misc-*" +- "-readability-braces-around-statements" +- "-readability-function-cognitive-complexity" +- "-readability-identifier-length" +- "-readability-implicit-bool-conversion" +- "-readability-magic-numbers" +- "-readability-math-missing-parentheses" +- "-readability-named-parameter" +- "-bugprone-easily-swappable-parameters" +# configure modernization +- "modernize-*" +- "-modernize-use-trailing-return-type" +# configure cppcoreguidelines +- "cppcoreguidelines-*" +- "-cppcoreguidelines-avoid-magic-numbers" +- "-cppcoreguidelines-pro-type-reinterpret-cast" # allows reinterpret_cast +- "-cppcoreguidelines-avoid-non-const-global-variables" +- "-cppcoreguidelines-pro-type-union-access" +# disable slow and pointless checks +- "-modernize-use-std-numbers" +- "-modernize-type-traits" +- "-cppcoreguidelines-owning-memory" +- "-cppcoreguidelines-macro-to-enum" +- "-readability-container-contains" +- "-bugprone-reserved-identifier" +- "-bugprone-stringview-nullptr" +- "-bugprone-standalone-empty" +- "-misc-unused-using-decls" diff --git a/framegen/.gitattributes b/framegen/.gitattributes new file mode 100644 index 0000000..8d476d4 --- /dev/null +++ b/framegen/.gitattributes @@ -0,0 +1,3 @@ +*.cpp diff=cpp eol=lf +*.hpp diff=cpp eol=lf +*.md diff=markdown eol=lf diff --git a/framegen/.gitignore b/framegen/.gitignore new file mode 100644 index 0000000..43ab8ae --- /dev/null +++ b/framegen/.gitignore @@ -0,0 +1,9 @@ +# cmake files +/build + +# ide/lsp files +/.zed +/.vscode +/.clangd +/.cache +/.ccls diff --git a/framegen/CMakeLists.txt b/framegen/CMakeLists.txt new file mode 100644 index 0000000..be88d62 --- /dev/null +++ b/framegen/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.10) + +project(lsfg-vk-framegen + DESCRIPTION "Lossless Scaling Frame Generation Backend" + LANGUAGES CXX) + +file(GLOB SOURCES + "src/common/*.cpp" + "src/config/*.cpp" + "src/core/*.cpp" + "src/pool/*.cpp" + "src/*.cpp" + "v3.1_src/core/*.cpp" + "v3.1_src/pool/*.cpp" + "v3.1_src/shaders/*.cpp" + "v3.1_src/utils/*.cpp" + "v3.1_src/*.cpp" + "v3.1p_src/core/*.cpp" + "v3.1p_src/pool/*.cpp" + "v3.1p_src/shaders/*.cpp" + "v3.1p_src/utils/*.cpp" + "v3.1p_src/*.cpp" +) + +add_library(lsfg-vk-framegen STATIC ${SOURCES}) + +# target +set_target_properties(lsfg-vk-framegen PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON) +target_include_directories(lsfg-vk-framegen + PUBLIC include + PUBLIC public + PRIVATE v3.1_include + PRIVATE v3.1p_include) +target_link_libraries(lsfg-vk-framegen + PUBLIC volk) + +# diagnostics +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set_target_properties(lsfg-vk-framegen PROPERTIES + EXPORT_COMPILE_COMMANDS ON) +endif() + +if(LSFGVK_EXCESS_DEBUG) + target_compile_options(lsfg-vk-framegen PRIVATE + -Weverything + # disable compat c++ flags + -Wno-pre-c++20-compat-pedantic + -Wno-pre-c++17-compat + -Wno-c++98-compat-pedantic + -Wno-c++98-compat + # disable other flags + -Wno-missing-designated-field-initializers + -Wno-shadow # allow shadowing + -Wno-switch-enum # ignore missing cases + -Wno-switch-default # ignore missing default + -Wno-padded # ignore automatic padding + -Wno-exit-time-destructors # allow globals + -Wno-global-constructors # allow globals + -Wno-cast-function-type-strict # for vulkan + ) + + set_target_properties(lsfg-vk-framegen PROPERTIES + CXX_CLANG_TIDY clang-tidy) +endif() diff --git a/framegen/LICENSE.md b/framegen/LICENSE.md new file mode 100644 index 0000000..b5c8a3e --- /dev/null +++ b/framegen/LICENSE.md @@ -0,0 +1,21 @@ +## MIT License + +Copyright (c) 2025 lsfg-vk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/framegen/README.md b/framegen/README.md new file mode 100644 index 0000000..aff3912 --- /dev/null +++ b/framegen/README.md @@ -0,0 +1,14 @@ +## lsfg-vk-framegen +Lossless Scaling Frame Generation + +This is a subproject of lsfg-vk and contains the dedicated Vulkan logic for generating frames. + +The project is intentionally structured as a fully external project, such that it can be integrated into other applications. + +### Interface + +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. + +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()`. + +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. diff --git a/framegen/include/common/exception.hpp b/framegen/include/common/exception.hpp new file mode 100644 index 0000000..a0e6a35 --- /dev/null +++ b/framegen/include/common/exception.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include +#include +#include + +namespace LSFG { + + /// Simple exception class for Vulkan errors. + class vulkan_error : public std::runtime_error { + public: + /// + /// Construct a vulkan_error with a message and a Vulkan result code. + /// + /// @param result The Vulkan result code associated with the error. + /// @param message The error message. + /// + explicit vulkan_error(VkResult result, const std::string& message); + + /// Get the Vulkan result code associated with this error. + [[nodiscard]] VkResult error() const { return this->result; } + + // Trivially copyable, moveable and destructible + vulkan_error(const vulkan_error&) = default; + vulkan_error(vulkan_error&&) = default; + vulkan_error& operator=(const vulkan_error&) = default; + vulkan_error& operator=(vulkan_error&&) = default; + ~vulkan_error() noexcept override; + private: + VkResult result; + }; + + /// Simple exception class for stacking errors. + class rethrowable_error : public std::runtime_error { + public: + /// + /// Construct a new rethrowable_error with a message. + /// + /// @param message The error message. + /// @param exe The original exception to rethrow. + /// + explicit rethrowable_error(const std::string& message, + const std::exception& exe); + + /// Get the exception as a string. + [[nodiscard]] const char* what() const noexcept override { + return message.c_str(); + } + + // Trivially copyable, moveable and destructible + rethrowable_error(const rethrowable_error&) = default; + rethrowable_error(rethrowable_error&&) = default; + rethrowable_error& operator=(const rethrowable_error&) = default; + rethrowable_error& operator=(rethrowable_error&&) = default; + ~rethrowable_error() noexcept override; + private: + std::string message; + }; + +} diff --git a/framegen/include/common/utils.hpp b/framegen/include/common/utils.hpp new file mode 100644 index 0000000..82e7039 --- /dev/null +++ b/framegen/include/common/utils.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include "core/commandbuffer.hpp" +#include "core/commandpool.hpp" +#include "core/descriptorpool.hpp" +#include "core/image.hpp" +#include "core/device.hpp" +#include "pool/resourcepool.hpp" +#include "pool/shaderpool.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +namespace LSFG::Utils { + + /// + /// Insert memory barriers for images in a command buffer. + /// + /// @throws std::logic_error if the command buffer is not in Recording state + /// + class BarrierBuilder { + public: + /// Create a barrier builder. + BarrierBuilder(const Core::CommandBuffer& buffer) + : commandBuffer(&buffer) { + this->barriers.reserve(16); // this is performance critical + } + + // Add a resource to the barrier builder. + BarrierBuilder& addR2W(Core::Image& image); + BarrierBuilder& addW2R(Core::Image& image); + + // Add an optional resource to the barrier builder. + BarrierBuilder& addR2W(std::optional& image) { + if (image.has_value()) this->addR2W(*image); return *this; } + BarrierBuilder& addW2R(std::optional& image) { + if (image.has_value()) this->addW2R(*image); return *this; } + + /// Add a list of resources to the barrier builder. + BarrierBuilder& addR2W(std::vector& images) { + for (auto& image : images) this->addR2W(image); return *this; } + BarrierBuilder& addW2R(std::vector& images) { + for (auto& image : images) this->addW2R(image); return *this; } + + /// Add an array of resources to the barrier builder. + template + BarrierBuilder& addR2W(std::array& images) { + for (auto& image : images) this->addR2W(image); return *this; } + template + BarrierBuilder& addW2R(std::array& images) { + for (auto& image : images) this->addW2R(image); return *this; } + + /// Finish building the barrier + void build() const; + private: + const Core::CommandBuffer* commandBuffer; + + std::vector barriers; + }; + + /// + /// Upload a DDS file to a Vulkan image. + /// + /// @param device The Vulkan device + /// @param commandPool The command pool + /// @param image The Vulkan image to upload to + /// @param path The path to the DDS file. + /// + /// @throws std::system_error If the file cannot be opened or read. + /// @throws ls:vulkan_error If the Vulkan image cannot be created or updated. + /// + void uploadImage(const Core::Device& device, + const Core::CommandPool& commandPool, + Core::Image& image, const std::string& path); + + /// + /// Clear a texture to white during setup. + /// + /// @param device The Vulkan device. + /// @param image The image to clear. + /// @param white If true, the image will be cleared to white, otherwise to black. + /// + /// @throws LSFG::vulkan_error If the Vulkan image cannot be cleared. + /// + void clearImage(const Core::Device& device, Core::Image& image, bool white = false); + +} + +namespace LSFG { + struct Vulkan { + Core::Device device; + Core::CommandPool commandPool; + Core::DescriptorPool descriptorPool; + + uint64_t generationCount; + float flowScale; + bool isHdr; + + Pool::ShaderPool shaders; + Pool::ResourcePool resources; + }; +} diff --git a/framegen/include/core/buffer.hpp b/framegen/include/core/buffer.hpp new file mode 100644 index 0000000..1aa1b49 --- /dev/null +++ b/framegen/include/core/buffer.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "core/device.hpp" + +#include + +#include +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan buffer. + /// + /// This class manages the lifetime of a Vulkan buffer. + /// + class Buffer { + public: + Buffer() noexcept = default; + + /// + /// Create the buffer. + /// + /// @param device Vulkan device + /// @param data Initial data for the buffer, also specifies the size of the buffer. + /// @param usage Usage flags for the buffer + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + template + Buffer(const Core::Device& device, const T& data, VkBufferUsageFlags usage) + : size(sizeof(T)) { + construct(device, reinterpret_cast(&data), usage); + } + + /// + /// Create the buffer. + /// + /// @param device Vulkan device + /// @param data Initial data for the buffer + /// @param size Size of the buffer in bytes + /// @param usage Usage flags for the buffer + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Buffer(const Core::Device& device, const void* data, size_t size, VkBufferUsageFlags usage) + : size(size) { + construct(device, data, usage); + } + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->buffer; } + /// Get the size of the buffer. + [[nodiscard]] size_t getSize() const { return this->size; } + + /// Trivially copyable, moveable and destructible + Buffer(const Buffer&) noexcept = default; + Buffer& operator=(const Buffer&) noexcept = default; + Buffer(Buffer&&) noexcept = default; + Buffer& operator=(Buffer&&) noexcept = default; + ~Buffer() = default; + private: + void construct(const Core::Device& device, const void* data, VkBufferUsageFlags usage); + + std::shared_ptr buffer; + std::shared_ptr memory; + + size_t size{}; + }; + +} diff --git a/framegen/include/core/commandbuffer.hpp b/framegen/include/core/commandbuffer.hpp new file mode 100644 index 0000000..35eeb57 --- /dev/null +++ b/framegen/include/core/commandbuffer.hpp @@ -0,0 +1,112 @@ +#pragma once + +#include "core/commandpool.hpp" +#include "core/fence.hpp" +#include "core/semaphore.hpp" +#include "core/device.hpp" + +#include + +#include +#include +#include +#include + +namespace LSFG::Core { + + /// State of the command buffer. + enum class CommandBufferState { + /// Command buffer is not initialized or has been destroyed. + Invalid, + /// Command buffer has been created. + Empty, + /// Command buffer recording has started. + Recording, + /// Command buffer recording has ended. + Full, + /// Command buffer has been submitted to a queue. + Submitted + }; + + /// + /// C++ wrapper class for a Vulkan command buffer. + /// + /// This class manages the lifetime of a Vulkan command buffer. + /// + class CommandBuffer { + public: + CommandBuffer() noexcept = default; + + /// + /// Create the command buffer. + /// + /// @param device Vulkan device + /// @param pool Vulkan command pool + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + CommandBuffer(const Core::Device& device, const CommandPool& pool); + + /// + /// Begin recording commands in the command buffer. + /// + /// @throws std::logic_error if the command buffer is in Empty state + /// @throws LSFG::vulkan_error if beginning the command buffer fails. + /// + void begin(); + + /// + /// Dispatch a compute command. + /// + /// @param x Number of groups in the X dimension + /// @param y Number of groups in the Y dimension + /// @param z Number of groups in the Z dimension + /// + /// @throws std::logic_error if the command buffer is not in Recording state + /// + void dispatch(uint32_t x, uint32_t y, uint32_t z) const; + + /// + /// End recording commands in the command buffer. + /// + /// @throws std::logic_error if the command buffer is not in Recording state + /// @throws LSFG::vulkan_error if ending the command buffer fails. + /// + void end(); + + /// + /// Submit the command buffer to a queue. + /// + /// @param queue Vulkan queue to submit to + /// @param fence Optional fence to signal when the command buffer has finished executing + /// @param waitSemaphores Semaphores to wait on before executing the command buffer + /// @param waitSemaphoreValues Values for the semaphores to wait on + /// @param signalSemaphores Semaphores to signal after executing the command buffer + /// @param signalSemaphoreValues Values for the semaphores to signal + /// + /// @throws std::logic_error if the command buffer is not in Full state. + /// @throws LSFG::vulkan_error if submission fails. + /// + void submit(VkQueue queue, std::optional fence, + const std::vector& waitSemaphores = {}, + std::optional> waitSemaphoreValues = std::nullopt, + const std::vector& signalSemaphores = {}, + std::optional> signalSemaphoreValues = std::nullopt); + + /// Get the state of the command buffer. + [[nodiscard]] CommandBufferState getState() const { return *this->state; } + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->commandBuffer; } + + /// Trivially copyable, moveable and destructible + CommandBuffer(const CommandBuffer&) noexcept = default; + CommandBuffer& operator=(const CommandBuffer&) noexcept = default; + CommandBuffer(CommandBuffer&&) noexcept = default; + CommandBuffer& operator=(CommandBuffer&&) noexcept = default; + ~CommandBuffer() = default; + private: + std::shared_ptr state; + std::shared_ptr commandBuffer; + }; + +} diff --git a/framegen/include/core/commandpool.hpp b/framegen/include/core/commandpool.hpp new file mode 100644 index 0000000..7f4539d --- /dev/null +++ b/framegen/include/core/commandpool.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "core/device.hpp" + +#include + +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan command pool. + /// + /// This class manages the lifetime of a Vulkan command pool. + /// + class CommandPool { + public: + CommandPool() noexcept = default; + + /// + /// Create the command pool. + /// + /// @param device Vulkan device + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + CommandPool(const Core::Device& device); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->commandPool; } + + /// Trivially copyable, moveable and destructible + CommandPool(const CommandPool&) noexcept = default; + CommandPool& operator=(const CommandPool&) noexcept = default; + CommandPool(CommandPool&&) noexcept = default; + CommandPool& operator=(CommandPool&&) noexcept = default; + ~CommandPool() = default; + private: + std::shared_ptr commandPool; + }; + +} diff --git a/framegen/include/core/descriptorpool.hpp b/framegen/include/core/descriptorpool.hpp new file mode 100644 index 0000000..792f843 --- /dev/null +++ b/framegen/include/core/descriptorpool.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "core/device.hpp" + +#include + +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan descriptor pool. + /// + /// This class manages the lifetime of a Vulkan descriptor pool. + /// + class DescriptorPool { + public: + DescriptorPool() noexcept = default; + + /// + /// Create the descriptor pool. + /// + /// @param device Vulkan device + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + DescriptorPool(const Core::Device& device); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->descriptorPool; } + + /// Trivially copyable, moveable and destructible + DescriptorPool(const DescriptorPool&) noexcept = default; + DescriptorPool& operator=(const DescriptorPool&) noexcept = default; + DescriptorPool(DescriptorPool&&) noexcept = default; + DescriptorPool& operator=(DescriptorPool&&) noexcept = default; + ~DescriptorPool() = default; + private: + std::shared_ptr descriptorPool; + }; + +} diff --git a/framegen/include/core/descriptorset.hpp b/framegen/include/core/descriptorset.hpp new file mode 100644 index 0000000..719035d --- /dev/null +++ b/framegen/include/core/descriptorset.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "core/device.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace LSFG::Core { + + class DescriptorSetUpdateBuilder; + + /// + /// C++ wrapper class for a Vulkan descriptor set. + /// + /// This class manages the lifetime of a Vulkan descriptor set. + /// + class DescriptorSet { + public: + DescriptorSet() noexcept = default; + + /// + /// Create the descriptor set. + /// + /// @param device Vulkan device + /// @param pool Descriptor pool to allocate from + /// @param shaderModule Shader module to use for the descriptor set + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + DescriptorSet(const Core::Device& device, + const DescriptorPool& pool, const ShaderModule& shaderModule); + + /// + /// Update the descriptor set with resources. + /// + /// @param device Vulkan device + /// + [[nodiscard]] DescriptorSetUpdateBuilder update(const Core::Device& device) const; + + /// + /// Bind a descriptor set to a command buffer. + /// + /// @param commandBuffer Command buffer to bind the descriptor set to. + /// @param pipeline Pipeline to bind the descriptor set to. + /// + void bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const; + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->descriptorSet; } + + /// Trivially copyable, moveable and destructible + DescriptorSet(const DescriptorSet&) noexcept = default; + DescriptorSet& operator=(const DescriptorSet&) noexcept = default; + DescriptorSet(DescriptorSet&&) noexcept = default; + DescriptorSet& operator=(DescriptorSet&&) noexcept = default; + ~DescriptorSet() = default; + private: + std::shared_ptr descriptorSet; + }; + + /// + /// Builder class for updating a descriptor set. + /// + class DescriptorSetUpdateBuilder { + friend class DescriptorSet; + public: + /// Add a resource to the descriptor set update. + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Image& image); + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Sampler& sampler); + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Buffer& buffer); + DescriptorSetUpdateBuilder& add(VkDescriptorType type); // empty entry + + /// Add a list of resources to the descriptor set update. + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& images) { + for (const auto& image : images) this->add(type, image); return *this; } + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& samplers) { + for (const auto& sampler : samplers) this->add(type, sampler); return *this; } + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& buffers) { + for (const auto& buffer : buffers) this->add(type, buffer); return *this; } + + /// Add an array of resources + template + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array& images) { + for (const auto& image : images) this->add(type, image); return *this; } + template + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array& samplers) { + for (const auto& sampler : samplers) this->add(type, sampler); return *this; } + template + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array& buffers) { + for (const auto& buffer : buffers) this->add(type, buffer); return *this; } + + /// Add an optional resource to the descriptor set update. + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& image) { + if (image.has_value()) this->add(type, *image); else this->add(type); return *this; } + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& sampler) { + if (sampler.has_value()) this->add(type, *sampler); else this->add(type); return *this; } + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& buffer) { + if (buffer.has_value()) this->add(type, *buffer); else this->add(type); return *this; } + + /// Finish building the descriptor set update. + void build(); + private: + const DescriptorSet* descriptorSet; + const Core::Device* device; + + DescriptorSetUpdateBuilder(const DescriptorSet& descriptorSet, const Core::Device& device) + : descriptorSet(&descriptorSet), device(&device) {} + + std::vector entries; + }; + +} diff --git a/framegen/include/core/device.hpp b/framegen/include/core/device.hpp new file mode 100644 index 0000000..d03a89c --- /dev/null +++ b/framegen/include/core/device.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "core/instance.hpp" + +#include + +#include +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan device. + /// + /// This class manages the lifetime of a Vulkan device. + /// + class Device { + public: + /// + /// Create the device. + /// + /// @param instance Vulkan instance + /// @param deviceUUID The UUID of the Vulkan device to use. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Device(const Instance& instance, uint64_t deviceUUID); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->device; } + /// Get the physical device associated with this logical device. + [[nodiscard]] VkPhysicalDevice getPhysicalDevice() const { return this->physicalDevice; } + /// Get the compute queue family index. + [[nodiscard]] uint32_t getComputeFamilyIdx() const { return this->computeFamilyIdx; } + /// Get the compute queue. + [[nodiscard]] VkQueue getComputeQueue() const { return this->computeQueue; } + + // Trivially copyable, moveable and destructible + Device(const Core::Device&) noexcept = default; + Device& operator=(const Core::Device&) noexcept = default; + Device(Device&&) noexcept = default; + Device& operator=(Device&&) noexcept = default; + ~Device() = default; + private: + std::shared_ptr device; + VkPhysicalDevice physicalDevice{}; + + uint32_t computeFamilyIdx{0}; + + VkQueue computeQueue{}; + }; + +} diff --git a/framegen/include/core/fence.hpp b/framegen/include/core/fence.hpp new file mode 100644 index 0000000..1739220 --- /dev/null +++ b/framegen/include/core/fence.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "core/device.hpp" + +#include + +#include +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan fence. + /// + /// This class manages the lifetime of a Vulkan fence. + /// + class Fence { + public: + Fence() noexcept = default; + + /// + /// Create the fence. + /// + /// @param device Vulkan device + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Fence(const Core::Device& device); + + /// + /// Reset the fence to an unsignaled state. + /// + /// @param device Vulkan device + /// + /// @throws LSFG::vulkan_error if resetting fails. + /// + void reset(const Core::Device& device) const; + + /// + /// Wait for the fence + /// + /// @param device Vulkan device + /// @param timeout The timeout in nanoseconds, or UINT64_MAX for no timeout. + /// @returns true if the fence signaled, false if it timed out. + /// + /// @throws LSFG::vulkan_error if waiting fails. + /// + [[nodiscard]] bool wait(const Core::Device& device, uint64_t timeout = UINT64_MAX) const; + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->fence; } + + // Trivially copyable, moveable and destructible + Fence(const Fence&) noexcept = default; + Fence& operator=(const Fence&) noexcept = default; + Fence(Fence&&) noexcept = default; + Fence& operator=(Fence&&) noexcept = default; + ~Fence() = default; + private: + std::shared_ptr fence; + }; + +} diff --git a/framegen/include/core/image.hpp b/framegen/include/core/image.hpp new file mode 100644 index 0000000..9731bdf --- /dev/null +++ b/framegen/include/core/image.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include "core/device.hpp" + +#include + +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan image. + /// + /// This class manages the lifetime of a Vulkan image. + /// + class Image { + public: + Image() noexcept = default; + + /// + /// Create the image. + /// + /// @param device Vulkan device + /// @param extent Extent of the image in pixels. + /// @param format Vulkan format of the image + /// @param usage Usage flags for the image + /// @param aspectFlags Aspect flags for the image view + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Image(const Core::Device& device, VkExtent2D extent, + VkFormat format = VK_FORMAT_R8G8B8A8_UNORM, + VkImageUsageFlags usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VkImageAspectFlags aspectFlags = VK_IMAGE_ASPECT_COLOR_BIT); + + /// + /// Create the image with shared backing memory. + /// + /// @param device Vulkan device + /// @param extent Extent of the image in pixels. + /// @param format Vulkan format of the image + /// @param usage Usage flags for the image + /// @param aspectFlags Aspect flags for the image view + /// @param fd File descriptor for shared memory. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Image(const Core::Device& device, VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int fd); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->image; } + /// Get the Vulkan device memory handle. + [[nodiscard]] auto getMemory() const { return *this->memory; } + /// Get the Vulkan image view handle. + [[nodiscard]] auto getView() const { return *this->view; } + /// Get the extent of the image. + [[nodiscard]] VkExtent2D getExtent() const { return this->extent; } + /// Get the format of the image. + [[nodiscard]] VkFormat getFormat() const { return this->format; } + /// Get the aspect flags of the image. + [[nodiscard]] VkImageAspectFlags getAspectFlags() const { return this->aspectFlags; } + + /// Set the layout of the image. + void setLayout(VkImageLayout layout) { *this->layout = layout; } + /// Get the current layout of the image. + [[nodiscard]] VkImageLayout getLayout() const { return *this->layout; } + + /// Trivially copyable, moveable and destructible + Image(const Image&) noexcept = default; + Image& operator=(const Image&) noexcept = default; + Image(Image&&) noexcept = default; + Image& operator=(Image&&) noexcept = default; + ~Image() = default; + private: + std::shared_ptr image; + std::shared_ptr memory; + std::shared_ptr view; + + std::shared_ptr layout; + + VkExtent2D extent{}; + VkFormat format{}; + VkImageAspectFlags aspectFlags{}; + }; + +} diff --git a/framegen/include/core/instance.hpp b/framegen/include/core/instance.hpp new file mode 100644 index 0000000..24cba8a --- /dev/null +++ b/framegen/include/core/instance.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan instance. + /// + /// This class manages the lifetime of a Vulkan instance. + /// + class Instance { + public: + /// + /// Create the instance. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Instance(); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return this->instance ? *this->instance : VK_NULL_HANDLE; } + + /// Trivially copyable, moveable and destructible + Instance(const Instance&) noexcept = default; + Instance& operator=(const Instance&) noexcept = default; + Instance(Instance&&) noexcept = default; + Instance& operator=(Instance&&) noexcept = default; + ~Instance() = default; + private: + std::shared_ptr instance; + }; + +} diff --git a/framegen/include/core/pipeline.hpp b/framegen/include/core/pipeline.hpp new file mode 100644 index 0000000..6eddb5a --- /dev/null +++ b/framegen/include/core/pipeline.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "core/commandbuffer.hpp" +#include "core/shadermodule.hpp" +#include "core/device.hpp" + +#include + +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan pipeline. + /// + /// This class manages the lifetime of a Vulkan pipeline. + /// + class Pipeline { + public: + Pipeline() noexcept = default; + + /// + /// Create a compute pipeline. + /// + /// @param device Vulkan device + /// @param shader Shader module to use for the pipeline. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Pipeline(const Core::Device& device, const ShaderModule& shader); + + /// + /// Bind the pipeline to a command buffer. + /// + /// @param commandBuffer Command buffer to bind the pipeline to. + /// + void bind(const CommandBuffer& commandBuffer) const; + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->pipeline; } + /// Get the pipeline layout. + [[nodiscard]] auto getLayout() const { return *this->layout; } + + /// Trivially copyable, moveable and destructible + Pipeline(const Pipeline&) noexcept = default; + Pipeline& operator=(const Pipeline&) noexcept = default; + Pipeline(Pipeline&&) noexcept = default; + Pipeline& operator=(Pipeline&&) noexcept = default; + ~Pipeline() = default; + private: + std::shared_ptr pipeline; + std::shared_ptr layout; + }; + +} diff --git a/framegen/include/core/sampler.hpp b/framegen/include/core/sampler.hpp new file mode 100644 index 0000000..981077c --- /dev/null +++ b/framegen/include/core/sampler.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "core/device.hpp" + +#include + +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan sampler. + /// + /// This class manages the lifetime of a Vulkan sampler. + /// + class Sampler { + public: + Sampler() noexcept = default; + + /// + /// Create the sampler. + /// + /// @param device Vulkan device + /// @param mode Address mode for the sampler. + /// @param compare Compare operation for the sampler. + /// @param isWhite Whether the border color is white. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Sampler(const Core::Device& device, + VkSamplerAddressMode mode, + VkCompareOp compare, + bool isWhite); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->sampler; } + + /// Trivially copyable, moveable and destructible + Sampler(const Sampler&) noexcept = default; + Sampler& operator=(const Sampler&) noexcept = default; + Sampler(Sampler&&) noexcept = default; + Sampler& operator=(Sampler&&) noexcept = default; + ~Sampler() = default; + private: + std::shared_ptr sampler; + }; + +} diff --git a/framegen/include/core/semaphore.hpp b/framegen/include/core/semaphore.hpp new file mode 100644 index 0000000..773ca0c --- /dev/null +++ b/framegen/include/core/semaphore.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "core/device.hpp" + +#include + +#include +#include +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan semaphore. + /// + /// This class manages the lifetime of a Vulkan semaphore. + /// + class Semaphore { + public: + Semaphore() noexcept = default; + + /// + /// Create the semaphore. + /// + /// @param device Vulkan device + /// @param initial Optional initial value for creating a timeline semaphore. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Semaphore(const Core::Device& device, std::optional initial = std::nullopt); + + /// + /// Import a semaphore. + /// + /// @param device Vulkan device + /// @param fd File descriptor to import the semaphore from. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Semaphore(const Core::Device& device, int fd); + + /// + /// Signal the semaphore to a specific value. + /// + /// @param device Vulkan device + /// @param value The value to signal the semaphore to. + /// + /// @throws std::logic_error if the semaphore is not a timeline semaphore. + /// @throws LSFG::vulkan_error if signaling fails. + /// + void signal(const Core::Device& device, uint64_t value) const; + + /// + /// Wait for the semaphore to reach a specific value. + /// + /// @param device Vulkan device + /// @param value The value to wait for. + /// @param timeout The timeout in nanoseconds, or UINT64_MAX for no timeout. + /// @returns true if the semaphore reached the value, false if it timed out. + /// + /// @throws std::logic_error if the semaphore is not a timeline semaphore. + /// @throws LSFG::vulkan_error if waiting fails. + /// + [[nodiscard]] bool wait(const Core::Device& device, uint64_t value, uint64_t timeout = UINT64_MAX) const; + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->semaphore; } + + // Trivially copyable, moveable and destructible + Semaphore(const Semaphore&) noexcept = default; + Semaphore& operator=(const Semaphore&) noexcept = default; + Semaphore(Semaphore&&) noexcept = default; + Semaphore& operator=(Semaphore&&) noexcept = default; + ~Semaphore() = default; + private: + std::shared_ptr semaphore; + bool isTimeline{}; + }; + +} diff --git a/framegen/include/core/shadermodule.hpp b/framegen/include/core/shadermodule.hpp new file mode 100644 index 0000000..ac38234 --- /dev/null +++ b/framegen/include/core/shadermodule.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "core/device.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace LSFG::Core { + + /// + /// C++ wrapper class for a Vulkan shader module. + /// + /// This class manages the lifetime of a Vulkan shader module. + /// + class ShaderModule { + public: + ShaderModule() noexcept = default; + + /// + /// Create the shader module. + /// + /// @param device Vulkan device + /// @param code SPIR-V bytecode for the shader. + /// @param descriptorTypes Descriptor types used in the shader. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + ShaderModule(const Core::Device& device, const std::vector& code, + const std::vector>& descriptorTypes); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->shaderModule; } + /// Get the descriptor set layout. + [[nodiscard]] auto getLayout() const { return *this->descriptorSetLayout; } + + /// Trivially copyable, moveable and destructible + ShaderModule(const ShaderModule&) noexcept = default; + ShaderModule& operator=(const ShaderModule&) noexcept = default; + ShaderModule(ShaderModule&&) noexcept = default; + ShaderModule& operator=(ShaderModule&&) noexcept = default; + ~ShaderModule() = default; + private: + std::shared_ptr shaderModule; + std::shared_ptr descriptorSetLayout; + }; + +} diff --git a/framegen/include/pool/resourcepool.hpp b/framegen/include/pool/resourcepool.hpp new file mode 100644 index 0000000..729dfb3 --- /dev/null +++ b/framegen/include/pool/resourcepool.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include "core/device.hpp" +#include "core/buffer.hpp" +#include "core/sampler.hpp" + +#include "vulkan/vulkan_core.h" + +#include +#include + +namespace LSFG::Pool { + + /// + /// Resource pool for each Vulkan device. + /// + class ResourcePool { + public: + ResourcePool() noexcept = default; + + /// + /// Create the resource pool. + /// + /// @param isHdr HDR support stored in buffers. + /// @param flowScale Scale factor stored in buffers. + /// + /// @throws std::runtime_error if the resource pool cannot be created. + /// + ResourcePool(bool isHdr, float flowScale) + : isHdr(isHdr), flowScale(flowScale) {} + + /// + /// Retrieve a buffer with given parameters or create it. + /// + /// @param timestamp Timestamp stored in buffer + /// @param firstIter First iteration stored in buffer + /// @param firstIterS First special iteration stored in buffer + /// @return Created or cached buffer + /// + /// @throws LSFG::vulkan_error if the buffer cannot be created. + /// + Core::Buffer getBuffer( + const Core::Device& device, + float timestamp = 0.0F, bool firstIter = false, bool firstIterS = false); + + /// + /// Retrieve a sampler by type or create it. + /// + /// @param type Type of the sampler + /// @param compare Compare operation for the sampler + /// @param isWhite Whether the sampler is white + /// @return Created or cached sampler + /// + /// @throws LSFG::vulkan_error if the sampler cannot be created. + /// + Core::Sampler getSampler( + const Core::Device& device, + VkSamplerAddressMode type = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + VkCompareOp compare = VK_COMPARE_OP_NEVER, + bool isWhite = false); + + private: + std::unordered_map buffers; + std::unordered_map samplers; + bool isHdr{}; + float flowScale{}; + }; + +} diff --git a/framegen/include/pool/shaderpool.hpp b/framegen/include/pool/shaderpool.hpp new file mode 100644 index 0000000..e572154 --- /dev/null +++ b/framegen/include/pool/shaderpool.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "core/device.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace LSFG::Pool { + + /// + /// Shader pool for each Vulkan device. + /// + class ShaderPool { + public: + ShaderPool() noexcept = default; + + /// + /// Create the shader pool. + /// + /// @param source Function to retrieve shader source code by name. + /// + /// @throws std::runtime_error if the shader pool cannot be created. + /// + ShaderPool(const std::function(const std::string&)>& source) + : source(source) {} + + /// + /// Retrieve a shader module by name or create it. + /// + /// @param name Name of the shader module + /// @param types Descriptor types for the shader module + /// @return Shader module + /// + /// @throws LSFG::vulkan_error if the shader module cannot be created. + /// + Core::ShaderModule getShader( + const Core::Device& device, const std::string& name, + const std::vector>& types); + + /// + /// Retrieve a pipeline shader module by name or create it. + /// + /// @param name Name of the shader module + /// @return Pipeline shader module or empty + /// + /// @throws LSFG::vulkan_error if the shader module cannot be created. + /// + Core::Pipeline getPipeline( + const Core::Device& device, const std::string& name); + private: + std::function(const std::string&)> source; + std::unordered_map shaders; + std::unordered_map pipelines; + }; + +} diff --git a/framegen/public/lsfg_3_1.hpp b/framegen/public/lsfg_3_1.hpp new file mode 100644 index 0000000..ae8b86e --- /dev/null +++ b/framegen/public/lsfg_3_1.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace LSFG_3_1 { + + /// + /// Initialize the LSFG library. + /// + /// @param deviceUUID The UUID of the Vulkan device to use. + /// @param isHdr Whether the images are in HDR format. + /// @param flowScale Internal flow scale factor. + /// @param generationCount Number of frames to generate. + /// @param loader Function to load shader source code by name. + /// + /// @throws LSFG::vulkan_error if Vulkan objects fail to initialize. + /// + void initialize(uint64_t deviceUUID, + bool isHdr, float flowScale, uint64_t generationCount, + const std::function(const std::string&)>& loader); + + /// + /// Create a new LSFG context on a swapchain. + /// + /// @param in0 File descriptor for the first input image. + /// @param in1 File descriptor for the second input image. + /// @param outN File descriptor for each output image. This defines the LSFG level. + /// @param extent The size of the images + /// @param format The format of the images. + /// @return A unique identifier for the created context. + /// + /// @throws LSFG::vulkan_error if the context cannot be created. + /// + int32_t createContext( + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format); + + /// + /// Present a context. + /// + /// @param id Unique identifier of the context to present. + /// @param inSem Semaphore to wait on before starting the generation. + /// @param outSem Semaphores to signal once each output image is ready. + /// + /// @throws LSFG::vulkan_error if the context cannot be presented. + /// + void presentContext(int32_t id, int inSem, const std::vector& outSem); + + /// + /// Delete an LSFG context. + /// + /// @param id Unique identifier of the context to delete. + /// + void deleteContext(int32_t id); + + /// + /// Deinitialize the LSFG library. + /// + void finalize(); + +} diff --git a/framegen/public/lsfg_3_1p.hpp b/framegen/public/lsfg_3_1p.hpp new file mode 100644 index 0000000..27417ce --- /dev/null +++ b/framegen/public/lsfg_3_1p.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace LSFG_3_1P { + + /// + /// Initialize the LSFG library. + /// + /// @param deviceUUID The UUID of the Vulkan device to use. + /// @param isHdr Whether the images are in HDR format. + /// @param flowScale Internal flow scale factor. + /// @param generationCount Number of frames to generate. + /// @param loader Function to load shader source code by name. + /// + /// @throws LSFG::vulkan_error if Vulkan objects fail to initialize. + /// + void initialize(uint64_t deviceUUID, + bool isHdr, float flowScale, uint64_t generationCount, + const std::function(const std::string&)>& loader); + + /// + /// Create a new LSFG context on a swapchain. + /// + /// @param in0 File descriptor for the first input image. + /// @param in1 File descriptor for the second input image. + /// @param outN File descriptor for each output image. This defines the LSFG level. + /// @param extent The size of the images + /// @param format The format of the images. + /// @return A unique identifier for the created context. + /// + /// @throws LSFG::vulkan_error if the context cannot be created. + /// + int32_t createContext( + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format); + + /// + /// Present a context. + /// + /// @param id Unique identifier of the context to present. + /// @param inSem Semaphore to wait on before starting the generation. + /// @param outSem Semaphores to signal once each output image is ready. + /// + /// @throws LSFG::vulkan_error if the context cannot be presented. + /// + void presentContext(int32_t id, int inSem, const std::vector& outSem); + + /// + /// Delete an LSFG context. + /// + /// @param id Unique identifier of the context to delete. + /// + void deleteContext(int32_t id); + + /// + /// Deinitialize the LSFG library. + /// + void finalize(); + +} diff --git a/framegen/src/common/exception.cpp b/framegen/src/common/exception.cpp new file mode 100644 index 0000000..5ea7b0d --- /dev/null +++ b/framegen/src/common/exception.cpp @@ -0,0 +1,24 @@ +#include "common/exception.hpp" + +#include + +#include +#include +#include +#include +#include + +using namespace LSFG; + +vulkan_error::vulkan_error(VkResult result, const std::string& message) + : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), + result(result) {} + +vulkan_error::~vulkan_error() noexcept = default; + +rethrowable_error::rethrowable_error(const std::string& message, const std::exception& exe) + : std::runtime_error(message) { + this->message = std::format("{}\n- {}", message, exe.what()); +} + +rethrowable_error::~rethrowable_error() noexcept = default; diff --git a/framegen/src/common/utils.cpp b/framegen/src/common/utils.cpp new file mode 100644 index 0000000..20346c5 --- /dev/null +++ b/framegen/src/common/utils.cpp @@ -0,0 +1,191 @@ +#include +#include + +#include "common/utils.hpp" +#include "core/buffer.hpp" +#include "core/image.hpp" +#include "core/device.hpp" +#include "core/commandpool.hpp" +#include "core/fence.hpp" +#include "common/exception.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace LSFG; +using namespace LSFG::Utils; + +BarrierBuilder& BarrierBuilder::addR2W(Core::Image& image) { + this->barriers.emplace_back(VkImageMemoryBarrier2 { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .srcAccessMask = VK_ACCESS_2_SHADER_READ_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .dstAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT, + .oldLayout = image.getLayout(), + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .image = image.handle(), + .subresourceRange = { + .aspectMask = image.getAspectFlags(), + .levelCount = 1, + .layerCount = 1 + } + }); + image.setLayout(VK_IMAGE_LAYOUT_GENERAL); + + return *this; +} + +BarrierBuilder& BarrierBuilder::addW2R(Core::Image& image) { + this->barriers.emplace_back(VkImageMemoryBarrier2 { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT, + .oldLayout = image.getLayout(), + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .image = image.handle(), + .subresourceRange = { + .aspectMask = image.getAspectFlags(), + .levelCount = 1, + .layerCount = 1 + } + }); + image.setLayout(VK_IMAGE_LAYOUT_GENERAL); + + return *this; +} + +void BarrierBuilder::build() const { + const VkDependencyInfo dependencyInfo = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .imageMemoryBarrierCount = static_cast(this->barriers.size()), + .pImageMemoryBarriers = this->barriers.data() + }; + vkCmdPipelineBarrier2(this->commandBuffer->handle(), &dependencyInfo); +} + +void Utils::uploadImage(const Core::Device& device, const Core::CommandPool& commandPool, + Core::Image& image, const std::string& path) { + // read image bytecode + std::ifstream file(path.data(), std::ios::binary | std::ios::ate); + if (!file.is_open()) + throw std::system_error(errno, std::generic_category(), "Failed to open image: " + path); + + std::streamsize size = file.tellg(); + size -= 124 + 4; // dds header and magic bytes + std::vector code(static_cast(size)); + + file.seekg(124 + 4, std::ios::beg); + if (!file.read(code.data(), size)) + throw std::system_error(errno, std::generic_category(), "Failed to read image: " + path); + + file.close(); + + // copy data to buffer + const Core::Buffer stagingBuffer( + device, code.data(), static_cast(code.size()), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT + ); + + // perform the upload + Core::CommandBuffer commandBuffer(device, commandPool); + commandBuffer.begin(); + + const VkImageMemoryBarrier barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_NONE, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = image.getLayout(), + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = image.handle(), + .subresourceRange = { + .aspectMask = image.getAspectFlags(), + .levelCount = 1, + .layerCount = 1 + } + }; + image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + vkCmdPipelineBarrier( + commandBuffer.handle(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &barrier + ); + + auto extent = image.getExtent(); + const VkBufferImageCopy region{ + .bufferImageHeight = 0, + .imageSubresource = { + .aspectMask = image.getAspectFlags(), + .layerCount = 1 + }, + .imageExtent = { extent.width, extent.height, 1 } + }; + vkCmdCopyBufferToImage( + commandBuffer.handle(), + stagingBuffer.handle(), image.handle(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion + ); + + commandBuffer.end(); + + Core::Fence fence(device); + commandBuffer.submit(device.getComputeQueue(), fence); + + // wait for the upload to complete + if (!fence.wait(device)) + throw LSFG::vulkan_error(VK_TIMEOUT, "Upload operation timed out"); +} + +void Utils::clearImage(const Core::Device& device, Core::Image& image, bool white) { + Core::Fence fence(device); + const Core::CommandPool cmdPool(device); + Core::CommandBuffer cmdBuf(device, cmdPool); + cmdBuf.begin(); + + const VkImageMemoryBarrier2 barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT, + .dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, + .oldLayout = image.getLayout(), + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = image.handle(), + .subresourceRange = { + .aspectMask = image.getAspectFlags(), + .levelCount = 1, + .layerCount = 1 + } + }; + const VkDependencyInfo dependencyInfo = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier + }; + image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + vkCmdPipelineBarrier2(cmdBuf.handle(), &dependencyInfo); + + const float clearValue = white ? 1.0F : 0.0F; + const VkClearColorValue clearColor = {{ clearValue, clearValue, clearValue, clearValue }}; + const VkImageSubresourceRange subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + }; + vkCmdClearColorImage(cmdBuf.handle(), + image.handle(), image.getLayout(), + &clearColor, + 1, &subresourceRange); + + cmdBuf.end(); + + cmdBuf.submit(device.getComputeQueue(), fence); + if (!fence.wait(device)) + throw LSFG::vulkan_error(VK_TIMEOUT, "Failed to wait for clearing fence."); +} diff --git a/framegen/src/core/buffer.cpp b/framegen/src/core/buffer.cpp new file mode 100644 index 0000000..319c829 --- /dev/null +++ b/framegen/src/core/buffer.cpp @@ -0,0 +1,86 @@ +#include +#include + +#include "core/buffer.hpp" +#include "core/device.hpp" +#include "common/exception.hpp" + +#include +#include +#include +#include + +using namespace LSFG::Core; + +void Buffer::construct(const Core::Device& device, const void* data, VkBufferUsageFlags usage) { + // create buffer + const VkBufferCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = this->size, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VkBuffer bufferHandle{}; + auto res = vkCreateBuffer(device.handle(), &desc, nullptr, &bufferHandle); + if (res != VK_SUCCESS || bufferHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create Vulkan buffer"); + + // find memory type + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps); + + VkMemoryRequirements memReqs; + vkGetBufferMemoryRequirements(device.handle(), bufferHandle, &memReqs); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + std::optional memType{}; + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN + (memProps.memoryTypes[i].propertyFlags & + (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))) { + memType.emplace(i); + break; + } // NOLINTEND + } + if (!memType.has_value()) + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for buffer"); +#pragma clang diagnostic pop + + // allocate and bind memory + const VkMemoryAllocateInfo allocInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memReqs.size, + .memoryTypeIndex = memType.value() + }; + VkDeviceMemory memoryHandle{}; + res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle); + if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan buffer"); + + res = vkBindBufferMemory(device.handle(), bufferHandle, memoryHandle, 0); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan buffer"); + + // upload data to buffer + uint8_t* buf{}; + res = vkMapMemory(device.handle(), memoryHandle, 0, this->size, 0, reinterpret_cast(&buf)); + if (res != VK_SUCCESS || buf == nullptr) + throw LSFG::vulkan_error(res, "Failed to map memory for Vulkan buffer"); + std::copy_n(reinterpret_cast(data), this->size, buf); + vkUnmapMemory(device.handle(), memoryHandle); + + // store buffer and memory in shared ptr + this->buffer = std::shared_ptr( + new VkBuffer(bufferHandle), + [dev = device.handle()](VkBuffer* img) { + vkDestroyBuffer(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device.handle()](VkDeviceMemory* mem) { + vkFreeMemory(dev, *mem, nullptr); + } + ); +} diff --git a/framegen/src/core/commandbuffer.cpp b/framegen/src/core/commandbuffer.cpp new file mode 100644 index 0000000..7deea63 --- /dev/null +++ b/framegen/src/core/commandbuffer.cpp @@ -0,0 +1,125 @@ +#include +#include + +#include "core/commandbuffer.hpp" +#include "core/device.hpp" +#include "core/commandpool.hpp" +#include "core/fence.hpp" +#include "core/semaphore.hpp" +#include "common/exception.hpp" + +#include +#include +#include +#include +#include + +using namespace LSFG::Core; + +CommandBuffer::CommandBuffer(const Core::Device& device, const CommandPool& pool) { + // create command buffer + const VkCommandBufferAllocateInfo desc{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = pool.handle(), + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1 + }; + VkCommandBuffer commandBufferHandle{}; + auto res = vkAllocateCommandBuffers(device.handle(), &desc, &commandBufferHandle); + if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to allocate command buffer"); + + // store command buffer in shared ptr + this->state = std::make_shared(CommandBufferState::Empty); + this->commandBuffer = std::shared_ptr( + new VkCommandBuffer(commandBufferHandle), + [dev = device.handle(), pool = pool.handle()](VkCommandBuffer* cmdBuffer) { + vkFreeCommandBuffers(dev, pool, 1, cmdBuffer); + } + ); +} + +void CommandBuffer::begin() { + if (*this->state != CommandBufferState::Empty) + throw std::logic_error("Command buffer is not in Empty state"); + + const VkCommandBufferBeginInfo beginInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; + auto res = vkBeginCommandBuffer(*this->commandBuffer, &beginInfo); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to begin command buffer"); + + *this->state = CommandBufferState::Recording; +} + +void CommandBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z) const { + if (*this->state != CommandBufferState::Recording) + throw std::logic_error("Command buffer is not in Recording state"); + + vkCmdDispatch(*this->commandBuffer, x, y, z); +} + +void CommandBuffer::end() { + if (*this->state != CommandBufferState::Recording) + throw std::logic_error("Command buffer is not in Recording state"); + + auto res = vkEndCommandBuffer(*this->commandBuffer); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to end command buffer"); + + *this->state = CommandBufferState::Full; +} + +void CommandBuffer::submit(VkQueue queue, std::optional fence, + const std::vector& waitSemaphores, + std::optional> waitSemaphoreValues, + const std::vector& signalSemaphores, + std::optional> signalSemaphoreValues) { + if (*this->state != CommandBufferState::Full) + throw std::logic_error("Command buffer is not in Full state"); + + const std::vector waitStages(waitSemaphores.size(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + VkTimelineSemaphoreSubmitInfo timelineInfo{ + .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, + }; + if (waitSemaphoreValues.has_value()) { + timelineInfo.waitSemaphoreValueCount = + static_cast(waitSemaphoreValues->size()); + timelineInfo.pWaitSemaphoreValues = waitSemaphoreValues->data(); + } + if (signalSemaphoreValues.has_value()) { + timelineInfo.signalSemaphoreValueCount = + static_cast(signalSemaphoreValues->size()); + timelineInfo.pSignalSemaphoreValues = signalSemaphoreValues->data(); + } + + std::vector waitSemaphoresHandles; + waitSemaphoresHandles.reserve(waitSemaphores.size()); + for (const auto& semaphore : waitSemaphores) + waitSemaphoresHandles.push_back(semaphore.handle()); + std::vector signalSemaphoresHandles; + signalSemaphoresHandles.reserve(signalSemaphores.size()); + for (const auto& semaphore : signalSemaphores) + signalSemaphoresHandles.push_back(semaphore.handle()); + + const VkSubmitInfo submitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = (waitSemaphoreValues.has_value() || signalSemaphoreValues.has_value()) + ? &timelineInfo : nullptr, + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphoresHandles.data(), + .pWaitDstStageMask = waitStages.data(), + .commandBufferCount = 1, + .pCommandBuffers = &(*this->commandBuffer), + .signalSemaphoreCount = static_cast(signalSemaphores.size()), + .pSignalSemaphores = signalSemaphoresHandles.data() + }; + auto res = vkQueueSubmit(queue, 1, &submitInfo, fence ? fence->handle() : VK_NULL_HANDLE); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to submit command buffer"); + + *this->state = CommandBufferState::Submitted; +} diff --git a/framegen/src/core/commandpool.cpp b/framegen/src/core/commandpool.cpp new file mode 100644 index 0000000..2ff457f --- /dev/null +++ b/framegen/src/core/commandpool.cpp @@ -0,0 +1,30 @@ +#include +#include + +#include "core/commandpool.hpp" +#include "core/device.hpp" +#include "common/exception.hpp" + +#include + +using namespace LSFG::Core; + +CommandPool::CommandPool(const Core::Device& device) { + // create command pool + const VkCommandPoolCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .queueFamilyIndex = device.getComputeFamilyIdx() + }; + VkCommandPool commandPoolHandle{}; + auto res = vkCreateCommandPool(device.handle(), &desc, nullptr, &commandPoolHandle); + if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create command pool"); + + // store command pool in shared ptr + this->commandPool = std::shared_ptr( + new VkCommandPool(commandPoolHandle), + [dev = device.handle()](VkCommandPool* commandPoolHandle) { + vkDestroyCommandPool(dev, *commandPoolHandle, nullptr); + } + ); +} diff --git a/framegen/src/core/descriptorpool.cpp b/framegen/src/core/descriptorpool.cpp new file mode 100644 index 0000000..eea5bb2 --- /dev/null +++ b/framegen/src/core/descriptorpool.cpp @@ -0,0 +1,41 @@ +#include +#include + +#include "core/descriptorpool.hpp" +#include "core/device.hpp" +#include "common/exception.hpp" + +#include +#include +#include + +using namespace LSFG::Core; + +DescriptorPool::DescriptorPool(const Core::Device& device) { + // create descriptor pool + const std::array pools{{ // arbitrary limits + { .type = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = 4096 }, + { .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = 4096 }, + { .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 4096 }, + { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 4096 } + }}; + const VkDescriptorPoolCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + .maxSets = 16384, + .poolSizeCount = static_cast(pools.size()), + .pPoolSizes = pools.data() + }; + VkDescriptorPool poolHandle{}; + auto res = vkCreateDescriptorPool(device.handle(), &desc, nullptr, &poolHandle); + if (res != VK_SUCCESS || poolHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create descriptor pool"); + + // store pool in shared ptr + this->descriptorPool = std::shared_ptr( + new VkDescriptorPool(poolHandle), + [dev = device.handle()](VkDescriptorPool* poolHandle) { + vkDestroyDescriptorPool(dev, *poolHandle, nullptr); + } + ); +} diff --git a/framegen/src/core/descriptorset.cpp b/framegen/src/core/descriptorset.cpp new file mode 100644 index 0000000..9300daa --- /dev/null +++ b/framegen/src/core/descriptorset.cpp @@ -0,0 +1,129 @@ +#include +#include + +#include "core/descriptorset.hpp" +#include "core/device.hpp" +#include "core/descriptorpool.hpp" +#include "core/shadermodule.hpp" +#include "core/commandbuffer.hpp" +#include "core/pipeline.hpp" +#include "core/image.hpp" +#include "core/sampler.hpp" +#include "core/buffer.hpp" +#include "common/exception.hpp" + +#include +#include + +using namespace LSFG::Core; + +DescriptorSet::DescriptorSet(const Core::Device& device, + const DescriptorPool& pool, const ShaderModule& shaderModule) { + // create descriptor set + VkDescriptorSetLayout layout = shaderModule.getLayout(); + const VkDescriptorSetAllocateInfo desc{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = pool.handle(), + .descriptorSetCount = 1, + .pSetLayouts = &layout + }; + VkDescriptorSet descriptorSetHandle{}; + auto res = vkAllocateDescriptorSets(device.handle(), &desc, &descriptorSetHandle); + if (res != VK_SUCCESS || descriptorSetHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to allocate descriptor set"); + + /// store set in shared ptr + this->descriptorSet = std::shared_ptr( + new VkDescriptorSet(descriptorSetHandle), + [dev = device.handle(), pool = pool](VkDescriptorSet* setHandle) { + vkFreeDescriptorSets(dev, pool.handle(), 1, setHandle); + } + ); +} + +DescriptorSetUpdateBuilder DescriptorSet::update(const Core::Device& device) const { + return { *this, device }; +} + +void DescriptorSet::bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const { + VkDescriptorSet descriptorSetHandle = this->handle(); + vkCmdBindDescriptorSets(commandBuffer.handle(), + VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.getLayout(), + 0, 1, &descriptorSetHandle, 0, nullptr); +} + +// updater class + +DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Image& image) { + this->entries.push_back({ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = this->descriptorSet->handle(), + .dstBinding = static_cast(this->entries.size()), + .descriptorCount = 1, + .descriptorType = type, + .pImageInfo = new VkDescriptorImageInfo { + .imageView = image.getView(), + .imageLayout = VK_IMAGE_LAYOUT_GENERAL + }, + .pBufferInfo = nullptr + }); + return *this; +} + +DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Sampler& sampler) { + this->entries.push_back({ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = this->descriptorSet->handle(), + .dstBinding = static_cast(this->entries.size()), + .descriptorCount = 1, + .descriptorType = type, + .pImageInfo = new VkDescriptorImageInfo { + .sampler = sampler.handle(), + }, + .pBufferInfo = nullptr + }); + return *this; +} + +DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Buffer& buffer) { + this->entries.push_back({ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = this->descriptorSet->handle(), + .dstBinding = static_cast(this->entries.size()), + .descriptorCount = 1, + .descriptorType = type, + .pImageInfo = nullptr, + .pBufferInfo = new VkDescriptorBufferInfo { + .buffer = buffer.handle(), + .range = buffer.getSize() + } + }); + return *this; +} + +DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type) { + this->entries.push_back({ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = this->descriptorSet->handle(), + .dstBinding = static_cast(this->entries.size()), + .descriptorCount = 1, + .descriptorType = type, + .pImageInfo = new VkDescriptorImageInfo { + }, + .pBufferInfo = nullptr + }); + return *this; +} + +void DescriptorSetUpdateBuilder::build() { + vkUpdateDescriptorSets(this->device->handle(), + static_cast(this->entries.size()), + this->entries.data(), 0, nullptr); + + // NOLINTBEGIN + for (const auto& entry : this->entries) { + delete entry.pImageInfo; + delete entry.pBufferInfo; + } + // NOLINTEND +} diff --git a/framegen/src/core/device.cpp b/framegen/src/core/device.cpp new file mode 100644 index 0000000..b4474da --- /dev/null +++ b/framegen/src/core/device.cpp @@ -0,0 +1,117 @@ +#include +#include + +#include "core/device.hpp" +#include "core/instance.hpp" +#include "common/exception.hpp" + +#include +#include +#include +#include + +using namespace LSFG::Core; + +const std::vector requiredExtensions = { + "VK_KHR_external_memory_fd", + "VK_KHR_external_semaphore_fd", + "VK_EXT_robustness2", +}; + +Device::Device(const Instance& instance, uint64_t deviceUUID) { + // get all physical devices + uint32_t deviceCount{}; + auto res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, nullptr); + if (res != VK_SUCCESS || deviceCount == 0) + throw LSFG::vulkan_error(res, "Failed to enumerate physical devices"); + + std::vector devices(deviceCount); + res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, devices.data()); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to get physical devices"); + + // get device by uuid + std::optional physicalDevice; + for (const auto& device : devices) { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(device, &properties); + + const uint64_t uuid = + static_cast(properties.vendorID) << 32 | properties.deviceID; + if (deviceUUID == uuid || deviceUUID == 0x1463ABAC) { + physicalDevice = device; + break; + } + } + if (!physicalDevice) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "Could not find physical device with UUID"); + + // find queue family indices + uint32_t familyCount{}; + vkGetPhysicalDeviceQueueFamilyProperties(*physicalDevice, &familyCount, nullptr); + + std::vector queueFamilies(familyCount); + vkGetPhysicalDeviceQueueFamilyProperties(*physicalDevice, &familyCount, queueFamilies.data()); + + std::optional computeFamilyIdx; + for (uint32_t i = 0; i < familyCount; ++i) { + if (queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT) + computeFamilyIdx = i; + } + if (!computeFamilyIdx) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No compute queue family found"); + + // create logical device + const float queuePriority{1.0F}; // highest priority + VkPhysicalDeviceRobustness2FeaturesEXT robustness2{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT, + .nullDescriptor = VK_TRUE, + }; + VkPhysicalDeviceVulkan13Features features13{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, + .pNext = &robustness2, + .synchronization2 = VK_TRUE + }; + const VkPhysicalDeviceVulkan12Features features12{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, + .pNext = &features13, + .timelineSemaphore = VK_TRUE, + .vulkanMemoryModel = VK_TRUE + }; + const VkDeviceQueueCreateInfo computeQueueDesc{ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = *computeFamilyIdx, + .queueCount = 1, + .pQueuePriorities = &queuePriority + }; + const VkDeviceCreateInfo deviceCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &features12, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &computeQueueDesc, + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data() + }; + VkDevice deviceHandle{}; + res = vkCreateDevice(*physicalDevice, &deviceCreateInfo, nullptr, &deviceHandle); + if (res != VK_SUCCESS | deviceHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create logical device"); + + volkLoadDevice(deviceHandle); + + // get compute queue + VkQueue queueHandle{}; + vkGetDeviceQueue(deviceHandle, *computeFamilyIdx, 0, &queueHandle); + + // store in shared ptr + this->computeQueue = queueHandle; + this->computeFamilyIdx = *computeFamilyIdx; + this->physicalDevice = *physicalDevice; + this->device = std::shared_ptr( + new VkDevice(deviceHandle), + [](VkDevice* device) { + vkDestroyDevice(*device, nullptr); + } + ); +} diff --git a/framegen/src/core/fence.cpp b/framegen/src/core/fence.cpp new file mode 100644 index 0000000..0284f08 --- /dev/null +++ b/framegen/src/core/fence.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include "core/fence.hpp" +#include "core/device.hpp" +#include "common/exception.hpp" + +#include +#include + +using namespace LSFG::Core; + +Fence::Fence(const Core::Device& device) { + // create fence + const VkFenceCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO + }; + VkFence fenceHandle{}; + auto res = vkCreateFence(device.handle(), &desc, nullptr, &fenceHandle); + if (res != VK_SUCCESS || fenceHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create fence"); + + // store fence in shared ptr + this->fence = std::shared_ptr( + new VkFence(fenceHandle), + [dev = device.handle()](VkFence* fenceHandle) { + vkDestroyFence(dev, *fenceHandle, nullptr); + } + ); +} + +void Fence::reset(const Core::Device& device) const { + VkFence fenceHandle = this->handle(); + auto res = vkResetFences(device.handle(), 1, &fenceHandle); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to reset fence"); +} + +bool Fence::wait(const Core::Device& device, uint64_t timeout) const { + VkFence fenceHandle = this->handle(); + auto res = vkWaitForFences(device.handle(), 1, &fenceHandle, VK_TRUE, timeout); + if (res != VK_SUCCESS && res != VK_TIMEOUT) + throw LSFG::vulkan_error(res, "Unable to wait for fence"); + + return res == VK_SUCCESS; +} diff --git a/framegen/src/core/image.cpp b/framegen/src/core/image.cpp new file mode 100644 index 0000000..cfbf2e2 --- /dev/null +++ b/framegen/src/core/image.cpp @@ -0,0 +1,242 @@ +#include +#include + +#include "core/image.hpp" +#include "core/device.hpp" +#include "common/exception.hpp" + +#include +#include +#include + +using namespace LSFG::Core; + +Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags) + : extent(extent), format(format), aspectFlags(aspectFlags) { + // create image + const VkImageCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = { + .width = extent.width, + .height = extent.height, + .depth = 1 + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VkImage imageHandle{}; + auto res = vkCreateImage(device.handle(), &desc, nullptr, &imageHandle); + if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create Vulkan image"); + + // find memory type + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps); + + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device.handle(), imageHandle, &memReqs); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + std::optional memType{}; + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN + (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + memType.emplace(i); + break; + } // NOLINTEND + } + if (!memType.has_value()) + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image"); +#pragma clang diagnostic pop + + // allocate and bind memory + const VkMemoryAllocateInfo allocInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memReqs.size, + .memoryTypeIndex = memType.value() + }; + VkDeviceMemory memoryHandle{}; + res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle); + if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image"); + + res = vkBindImageMemory(device.handle(), imageHandle, memoryHandle, 0); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image"); + + // create image view + const VkImageViewCreateInfo viewDesc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = imageHandle, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY + }, + .subresourceRange = { + .aspectMask = aspectFlags, + .levelCount = 1, + .layerCount = 1 + } + }; + + VkImageView viewHandle{}; + res = vkCreateImageView(device.handle(), &viewDesc, nullptr, &viewHandle); + if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create image view"); + + // store objects in shared ptr + this->layout = std::make_shared(VK_IMAGE_LAYOUT_UNDEFINED); + this->image = std::shared_ptr( + new VkImage(imageHandle), + [dev = device.handle()](VkImage* img) { + vkDestroyImage(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device.handle()](VkDeviceMemory* mem) { + vkFreeMemory(dev, *mem, nullptr); + } + ); + this->view = std::shared_ptr( + new VkImageView(viewHandle), + [dev = device.handle()](VkImageView* imgView) { + vkDestroyImageView(dev, *imgView, nullptr); + } + ); +} + +// shared memory constructor + +Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int fd) + : extent(extent), format(format), aspectFlags(aspectFlags) { + // create image + const VkExternalMemoryImageCreateInfo externalInfo{ + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR + }; + const VkImageCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &externalInfo, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = { + .width = extent.width, + .height = extent.height, + .depth = 1 + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VkImage imageHandle{}; + auto res = vkCreateImage(device.handle(), &desc, nullptr, &imageHandle); + if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create Vulkan image"); + + // find memory type + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps); + + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device.handle(), imageHandle, &memReqs); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + std::optional memType{}; + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN + (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + memType.emplace(i); + break; + } // NOLINTEND + } + if (!memType.has_value()) + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image"); +#pragma clang diagnostic pop + + // ~~allocate~~ and bind memory + const VkMemoryDedicatedAllocateInfoKHR dedicatedInfo2{ + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR, + .image = imageHandle, + }; + const VkImportMemoryFdInfoKHR importInfo{ + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, + .pNext = &dedicatedInfo2, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR, + .fd = fd // closes the fd + }; + const VkMemoryAllocateInfo allocInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = fd == -1 ? nullptr : &importInfo, + .allocationSize = memReqs.size, + .memoryTypeIndex = memType.value() + }; + VkDeviceMemory memoryHandle{}; + res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle); + if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image"); + + res = vkBindImageMemory(device.handle(), imageHandle, memoryHandle, 0); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image"); + + // create image view + const VkImageViewCreateInfo viewDesc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = imageHandle, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY + }, + .subresourceRange = { + .aspectMask = aspectFlags, + .levelCount = 1, + .layerCount = 1 + } + }; + + VkImageView viewHandle{}; + res = vkCreateImageView(device.handle(), &viewDesc, nullptr, &viewHandle); + if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create image view"); + + // store objects in shared ptr + this->layout = std::make_shared(VK_IMAGE_LAYOUT_UNDEFINED); + this->image = std::shared_ptr( + new VkImage(imageHandle), + [dev = device.handle()](VkImage* img) { + vkDestroyImage(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device.handle()](VkDeviceMemory* mem) { + vkFreeMemory(dev, *mem, nullptr); + } + ); + this->view = std::shared_ptr( + new VkImageView(viewHandle), + [dev = device.handle()](VkImageView* imgView) { + vkDestroyImageView(dev, *imgView, nullptr); + } + ); +} diff --git a/framegen/src/core/instance.cpp b/framegen/src/core/instance.cpp new file mode 100644 index 0000000..981e634 --- /dev/null +++ b/framegen/src/core/instance.cpp @@ -0,0 +1,49 @@ +#include +#include + +#include "core/instance.hpp" +#include "common/exception.hpp" + +#include +#include +#include + +using namespace LSFG::Core; + +const std::vector requiredExtensions = { + +}; + +Instance::Instance() { + volkInitialize(); + + // create Vulkan instance + const VkApplicationInfo appInfo{ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "lsfg-vk-base", + .applicationVersion = VK_MAKE_VERSION(0, 0, 1), + .pEngineName = "lsfg-vk-base", + .engineVersion = VK_MAKE_VERSION(0, 0, 1), + .apiVersion = VK_API_VERSION_1_3 + }; + const VkInstanceCreateInfo createInfo{ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &appInfo, + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data() + }; + VkInstance instanceHandle{}; + auto res = vkCreateInstance(&createInfo, nullptr, &instanceHandle); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to create Vulkan instance"); + + volkLoadInstance(instanceHandle); + + // store in shared ptr + this->instance = std::shared_ptr( + new VkInstance(instanceHandle), + [](VkInstance* instance) { + vkDestroyInstance(*instance, nullptr); + } + ); +} diff --git a/framegen/src/core/pipeline.cpp b/framegen/src/core/pipeline.cpp new file mode 100644 index 0000000..c2a6375 --- /dev/null +++ b/framegen/src/core/pipeline.cpp @@ -0,0 +1,62 @@ +#include +#include + +#include "core/pipeline.hpp" +#include "core/device.hpp" +#include "core/shadermodule.hpp" +#include "core/commandbuffer.hpp" +#include "common/exception.hpp" + +#include + +using namespace LSFG::Core; + +Pipeline::Pipeline(const Core::Device& device, const ShaderModule& shader) { + // create pipeline layout + VkDescriptorSetLayout shaderLayout = shader.getLayout(); + const VkPipelineLayoutCreateInfo layoutDesc{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = 1, + .pSetLayouts = &shaderLayout, + }; + VkPipelineLayout layoutHandle{}; + auto res = vkCreatePipelineLayout(device.handle(), &layoutDesc, nullptr, &layoutHandle); + if (res != VK_SUCCESS || !layoutHandle) + throw LSFG::vulkan_error(res, "Failed to create pipeline layout"); + + // create pipeline + const VkPipelineShaderStageCreateInfo shaderStageInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, + .module = shader.handle(), + .pName = "main", + }; + const VkComputePipelineCreateInfo pipelineDesc{ + .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + .stage = shaderStageInfo, + .layout = layoutHandle, + }; + VkPipeline pipelineHandle{}; + res = vkCreateComputePipelines(device.handle(), + VK_NULL_HANDLE, 1, &pipelineDesc, nullptr, &pipelineHandle); + if (res != VK_SUCCESS || !pipelineHandle) + throw LSFG::vulkan_error(res, "Failed to create compute pipeline"); + + // store layout and pipeline in shared ptr + this->layout = std::shared_ptr( + new VkPipelineLayout(layoutHandle), + [dev = device.handle()](VkPipelineLayout* layout) { + vkDestroyPipelineLayout(dev, *layout, nullptr); + } + ); + this->pipeline = std::shared_ptr( + new VkPipeline(pipelineHandle), + [dev = device.handle()](VkPipeline* pipeline) { + vkDestroyPipeline(dev, *pipeline, nullptr); + } + ); +} + +void Pipeline::bind(const CommandBuffer& commandBuffer) const { + vkCmdBindPipeline(commandBuffer.handle(), VK_PIPELINE_BIND_POINT_COMPUTE, *this->pipeline); +} diff --git a/framegen/src/core/sampler.cpp b/framegen/src/core/sampler.cpp new file mode 100644 index 0000000..2e8f4eb --- /dev/null +++ b/framegen/src/core/sampler.cpp @@ -0,0 +1,43 @@ +#include +#include + +#include "core/sampler.hpp" +#include "core/device.hpp" +#include "common/exception.hpp" + +#include + +using namespace LSFG::Core; + +Sampler::Sampler(const Core::Device& device, + VkSamplerAddressMode mode, + VkCompareOp compare, + bool isWhite) { + // create sampler + const VkSamplerCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .addressModeU = mode, + .addressModeV = mode, + .addressModeW = mode, + .compareOp = compare, + .maxLod = VK_LOD_CLAMP_NONE, + .borderColor = + isWhite ? VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE + : VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK + }; + VkSampler samplerHandle{}; + auto res = vkCreateSampler(device.handle(), &desc, nullptr, &samplerHandle); + if (res != VK_SUCCESS || samplerHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create sampler"); + + // store sampler in shared ptr + this->sampler = std::shared_ptr( + new VkSampler(samplerHandle), + [dev = device.handle()](VkSampler* samplerHandle) { + vkDestroySampler(dev, *samplerHandle, nullptr); + } + ); +} diff --git a/framegen/src/core/semaphore.cpp b/framegen/src/core/semaphore.cpp new file mode 100644 index 0000000..102898a --- /dev/null +++ b/framegen/src/core/semaphore.cpp @@ -0,0 +1,110 @@ +#include +#include + +#include "core/semaphore.hpp" +#include "core/device.hpp" +#include "common/exception.hpp" + +#include +#include +#include +#include + +using namespace LSFG::Core; + +Semaphore::Semaphore(const Core::Device& device, std::optional initial) { + // create semaphore + const VkSemaphoreTypeCreateInfo typeInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, + .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, + .initialValue = initial.value_or(0) + }; + const VkSemaphoreCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = initial.has_value() ? &typeInfo : nullptr, + }; + VkSemaphore semaphoreHandle{}; + auto res = vkCreateSemaphore(device.handle(), &desc, nullptr, &semaphoreHandle); + if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create semaphore"); + + // store semaphore in shared ptr + this->isTimeline = initial.has_value(); + this->semaphore = std::shared_ptr( + new VkSemaphore(semaphoreHandle), + [dev = device.handle()](VkSemaphore* semaphoreHandle) { + vkDestroySemaphore(dev, *semaphoreHandle, nullptr); + } + ); +} + +Semaphore::Semaphore(const Core::Device& device, int fd) { + // create semaphore + const VkExportSemaphoreCreateInfo exportInfo{ + .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT + }; + const VkSemaphoreCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &exportInfo + }; + VkSemaphore semaphoreHandle{}; + auto res = vkCreateSemaphore(device.handle(), &desc, nullptr, &semaphoreHandle); + if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create semaphore"); + + // import semaphore from fd + auto vkImportSemaphoreFdKHR = reinterpret_cast( + vkGetDeviceProcAddr(device.handle(), "vkImportSemaphoreFdKHR")); + + const VkImportSemaphoreFdInfoKHR importInfo{ + .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, + .semaphore = semaphoreHandle, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, + .fd = fd // closes the fd + }; + res = vkImportSemaphoreFdKHR(device.handle(), &importInfo); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to import semaphore from fd"); + + // store semaphore in shared ptr + this->isTimeline = false; + this->semaphore = std::shared_ptr( + new VkSemaphore(semaphoreHandle), + [dev = device.handle()](VkSemaphore* semaphoreHandle) { + vkDestroySemaphore(dev, *semaphoreHandle, nullptr); + } + ); +} + +void Semaphore::signal(const Core::Device& device, uint64_t value) const { + if (!this->isTimeline) + throw std::logic_error("Invalid timeline semaphore"); + + const VkSemaphoreSignalInfo signalInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO, + .semaphore = this->handle(), + .value = value + }; + auto res = vkSignalSemaphore(device.handle(), &signalInfo); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to signal semaphore"); +} + +bool Semaphore::wait(const Core::Device& device, uint64_t value, uint64_t timeout) const { + if (!this->isTimeline) + throw std::logic_error("Invalid timeline semaphore"); + + VkSemaphore semaphore = this->handle(); + const VkSemaphoreWaitInfo waitInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, + .semaphoreCount = 1, + .pSemaphores = &semaphore, + .pValues = &value + }; + auto res = vkWaitSemaphores(device.handle(), &waitInfo, timeout); + if (res != VK_SUCCESS && res != VK_TIMEOUT) + throw LSFG::vulkan_error(res, "Unable to wait for semaphore"); + + return res == VK_SUCCESS; +} diff --git a/framegen/src/core/shadermodule.cpp b/framegen/src/core/shadermodule.cpp new file mode 100644 index 0000000..92ce4e8 --- /dev/null +++ b/framegen/src/core/shadermodule.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include "core/shadermodule.hpp" +#include "core/device.hpp" +#include "common/exception.hpp" + +#include +#include +#include +#include +#include + +using namespace LSFG::Core; + +ShaderModule::ShaderModule(const Core::Device& device, const std::vector& code, + const std::vector>& descriptorTypes) { + // create shader module + const uint8_t* data_ptr = code.data(); + const VkShaderModuleCreateInfo createInfo{ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = code.size(), + .pCode = reinterpret_cast(data_ptr) + }; + VkShaderModule shaderModuleHandle{}; + auto res = vkCreateShaderModule(device.handle(), &createInfo, nullptr, &shaderModuleHandle); + if (res != VK_SUCCESS || !shaderModuleHandle) + throw LSFG::vulkan_error(res, "Failed to create shader module"); + + // create descriptor set layout + std::vector layoutBindings; + size_t bindIdx = 0; + for (const auto &[count, type] : descriptorTypes) + for (size_t i = 0; i < count; i++, bindIdx++) + layoutBindings.emplace_back(VkDescriptorSetLayoutBinding { + .binding = static_cast(bindIdx), + .descriptorType = type, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT + }); + + const VkDescriptorSetLayoutCreateInfo layoutDesc{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = static_cast(layoutBindings.size()), + .pBindings = layoutBindings.data() + }; + VkDescriptorSetLayout descriptorSetLayout{}; + res = vkCreateDescriptorSetLayout(device.handle(), &layoutDesc, nullptr, &descriptorSetLayout); + if (res != VK_SUCCESS || !descriptorSetLayout) + throw LSFG::vulkan_error(res, "Failed to create descriptor set layout"); + + // store module and layout in shared ptr + this->shaderModule = std::shared_ptr( + new VkShaderModule(shaderModuleHandle), + [dev = device.handle()](VkShaderModule* shaderModuleHandle) { + vkDestroyShaderModule(dev, *shaderModuleHandle, nullptr); + } + ); + this->descriptorSetLayout = std::shared_ptr( + new VkDescriptorSetLayout(descriptorSetLayout), + [dev = device.handle()](VkDescriptorSetLayout* layout) { + vkDestroyDescriptorSetLayout(dev, *layout, nullptr); + } + ); +} diff --git a/framegen/src/pool/resourcepool.cpp b/framegen/src/pool/resourcepool.cpp new file mode 100644 index 0000000..8180945 --- /dev/null +++ b/framegen/src/pool/resourcepool.cpp @@ -0,0 +1,72 @@ +#include "pool/resourcepool.hpp" +#include "core/buffer.hpp" +#include "core/device.hpp" +#include "core/sampler.hpp" + +#include + +#include +#include + +using namespace LSFG; +using namespace LSFG::Pool; + +struct ConstantBuffer { + std::array inputOffset; + uint32_t firstIter; + uint32_t firstIterS; + uint32_t advancedColorKind; + uint32_t hdrSupport; + float resolutionInvScale; + float timestamp; + float uiThreshold; + std::array pad; +}; + +Core::Buffer ResourcePool::getBuffer( + const Core::Device& device, + float timestamp, bool firstIter, bool firstIterS) { + uint64_t hash = 0; + const union { float f; uint32_t i; } u{ + .f = timestamp }; + hash |= u.i; + hash |= static_cast(firstIter) << 32; + hash |= static_cast(firstIterS) << 33; + + auto it = buffers.find(hash); + if (it != buffers.end()) + return it->second; + + // create the buffer + const ConstantBuffer data{ + .inputOffset = { 0, 0 }, + .advancedColorKind = this->isHdr ? 2U : 0U, + .hdrSupport = this->isHdr, + .resolutionInvScale = this->flowScale, + .timestamp = timestamp, + .uiThreshold = 0.5F, + }; + Core::Buffer buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + buffers[hash] = buffer; + return buffer; +} + +Core::Sampler ResourcePool::getSampler( + const Core::Device& device, + VkSamplerAddressMode type, + VkCompareOp compare, + bool isWhite) { + uint64_t hash = 0; + hash |= static_cast(type) << 0; + hash |= static_cast(compare) << 8; + hash |= static_cast(isWhite) << 16; + + auto it = samplers.find(hash); + if (it != samplers.end()) + return it->second; + + // create the sampler + Core::Sampler sampler(device, type, compare, isWhite); + samplers[hash] = sampler; + return sampler; +} diff --git a/framegen/src/pool/shaderpool.cpp b/framegen/src/pool/shaderpool.cpp new file mode 100644 index 0000000..7aaf00b --- /dev/null +++ b/framegen/src/pool/shaderpool.cpp @@ -0,0 +1,48 @@ +#include "pool/shaderpool.hpp" +#include "core/shadermodule.hpp" +#include "core/device.hpp" +#include "core/pipeline.hpp" + +#include + +#include +#include +#include +#include +#include + +using namespace LSFG; +using namespace LSFG::Pool; + +Core::ShaderModule ShaderPool::getShader( + const Core::Device& device, const std::string& name, + const std::vector>& types) { + auto it = shaders.find(name); + if (it != shaders.end()) + return it->second; + + // grab the shader + auto bytecode = this->source(name); + if (bytecode.empty()) + throw std::runtime_error("Shader code is empty: " + name); + + // create the shader module + Core::ShaderModule shader(device, bytecode, types); + shaders[name] = shader; + return shader; +} + +Core::Pipeline ShaderPool::getPipeline( + const Core::Device& device, const std::string& name) { + auto it = pipelines.find(name); + if (it != pipelines.end()) + return it->second; + + // grab the shader module + auto shader = this->getShader(device, name, {}); + + // create the pipeline + Core::Pipeline pipeline(device, shader); + pipelines[name] = pipeline; + return pipeline; +} diff --git a/framegen/v3.1_include/v3_1/context.hpp b/framegen/v3.1_include/v3_1/context.hpp new file mode 100644 index 0000000..16e3ffa --- /dev/null +++ b/framegen/v3.1_include/v3_1/context.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "core/image.hpp" +#include "core/semaphore.hpp" +#include "core/fence.hpp" +#include "core/commandbuffer.hpp" +#include "shaders/alpha.hpp" +#include "shaders/beta.hpp" +#include "shaders/delta.hpp" +#include "shaders/gamma.hpp" +#include "shaders/generate.hpp" +#include "shaders/mipmaps.hpp" +#include "common/utils.hpp" + +#include + +#include +#include +#include + +namespace LSFG_3_1 { + + using namespace LSFG; + + class Context { + public: + /// + /// Create a context + /// + /// @param vk The Vulkan instance to use. + /// @param in0 File descriptor for the first input image. + /// @param in1 File descriptor for the second input image. + /// @param outN File descriptors for the output images. + /// @param extent The size of the images. + /// @param format The format of the images. + /// + /// @throws LSFG::vulkan_error if the context fails to initialize. + /// + Context(Vulkan& vk, + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format); + + /// + /// Present on the context. + /// + /// @param inSem Semaphore to wait on before starting the generation. + /// @param outSem Semaphores to signal after each generation is done. + /// + /// @throws LSFG::vulkan_error if the context fails to present. + /// + void present(Vulkan& vk, + int inSem, const std::vector& outSem); + + // Trivially copyable, moveable and destructible + Context(const Context&) = default; + Context& operator=(const Context&) = default; + Context(Context&&) = default; + Context& operator=(Context&&) = default; + ~Context() = default; + private: + Core::Image inImg_0, inImg_1; // inImg_0 is next when fc % 2 == 0 + uint64_t frameIdx{0}; + + struct RenderData { + Core::Semaphore inSemaphore; // signaled when input is ready + std::vector internalSemaphores; // signaled when first step is done + std::vector outSemaphores; // signaled when each pass is done + std::vector completionFences; // fence for completion of each pass + + Core::CommandBuffer cmdBuffer1; + std::vector cmdBuffers2; // command buffers for second step + + bool shouldWait{false}; + }; + std::array data; + + Shaders::Mipmaps mipmaps; + std::array alpha; + Shaders::Beta beta; + std::array gamma; + std::array delta; + Shaders::Generate generate; + }; + +} diff --git a/framegen/v3.1_include/v3_1/shaders/alpha.hpp b/framegen/v3.1_include/v3_1/shaders/alpha.hpp new file mode 100644 index 0000000..11e516b --- /dev/null +++ b/framegen/v3.1_include/v3_1/shaders/alpha.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include + +namespace LSFG_3_1::Shaders { + + using namespace LSFG; + + /// + /// Alpha shader. + /// + class Alpha { + public: + Alpha() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg One mipmap level + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Alpha(Vulkan& vk, Core::Image inImg); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Alpha(const Alpha&) noexcept = default; + Alpha& operator=(const Alpha&) noexcept = default; + Alpha(Alpha&&) noexcept = default; + Alpha& operator=(Alpha&&) noexcept = default; + ~Alpha() = default; + private: + std::array shaderModules; + std::array pipelines; + Core::Sampler sampler; + std::array descriptorSets; + std::array lastDescriptorSet; + + Core::Image inImg; + std::array tempImgs1; + std::array tempImgs2; + std::array tempImgs3; + std::array, 3> outImgs; + }; + +} diff --git a/framegen/v3.1_include/v3_1/shaders/beta.hpp b/framegen/v3.1_include/v3_1/shaders/beta.hpp new file mode 100644 index 0000000..58fd974 --- /dev/null +++ b/framegen/v3.1_include/v3_1/shaders/beta.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include + +namespace LSFG_3_1::Shaders { + + using namespace LSFG; + + /// + /// Beta shader. + /// + class Beta { + public: + Beta() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs Three sets of four RGBA images, corresponding to a frame count % 3. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Beta(Vulkan& vk, std::array, 3> inImgs); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Beta(const Beta&) noexcept = default; + Beta& operator=(const Beta&) noexcept = default; + Beta(Beta&&) noexcept = default; + Beta& operator=(Beta&&) noexcept = default; + ~Beta() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + + std::array, 3> inImgs; + std::array tempImgs1; + std::array tempImgs2; + std::array outImgs; + }; + +} diff --git a/framegen/v3.1_include/v3_1/shaders/delta.hpp b/framegen/v3.1_include/v3_1/shaders/delta.hpp new file mode 100644 index 0000000..e671223 --- /dev/null +++ b/framegen/v3.1_include/v3_1/shaders/delta.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include +#include +#include + +namespace LSFG_3_1::Shaders { + + using namespace LSFG; + + /// + /// Delta shader. + /// + class Delta { + public: + Delta() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs1 Three sets of four RGBA images, corresponding to a frame count % 3. + /// @param inImg2 Second Input image + /// @param optImg1 Optional image for non-first passes. + /// @param optImg2 Second optional image for non-first passes. + /// @param optImg3 Third optional image for non-first passes. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Delta(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg1, + std::optional optImg2, + std::optional optImg3); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Get the first output image + [[nodiscard]] const auto& getOutImage1() const { return this->outImg1; } + /// Get the second output image + [[nodiscard]] const auto& getOutImage2() const { return this->outImg2; } + + /// Trivially copyable, moveable and destructible + Delta(const Delta&) noexcept = default; + Delta& operator=(const Delta&) noexcept = default; + Delta(Delta&&) noexcept = default; + Delta& operator=(Delta&&) noexcept = default; + ~Delta() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + struct DeltaPass { + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + std::array sixthDescriptorSet; + }; + std::vector passes; + + std::array, 3> inImgs1; + Core::Image inImg2; + std::optional optImg1, optImg2, optImg3; + std::array tempImgs1; + std::array tempImgs2; + Core::Image outImg1, outImg2; + }; + +} diff --git a/framegen/v3.1_include/v3_1/shaders/gamma.hpp b/framegen/v3.1_include/v3_1/shaders/gamma.hpp new file mode 100644 index 0000000..f3b4f73 --- /dev/null +++ b/framegen/v3.1_include/v3_1/shaders/gamma.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include +#include +#include + +namespace LSFG_3_1::Shaders { + + using namespace LSFG; + + /// + /// Gamma shader. + /// + class Gamma { + public: + Gamma() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs1 Three sets of four RGBA images, corresponding to a frame count % 3. + /// @param inImg2 Second Input image + /// @param optImg Optional image for non-first passes. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Gamma(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, std::optional optImg); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Get the output image + [[nodiscard]] const auto& getOutImage() const { return this->outImg; } + + /// Trivially copyable, moveable and destructible + Gamma(const Gamma&) noexcept = default; + Gamma& operator=(const Gamma&) noexcept = default; + Gamma(Gamma&&) noexcept = default; + Gamma& operator=(Gamma&&) noexcept = default; + ~Gamma() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + struct GammaPass { + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + }; + std::vector passes; + + std::array, 3> inImgs1; + Core::Image inImg2; + std::optional optImg; + std::array tempImgs1; + std::array tempImgs2; + Core::Image outImg; + }; + +} diff --git a/framegen/v3.1_include/v3_1/shaders/generate.hpp b/framegen/v3.1_include/v3_1/shaders/generate.hpp new file mode 100644 index 0000000..5d63ef2 --- /dev/null +++ b/framegen/v3.1_include/v3_1/shaders/generate.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include + +#include +#include +#include + +namespace LSFG_3_1::Shaders { + + using namespace LSFG; + + /// + /// Generate shader. + /// + class Generate { + public: + Generate() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg1 Input image 1. + /// @param inImg2 Input image 2. + /// @param inImg3 Input image 3. + /// @param inImg4 Input image 4. + /// @param inImg5 Input image 5. + /// @param fds File descriptors for the output images. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Generate(Vulkan& vk, + Core::Image inImg1, Core::Image inImg2, + Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, + const std::vector& fds, VkFormat format); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Trivially copyable, moveable and destructible + Generate(const Generate&) noexcept = default; + Generate& operator=(const Generate&) noexcept = default; + Generate(Generate&&) noexcept = default; + Generate& operator=(Generate&&) noexcept = default; + ~Generate() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + std::array samplers; + struct GeneratePass { + Core::Buffer buffer; + std::array descriptorSet; + }; + std::vector passes; + + Core::Image inImg1, inImg2; + Core::Image inImg3, inImg4, inImg5; + std::vector outImgs; + }; + +} diff --git a/framegen/v3.1_include/v3_1/shaders/mipmaps.hpp b/framegen/v3.1_include/v3_1/shaders/mipmaps.hpp new file mode 100644 index 0000000..e3595b4 --- /dev/null +++ b/framegen/v3.1_include/v3_1/shaders/mipmaps.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include + +namespace LSFG_3_1::Shaders { + + using namespace LSFG; + + /// + /// Mipmaps shader. + /// + class Mipmaps { + public: + Mipmaps() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg_0 The next frame (when fc % 2 == 0) + /// @param inImg_1 The next frame (when fc % 2 == 1) + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Mipmaps(Vulkan& vk, Core::Image inImg_0, Core::Image inImg_1); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images. + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Mipmaps(const Mipmaps&) noexcept = default; + Mipmaps& operator=(const Mipmaps&) noexcept = default; + Mipmaps(Mipmaps&&) noexcept = default; + Mipmaps& operator=(Mipmaps&&) noexcept = default; + ~Mipmaps() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + Core::Buffer buffer; + Core::Sampler sampler; + std::array descriptorSets; + + Core::Image inImg_0, inImg_1; + std::array outImgs; + }; + +} diff --git a/framegen/v3.1_src/context.cpp b/framegen/v3.1_src/context.cpp new file mode 100644 index 0000000..4984842 --- /dev/null +++ b/framegen/v3.1_src/context.cpp @@ -0,0 +1,122 @@ +#include +#include + +#include "v3_1/context.hpp" +#include "common/utils.hpp" +#include "common/exception.hpp" + +#include +#include +#include +#include +#include + +using namespace LSFG_3_1; + +Context::Context(Vulkan& vk, + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format) { + // import input images + this->inImg_0 = Core::Image(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, in0); + this->inImg_1 = Core::Image(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, in1); + + // prepare render data + for (size_t i = 0; i < 8; i++) { + auto& data = this->data.at(i); + data.internalSemaphores.resize(vk.generationCount); + data.outSemaphores.resize(vk.generationCount); + data.completionFences.resize(vk.generationCount); + data.cmdBuffers2.resize(vk.generationCount); + } + + // create shader chains + this->mipmaps = Shaders::Mipmaps(vk, this->inImg_0, this->inImg_1); + for (size_t i = 0; i < 7; i++) + this->alpha.at(i) = Shaders::Alpha(vk, this->mipmaps.getOutImages().at(i)); + this->beta = Shaders::Beta(vk, this->alpha.at(0).getOutImages()); + for (size_t i = 0; i < 7; i++) { + this->gamma.at(i) = Shaders::Gamma(vk, + this->alpha.at(6 - i).getOutImages(), + this->beta.getOutImages().at(std::min(6 - i, 5)), + (i == 0) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage())); + if (i < 4) continue; + + this->delta.at(i - 4) = Shaders::Delta(vk, + this->alpha.at(6 - i).getOutImages(), + this->beta.getOutImages().at(6 - i), + (i == 4) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()), + (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage1()), + (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage2())); + } + this->generate = Shaders::Generate(vk, + this->inImg_0, this->inImg_1, + this->gamma.at(6).getOutImage(), + this->delta.at(2).getOutImage1(), + this->delta.at(2).getOutImage2(), + outN, format); +} + +void Context::present(Vulkan& vk, + int inSem, const std::vector& outSem) { + auto& data = this->data.at(this->frameIdx % 8); + + // 3. wait for completion of previous frame in this slot + if (data.shouldWait) + for (auto& fence : data.completionFences) + if (!fence.wait(vk.device, UINT64_MAX)) + throw LSFG::vulkan_error(VK_TIMEOUT, "Fence wait timed out"); + data.shouldWait = true; + + // 1. create mipmaps and process input image + if (inSem >= 0) data.inSemaphore = Core::Semaphore(vk.device, inSem); + for (size_t i = 0; i < vk.generationCount; i++) + data.internalSemaphores.at(i) = Core::Semaphore(vk.device); + + data.cmdBuffer1 = Core::CommandBuffer(vk.device, vk.commandPool); + data.cmdBuffer1.begin(); + + this->mipmaps.Dispatch(data.cmdBuffer1, this->frameIdx); + for (size_t i = 0; i < 7; i++) + this->alpha.at(6 - i).Dispatch(data.cmdBuffer1, this->frameIdx); + this->beta.Dispatch(data.cmdBuffer1, this->frameIdx); + + data.cmdBuffer1.end(); + std::vector waits = { data.inSemaphore }; + if (inSem < 0) waits.clear(); + data.cmdBuffer1.submit(vk.device.getComputeQueue(), std::nullopt, + waits, std::nullopt, + data.internalSemaphores, std::nullopt); + + // 2. generate intermediary frames + for (size_t pass = 0; pass < vk.generationCount; pass++) { + auto& internalSemaphore = data.internalSemaphores.at(pass); + auto& outSemaphore = data.outSemaphores.at(pass); + if (inSem >= 0) outSemaphore = Core::Semaphore(vk.device, outSem.empty() ? -1 : outSem.at(pass)); + auto& completionFence = data.completionFences.at(pass); + completionFence = Core::Fence(vk.device); + + auto& buf2 = data.cmdBuffers2.at(pass); + buf2 = Core::CommandBuffer(vk.device, vk.commandPool); + buf2.begin(); + + for (size_t i = 0; i < 7; i++) { + this->gamma.at(i).Dispatch(buf2, this->frameIdx, pass); + if (i >= 4) + this->delta.at(i - 4).Dispatch(buf2, this->frameIdx, pass); + } + this->generate.Dispatch(buf2, this->frameIdx, pass); + + buf2.end(); + std::vector signals = { outSemaphore }; + if (inSem < 0) signals.clear(); + buf2.submit(vk.device.getComputeQueue(), completionFence, + { internalSemaphore }, std::nullopt, + signals, std::nullopt); + } + + this->frameIdx++; +} diff --git a/framegen/v3.1_src/lsfg.cpp b/framegen/v3.1_src/lsfg.cpp new file mode 100644 index 0000000..a894f12 --- /dev/null +++ b/framegen/v3.1_src/lsfg.cpp @@ -0,0 +1,97 @@ +#include +#include + +#include "lsfg_3_1.hpp" +#include "v3_1/context.hpp" +#include "core/commandpool.hpp" +#include "core/descriptorpool.hpp" +#include "core/instance.hpp" +#include "pool/shaderpool.hpp" +#include "common/exception.hpp" +#include "common/utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace LSFG; +using namespace LSFG_3_1; + +namespace { + std::optional instance; + std::optional device; + std::unordered_map contexts; +} + +void LSFG_3_1::initialize(uint64_t deviceUUID, + bool isHdr, float flowScale, uint64_t generationCount, + const std::function(const std::string&)>& loader) { + if (instance.has_value() || device.has_value()) + return; + + instance.emplace(); + device.emplace(Vulkan { + .device{*instance, deviceUUID}, + .generationCount = generationCount, + .flowScale = flowScale, + .isHdr = isHdr + }); + contexts = std::unordered_map(); + + device->commandPool = Core::CommandPool(device->device); + device->descriptorPool = Core::DescriptorPool(device->device); + + device->resources = Pool::ResourcePool(device->isHdr, device->flowScale); + device->shaders = Pool::ShaderPool(loader); + + std::srand(static_cast(std::time(nullptr))); +} + +int32_t LSFG_3_1::createContext( + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format) { + if (!instance.has_value() || !device.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + const int32_t id = std::rand(); + contexts.emplace(id, Context(*device, in0, in1, outN, extent, format)); + return id; +} + +void LSFG_3_1::presentContext(int32_t id, int inSem, const std::vector& outSem) { + if (!instance.has_value() || !device.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + auto it = contexts.find(id); + if (it == contexts.end()) + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Context not found"); + + it->second.present(*device, inSem, outSem); +} + +void LSFG_3_1::deleteContext(int32_t id) { + if (!instance.has_value() || !device.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + auto it = contexts.find(id); + if (it == contexts.end()) + throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context"); + + vkDeviceWaitIdle(device->device.handle()); + contexts.erase(it); +} + +void LSFG_3_1::finalize() { + if (!instance.has_value() || !device.has_value()) + return; + + vkDeviceWaitIdle(device->device.handle()); + contexts.clear(); + device.reset(); + instance.reset(); +} diff --git a/framegen/v3.1_src/shaders/alpha.cpp b/framegen/v3.1_src/shaders/alpha.cpp new file mode 100644 index 0000000..1d36b1d --- /dev/null +++ b/framegen/v3.1_src/shaders/alpha.cpp @@ -0,0 +1,140 @@ +#include +#include + +#include "v3_1/shaders/alpha.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include +#include +#include + +using namespace LSFG_3_1::Shaders; + +Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "alpha[0]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "alpha[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "alpha[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "alpha[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "alpha[0]"), + vk.shaders.getPipeline(vk.device, "alpha[1]"), + vk.shaders.getPipeline(vk.device, "alpha[2]"), + vk.shaders.getPipeline(vk.device, "alpha[3]") + }}; + this->sampler = vk.resources.getSampler(vk.device); + for (size_t i = 0; i < 3; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i)); + for (size_t i = 0; i < 3; i++) + this->lastDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(3)); + + // create internal images/outputs + const VkExtent2D extent = this->inImg.getExtent(); + const VkExtent2D halfExtent = { + .width = (extent.width + 1) >> 1, + .height = (extent.height + 1) >> 1 + }; + for (size_t i = 0; i < 2; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, halfExtent); + this->tempImgs2.at(i) = Core::Image(vk.device, halfExtent); + } + + const VkExtent2D quarterExtent = { + .width = (halfExtent.width + 1) >> 1, + .height = (halfExtent.height + 1) >> 1 + }; + for (size_t i = 0; i < 4; i++) { + this->tempImgs3.at(i) = Core::Image(vk.device, quarterExtent); + for (size_t j = 0; j < 3; j++) + this->outImgs.at(j).at(i) = Core::Image(vk.device, quarterExtent); + } + + // hook up shaders + this->descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + this->descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs3) + .build(); + for (size_t i = 0; i < 3; i++) + this->lastDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i)) + .build(); +} + +void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto halfExtent = this->tempImgs1.at(0).getExtent(); + uint32_t threadsX = (halfExtent.width + 7) >> 3; + uint32_t threadsY = (halfExtent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImg) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(0).bind(buf); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third pass + const auto quarterExtent = this->tempImgs3.at(0).getExtent(); + threadsX = (quarterExtent.width + 7) >> 3; + threadsY = (quarterExtent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs3) + .build(); + + this->pipelines.at(2).bind(buf); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs3) + .addR2W(this->outImgs.at(frameCount % 3)) + .build(); + + this->pipelines.at(3).bind(buf); + this->lastDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1_src/shaders/beta.cpp b/framegen/v3.1_src/shaders/beta.cpp new file mode 100644 index 0000000..b61a005 --- /dev/null +++ b/framegen/v3.1_src/shaders/beta.cpp @@ -0,0 +1,162 @@ +#include +#include + +#include "v3_1/shaders/beta.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include +#include +#include +#include + +using namespace LSFG_3_1::Shaders; + +Beta::Beta(Vulkan& vk, std::array, 3> inImgs) + : inImgs(std::move(inImgs)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "beta[0]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 12, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "beta[0]"), + vk.shaders.getPipeline(vk.device, "beta[1]"), + vk.shaders.getPipeline(vk.device, "beta[2]"), + vk.shaders.getPipeline(vk.device, "beta[3]"), + vk.shaders.getPipeline(vk.device, "beta[4]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + for (size_t i = 0; i < 3; i++) + this->firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(0)); + for (size_t i = 0; i < 4; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i + 1)); + this->buffer = vk.resources.getBuffer(vk.device, 0.5F); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs.at(0).at(0).getExtent(); + for (size_t i = 0; i < 2; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + } + + for (size_t i = 0; i < 6; i++) + this->outImgs.at(i) = Core::Image(vk.device, + { extent.width >> i, extent.height >> i }, + VK_FORMAT_R8_UNORM); + + // hook up shaders + for (size_t i = 0; i < 3; i++) { + this->firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 1) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + } + this->descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + this->descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .build(); +} + +void Beta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto extent = this->tempImgs1.at(0).getExtent(); + uint32_t threadsX = (extent.width + 7) >> 3; + uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs.at(0)) + .addW2R(this->inImgs.at(1)) + .addW2R(this->inImgs.at(2)) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(0).bind(buf); + this->firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth pass + threadsX = (extent.width + 31) >> 5; + threadsY = (extent.height + 31) >> 5; + + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->outImgs) + .build(); + + this->pipelines.at(4).bind(buf); + this->descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1_src/shaders/delta.cpp b/framegen/v3.1_src/shaders/delta.cpp new file mode 100644 index 0000000..bcafbf7 --- /dev/null +++ b/framegen/v3.1_src/shaders/delta.cpp @@ -0,0 +1,341 @@ +#include +#include + +#include "v3_1/shaders/delta.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include +#include +#include +#include +#include + +using namespace LSFG_3_1::Shaders; + +Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg1, + std::optional optImg2, + std::optional optImg3) + : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), + optImg1(std::move(optImg1)), optImg2(std::move(optImg2)), + optImg3(std::move(optImg3)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "delta[0]", + { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 9, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[5]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 10, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[6]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[7]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[8]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[9]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "delta[0]"), + vk.shaders.getPipeline(vk.device, "delta[1]"), + vk.shaders.getPipeline(vk.device, "delta[2]"), + vk.shaders.getPipeline(vk.device, "delta[3]"), + vk.shaders.getPipeline(vk.device, "delta[4]"), + vk.shaders.getPipeline(vk.device, "delta[5]"), + vk.shaders.getPipeline(vk.device, "delta[6]"), + vk.shaders.getPipeline(vk.device, "delta[7]"), + vk.shaders.getPipeline(vk.device, "delta[8]"), + vk.shaders.getPipeline(vk.device, "delta[9]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + this->samplers.at(2) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); + for (size_t i = 0; i < 4; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + } + + this->outImg1 = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + this->outImg2 = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + + // hook up shaders + for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(pass_idx + 1) / static_cast(vk.generationCount + 1), + false, !this->optImg1.has_value()); + for (size_t i = 0; i < 3; i++) { + pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(0)); + pass.firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(2)) + .build(); + } + pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(1)); + pass.descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(2)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(2)); + pass.descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(3)); + pass.descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(4)); + pass.descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1) + .build(); + for (size_t i = 0; i < 3; i++) { + pass.sixthDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(5)); + pass.sixthDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(1)) + .build(); + } + pass.descriptorSets.at(4) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(6)); + pass.descriptorSets.at(4).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .build(); + pass.descriptorSets.at(5) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(7)); + pass.descriptorSets.at(5).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(1)) + .build(); + pass.descriptorSets.at(6) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(8)); + pass.descriptorSets.at(6).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .build(); + pass.descriptorSets.at(7) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(9)); + pass.descriptorSets.at(7).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2) + .build(); + } +} + +void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first shader + const auto extent = this->tempImgs1.at(0).getExtent(); + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg1) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .addR2W(this->tempImgs1.at(2)) + .build(); + + this->pipelines.at(0).bind(buf); + pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addW2R(this->tempImgs1.at(2)) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addW2R(this->optImg1) + .addW2R(this->inImg2) + .addR2W(this->outImg1) + .build(); + + this->pipelines.at(4).bind(buf); + pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); + + // sixth shader + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg1) + .addW2R(this->optImg2) + .addR2W(this->tempImgs2.at(0)) + .addR2W(this->tempImgs2.at(1)) + .build(); + + this->pipelines.at(5).bind(buf); + pass.sixthDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(5)); + buf.dispatch(threadsX, threadsY, 1); + + // seventh shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2.at(0)) + .addW2R(this->tempImgs2.at(1)) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .build(); + + this->pipelines.at(6).bind(buf); + pass.descriptorSets.at(4).bind(buf, this->pipelines.at(6)); + buf.dispatch(threadsX, threadsY, 1); + + // eighth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addR2W(this->tempImgs2.at(0)) + .addR2W(this->tempImgs2.at(1)) + .build(); + this->pipelines.at(7).bind(buf); + pass.descriptorSets.at(5).bind(buf, this->pipelines.at(7)); + buf.dispatch(threadsX, threadsY, 1); + + // ninth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2.at(0)) + .addW2R(this->tempImgs2.at(1)) + .addW2R(this->optImg3) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .build(); + + this->pipelines.at(8).bind(buf); + pass.descriptorSets.at(6).bind(buf, this->pipelines.at(8)); + buf.dispatch(threadsX, threadsY, 1); + + // tenth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addW2R(this->optImg3) + .addR2W(this->outImg2) + .build(); + + this->pipelines.at(9).bind(buf); + pass.descriptorSets.at(7).bind(buf, this->pipelines.at(9)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1_src/shaders/gamma.cpp b/framegen/v3.1_src/shaders/gamma.cpp new file mode 100644 index 0000000..a5b9ff8 --- /dev/null +++ b/framegen/v3.1_src/shaders/gamma.cpp @@ -0,0 +1,193 @@ +#include +#include + +#include "v3_1/shaders/gamma.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include +#include +#include +#include +#include + +using namespace LSFG_3_1::Shaders; + +Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg) + : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), + optImg(std::move(optImg)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "gamma[0]", + { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 9, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "gamma[0]"), + vk.shaders.getPipeline(vk.device, "gamma[1]"), + vk.shaders.getPipeline(vk.device, "gamma[2]"), + vk.shaders.getPipeline(vk.device, "gamma[3]"), + vk.shaders.getPipeline(vk.device, "gamma[4]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + this->samplers.at(2) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); + for (size_t i = 0; i < 4; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + } + + this->outImg = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + + // hook up shaders + for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(pass_idx + 1) / static_cast(vk.generationCount + 1), + !this->optImg.has_value()); + for (size_t i = 0; i < 3; i++) { + pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(0)); + pass.firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(2)) + .build(); + } + pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(1)); + pass.descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(2)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(2)); + pass.descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(3)); + pass.descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(4)); + pass.descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .build(); + } +} + +void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first shader + const auto extent = this->tempImgs1.at(0).getExtent(); + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .addR2W(this->tempImgs1.at(2)) + .build(); + + this->pipelines.at(0).bind(buf); + pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addW2R(this->tempImgs1.at(2)) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addW2R(this->optImg) + .addW2R(this->inImg2) + .addR2W(this->outImg) + .build(); + + this->pipelines.at(4).bind(buf); + pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1_src/shaders/generate.cpp b/framegen/v3.1_src/shaders/generate.cpp new file mode 100644 index 0000000..44af35a --- /dev/null +++ b/framegen/v3.1_src/shaders/generate.cpp @@ -0,0 +1,83 @@ +#include +#include + +#include "v3_1/shaders/generate.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include +#include +#include +#include + +using namespace LSFG_3_1::Shaders; + +Generate::Generate(Vulkan& vk, + Core::Image inImg1, Core::Image inImg2, + Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, + const std::vector& fds, VkFormat format) + : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)), + inImg3(std::move(inImg3)), inImg4(std::move(inImg4)), + inImg5(std::move(inImg5)) { + // create resources + this->shaderModule = vk.shaders.getShader(vk.device, "generate", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); + this->pipeline = vk.shaders.getPipeline(vk.device, "generate"); + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS); + + // create internal images/outputs + const VkExtent2D extent = this->inImg1.getExtent(); + for (size_t i = 0; i < vk.generationCount; i++) + this->outImgs.emplace_back(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, fds.empty() ? -1 : fds.at(i)); + + // hook up shaders + for (size_t i = 0; i < vk.generationCount; i++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(i + 1) / static_cast(vk.generationCount + 1)); + for (size_t j = 0; j < 2; j++) { + pass.descriptorSet.at(j) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModule); + pass.descriptorSet.at(j).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg2 : this->inImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg1 : this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg4) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg5) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i)) + .build(); + } + } +} + +void Generate::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first pass + const auto extent = this->inImg1.getExtent(); + const uint32_t threadsX = (extent.width + 15) >> 4; + const uint32_t threadsY = (extent.height + 15) >> 4; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImg1) + .addW2R(this->inImg2) + .addW2R(this->inImg3) + .addW2R(this->inImg4) + .addW2R(this->inImg5) + .addR2W(this->outImgs.at(pass_idx)) + .build(); + + this->pipeline.bind(buf); + pass.descriptorSet.at(frameCount % 2).bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1_src/shaders/mipmaps.cpp b/framegen/v3.1_src/shaders/mipmaps.cpp new file mode 100644 index 0000000..ca7605b --- /dev/null +++ b/framegen/v3.1_src/shaders/mipmaps.cpp @@ -0,0 +1,66 @@ +#include +#include + +#include "v3_1/shaders/mipmaps.hpp" +#include "common/utils.hpp" +#include "core/image.hpp" +#include "core/commandbuffer.hpp" + +#include +#include +#include + +using namespace LSFG_3_1::Shaders; + +Mipmaps::Mipmaps(Vulkan& vk, + Core::Image inImg_0, Core::Image inImg_1) + : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) { + // create resources + this->shaderModule = vk.shaders.getShader(vk.device, "mipmaps", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); + this->pipeline = vk.shaders.getPipeline(vk.device, "mipmaps"); + this->buffer = vk.resources.getBuffer(vk.device); + this->sampler = vk.resources.getSampler(vk.device); + for (size_t i = 0; i < 2; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModule); + + // create outputs + const VkExtent2D flowExtent{ + .width = static_cast( + static_cast(this->inImg_0.getExtent().width) / vk.flowScale), + .height = static_cast( + static_cast(this->inImg_0.getExtent().height) / vk.flowScale) + }; + for (size_t i = 0; i < 7; i++) + this->outImgs.at(i) = Core::Image(vk.device, + { flowExtent.width >> i, flowExtent.height >> i }, + VK_FORMAT_R8_UNORM); + + // hook up shaders + for (size_t fc = 0; fc < 2; fc++) + this->descriptorSets.at(fc).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg_0 : this->inImg_1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .build(); +} + +void Mipmaps::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto flowExtent = this->outImgs.at(0).getExtent(); + const uint32_t threadsX = (flowExtent.width + 63) >> 6; + const uint32_t threadsY = (flowExtent.height + 63) >> 6; + + Utils::BarrierBuilder(buf) + .addW2R((frameCount % 2 == 0) ? this->inImg_0 : this->inImg_1) + .addR2W(this->outImgs) + .build(); + + this->pipeline.bind(buf); + this->descriptorSets.at(frameCount % 2).bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1p_include/v3_1p/context.hpp b/framegen/v3.1p_include/v3_1p/context.hpp new file mode 100644 index 0000000..ce965a0 --- /dev/null +++ b/framegen/v3.1p_include/v3_1p/context.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "core/image.hpp" +#include "core/semaphore.hpp" +#include "core/fence.hpp" +#include "core/commandbuffer.hpp" +#include "shaders/alpha.hpp" +#include "shaders/beta.hpp" +#include "shaders/delta.hpp" +#include "shaders/gamma.hpp" +#include "shaders/generate.hpp" +#include "shaders/mipmaps.hpp" +#include "common/utils.hpp" + +#include + +#include +#include +#include + +namespace LSFG_3_1P { + + using namespace LSFG; + + class Context { + public: + /// + /// Create a context + /// + /// @param vk The Vulkan instance to use. + /// @param in0 File descriptor for the first input image. + /// @param in1 File descriptor for the second input image. + /// @param outN File descriptors for the output images. + /// @param extent The size of the images. + /// @param format The format of the images. + /// + /// @throws LSFG::vulkan_error if the context fails to initialize. + /// + Context(Vulkan& vk, + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format); + + /// + /// Present on the context. + /// + /// @param inSem Semaphore to wait on before starting the generation. + /// @param outSem Semaphores to signal after each generation is done. + /// + /// @throws LSFG::vulkan_error if the context fails to present. + /// + void present(Vulkan& vk, + int inSem, const std::vector& outSem); + + // Trivially copyable, moveable and destructible + Context(const Context&) = default; + Context& operator=(const Context&) = default; + Context(Context&&) = default; + Context& operator=(Context&&) = default; + ~Context() = default; + private: + Core::Image inImg_0, inImg_1; // inImg_0 is next when fc % 2 == 0 + uint64_t frameIdx{0}; + + struct RenderData { + Core::Semaphore inSemaphore; // signaled when input is ready + std::vector internalSemaphores; // signaled when first step is done + std::vector outSemaphores; // signaled when each pass is done + std::vector completionFences; // fence for completion of each pass + + Core::CommandBuffer cmdBuffer1; + std::vector cmdBuffers2; // command buffers for second step + + bool shouldWait{false}; + }; + std::array data; + + Shaders::Mipmaps mipmaps; + std::array alpha; + Shaders::Beta beta; + std::array gamma; + std::array delta; + Shaders::Generate generate; + }; + +} diff --git a/framegen/v3.1p_include/v3_1p/shaders/alpha.hpp b/framegen/v3.1p_include/v3_1p/shaders/alpha.hpp new file mode 100644 index 0000000..c816285 --- /dev/null +++ b/framegen/v3.1p_include/v3_1p/shaders/alpha.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include + +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; + + /// + /// Alpha shader. + /// + class Alpha { + public: + Alpha() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg One mipmap level + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Alpha(Vulkan& vk, Core::Image inImg); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Alpha(const Alpha&) noexcept = default; + Alpha& operator=(const Alpha&) noexcept = default; + Alpha(Alpha&&) noexcept = default; + Alpha& operator=(Alpha&&) noexcept = default; + ~Alpha() = default; + private: + std::array shaderModules; + std::array pipelines; + Core::Sampler sampler; + std::array descriptorSets; + std::array lastDescriptorSet; + + Core::Image inImg; + Core::Image tempImg1; + Core::Image tempImg2; + std::array tempImgs3; + std::array, 3> outImgs; + }; + +} diff --git a/framegen/v3.1p_include/v3_1p/shaders/beta.hpp b/framegen/v3.1p_include/v3_1p/shaders/beta.hpp new file mode 100644 index 0000000..549aa9e --- /dev/null +++ b/framegen/v3.1p_include/v3_1p/shaders/beta.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include + +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; + + /// + /// Beta shader. + /// + class Beta { + public: + Beta() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs Three sets of two RGBA images, corresponding to a frame count % 3. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Beta(Vulkan& vk, std::array, 3> inImgs); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Beta(const Beta&) noexcept = default; + Beta& operator=(const Beta&) noexcept = default; + Beta(Beta&&) noexcept = default; + Beta& operator=(Beta&&) noexcept = default; + ~Beta() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + + std::array, 3> inImgs; + std::array tempImgs1; + std::array tempImgs2; + std::array outImgs; + }; + +} diff --git a/framegen/v3.1p_include/v3_1p/shaders/delta.hpp b/framegen/v3.1p_include/v3_1p/shaders/delta.hpp new file mode 100644 index 0000000..fdbf3d1 --- /dev/null +++ b/framegen/v3.1p_include/v3_1p/shaders/delta.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include +#include +#include + +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; + + /// + /// Delta shader. + /// + class Delta { + public: + Delta() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs1 Three sets of two RGBA images, corresponding to a frame count % 3. + /// @param inImg2 Second Input image + /// @param optImg1 Optional image for non-first passes. + /// @param optImg2 Second optional image for non-first passes. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Delta(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg1, + std::optional optImg2); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx, + bool last); + + /// Get the first output image + [[nodiscard]] const auto& getOutImage1() const { return this->outImg1; } + /// Get the second output image + [[nodiscard]] const auto& getOutImage2() const { return this->outImg2; } + + /// Trivially copyable, moveable and destructible + Delta(const Delta&) noexcept = default; + Delta& operator=(const Delta&) noexcept = default; + Delta(Delta&&) noexcept = default; + Delta& operator=(Delta&&) noexcept = default; + ~Delta() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + struct DeltaPass { + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + std::array sixthDescriptorSet; + }; + std::vector passes; + + std::array, 3> inImgs1; + Core::Image inImg2; + std::optional optImg1, optImg2; + std::array tempImgs1; + std::array tempImgs2; + Core::Image outImg1, outImg2; + }; + +} diff --git a/framegen/v3.1p_include/v3_1p/shaders/gamma.hpp b/framegen/v3.1p_include/v3_1p/shaders/gamma.hpp new file mode 100644 index 0000000..b1c3842 --- /dev/null +++ b/framegen/v3.1p_include/v3_1p/shaders/gamma.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include +#include +#include + +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; + + /// + /// Gamma shader. + /// + class Gamma { + public: + Gamma() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs1 Three sets of two RGBA images, corresponding to a frame count % 3. + /// @param inImg2 Second Input image + /// @param optImg Optional image for non-first passes. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Gamma(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, std::optional optImg); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Get the output image + [[nodiscard]] const auto& getOutImage() const { return this->outImg; } + + /// Trivially copyable, moveable and destructible + Gamma(const Gamma&) noexcept = default; + Gamma& operator=(const Gamma&) noexcept = default; + Gamma(Gamma&&) noexcept = default; + Gamma& operator=(Gamma&&) noexcept = default; + ~Gamma() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + struct GammaPass { + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + }; + std::vector passes; + + std::array, 3> inImgs1; + Core::Image inImg2; + std::optional optImg; + std::array tempImgs1; + std::array tempImgs2; + Core::Image outImg; + }; + +} diff --git a/framegen/v3.1p_include/v3_1p/shaders/generate.hpp b/framegen/v3.1p_include/v3_1p/shaders/generate.hpp new file mode 100644 index 0000000..42f1e7b --- /dev/null +++ b/framegen/v3.1p_include/v3_1p/shaders/generate.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include + +#include +#include +#include + +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; + + /// + /// Generate shader. + /// + class Generate { + public: + Generate() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg1 Input image 1. + /// @param inImg2 Input image 2. + /// @param inImg3 Input image 3. + /// @param inImg4 Input image 4. + /// @param inImg5 Input image 5. + /// @param fds File descriptors for the output images. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Generate(Vulkan& vk, + Core::Image inImg1, Core::Image inImg2, + Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, + const std::vector& fds, VkFormat format); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Trivially copyable, moveable and destructible + Generate(const Generate&) noexcept = default; + Generate& operator=(const Generate&) noexcept = default; + Generate(Generate&&) noexcept = default; + Generate& operator=(Generate&&) noexcept = default; + ~Generate() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + std::array samplers; + struct GeneratePass { + Core::Buffer buffer; + std::array descriptorSet; + }; + std::vector passes; + + Core::Image inImg1, inImg2; + Core::Image inImg3, inImg4, inImg5; + std::vector outImgs; + }; + +} diff --git a/framegen/v3.1p_include/v3_1p/shaders/mipmaps.hpp b/framegen/v3.1p_include/v3_1p/shaders/mipmaps.hpp new file mode 100644 index 0000000..ce0dca1 --- /dev/null +++ b/framegen/v3.1p_include/v3_1p/shaders/mipmaps.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include + +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; + + /// + /// Mipmaps shader. + /// + class Mipmaps { + public: + Mipmaps() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg_0 The next frame (when fc % 2 == 0) + /// @param inImg_1 The next frame (when fc % 2 == 1) + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Mipmaps(Vulkan& vk, Core::Image inImg_0, Core::Image inImg_1); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images. + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Mipmaps(const Mipmaps&) noexcept = default; + Mipmaps& operator=(const Mipmaps&) noexcept = default; + Mipmaps(Mipmaps&&) noexcept = default; + Mipmaps& operator=(Mipmaps&&) noexcept = default; + ~Mipmaps() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + Core::Buffer buffer; + Core::Sampler sampler; + std::array descriptorSets; + + Core::Image inImg_0, inImg_1; + std::array outImgs; + }; + +} diff --git a/framegen/v3.1p_src/context.cpp b/framegen/v3.1p_src/context.cpp new file mode 100644 index 0000000..6702b5b --- /dev/null +++ b/framegen/v3.1p_src/context.cpp @@ -0,0 +1,122 @@ +#include +#include + +#include "v3_1p/context.hpp" +#include "common/utils.hpp" +#include "common/exception.hpp" + +#include +#include +#include +#include +#include + +using namespace LSFG; +using namespace LSFG_3_1P; + +Context::Context(Vulkan& vk, + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format) { + // import input images + this->inImg_0 = Core::Image(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, in0); + this->inImg_1 = Core::Image(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, in1); + + // prepare render data + for (size_t i = 0; i < 8; i++) { + auto& data = this->data.at(i); + data.internalSemaphores.resize(vk.generationCount); + data.outSemaphores.resize(vk.generationCount); + data.completionFences.resize(vk.generationCount); + data.cmdBuffers2.resize(vk.generationCount); + } + + // create shader chains + this->mipmaps = Shaders::Mipmaps(vk, this->inImg_0, this->inImg_1); + for (size_t i = 0; i < 7; i++) + this->alpha.at(i) = Shaders::Alpha(vk, this->mipmaps.getOutImages().at(i)); + this->beta = Shaders::Beta(vk, this->alpha.at(0).getOutImages()); + for (size_t i = 0; i < 7; i++) { + this->gamma.at(i) = Shaders::Gamma(vk, + this->alpha.at(6 - i).getOutImages(), + this->beta.getOutImages().at(std::min(6 - i, 5)), + (i == 0) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage())); + if (i < 4) continue; + + this->delta.at(i - 4) = Shaders::Delta(vk, + this->alpha.at(6 - i).getOutImages(), + this->beta.getOutImages().at(6 - i), + (i == 4) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()), + (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage1())); + } + this->generate = Shaders::Generate(vk, + this->inImg_0, this->inImg_1, + this->gamma.at(6).getOutImage(), + this->delta.at(2).getOutImage1(), + this->delta.at(2).getOutImage2(), + outN, format); +} + +void Context::present(Vulkan& vk, + int inSem, const std::vector& outSem) { + auto& data = this->data.at(this->frameIdx % 8); + + // 3. wait for completion of previous frame in this slot + if (data.shouldWait) + for (auto& fence : data.completionFences) + if (!fence.wait(vk.device, UINT64_MAX)) + throw LSFG::vulkan_error(VK_TIMEOUT, "Fence wait timed out"); + data.shouldWait = true; + + // 1. create mipmaps and process input image + if (inSem >= 0) data.inSemaphore = Core::Semaphore(vk.device, inSem); + for (size_t i = 0; i < vk.generationCount; i++) + data.internalSemaphores.at(i) = Core::Semaphore(vk.device); + + data.cmdBuffer1 = Core::CommandBuffer(vk.device, vk.commandPool); + data.cmdBuffer1.begin(); + + this->mipmaps.Dispatch(data.cmdBuffer1, this->frameIdx); + for (size_t i = 0; i < 7; i++) + this->alpha.at(6 - i).Dispatch(data.cmdBuffer1, this->frameIdx); + this->beta.Dispatch(data.cmdBuffer1, this->frameIdx); + + data.cmdBuffer1.end(); + std::vector waits = { data.inSemaphore }; + if (inSem < 0) waits.clear(); + data.cmdBuffer1.submit(vk.device.getComputeQueue(), std::nullopt, + waits, std::nullopt, + data.internalSemaphores, std::nullopt); + + // 2. generate intermediary frames + for (size_t pass = 0; pass < vk.generationCount; pass++) { + auto& internalSemaphore = data.internalSemaphores.at(pass); + auto& outSemaphore = data.outSemaphores.at(pass); + if (inSem >= 0) outSemaphore = Core::Semaphore(vk.device, outSem.empty() ? -1 : outSem.at(pass)); + auto& completionFence = data.completionFences.at(pass); + completionFence = Core::Fence(vk.device); + + auto& buf2 = data.cmdBuffers2.at(pass); + buf2 = Core::CommandBuffer(vk.device, vk.commandPool); + buf2.begin(); + + for (size_t i = 0; i < 7; i++) { + this->gamma.at(i).Dispatch(buf2, this->frameIdx, pass); + if (i >= 4) + this->delta.at(i - 4).Dispatch(buf2, this->frameIdx, pass, i == 6); + } + this->generate.Dispatch(buf2, this->frameIdx, pass); + + buf2.end(); + std::vector signals = { outSemaphore }; + if (inSem < 0) signals.clear(); + buf2.submit(vk.device.getComputeQueue(), completionFence, + { internalSemaphore }, std::nullopt, + signals, std::nullopt); + } + + this->frameIdx++; +} diff --git a/framegen/v3.1p_src/lsfg.cpp b/framegen/v3.1p_src/lsfg.cpp new file mode 100644 index 0000000..a22609d --- /dev/null +++ b/framegen/v3.1p_src/lsfg.cpp @@ -0,0 +1,97 @@ +#include +#include + +#include "lsfg_3_1p.hpp" +#include "v3_1p/context.hpp" +#include "core/commandpool.hpp" +#include "core/descriptorpool.hpp" +#include "core/instance.hpp" +#include "pool/shaderpool.hpp" +#include "common/exception.hpp" +#include "common/utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace LSFG; +using namespace LSFG_3_1P; + +namespace { + std::optional instance; + std::optional device; + std::unordered_map contexts; +} + +void LSFG_3_1P::initialize(uint64_t deviceUUID, + bool isHdr, float flowScale, uint64_t generationCount, + const std::function(const std::string&)>& loader) { + if (instance.has_value() || device.has_value()) + return; + + instance.emplace(); + device.emplace(Vulkan { + .device{*instance, deviceUUID}, + .generationCount = generationCount, + .flowScale = flowScale, + .isHdr = isHdr + }); + contexts = std::unordered_map(); + + device->commandPool = Core::CommandPool(device->device); + device->descriptorPool = Core::DescriptorPool(device->device); + + device->resources = Pool::ResourcePool(device->isHdr, device->flowScale); + device->shaders = Pool::ShaderPool(loader); + + std::srand(static_cast(std::time(nullptr))); +} + +int32_t LSFG_3_1P::createContext( + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format) { + if (!instance.has_value() || !device.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + const int32_t id = std::rand(); + contexts.emplace(id, Context(*device, in0, in1, outN, extent, format)); + return id; +} + +void LSFG_3_1P::presentContext(int32_t id, int inSem, const std::vector& outSem) { + if (!instance.has_value() || !device.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + auto it = contexts.find(id); + if (it == contexts.end()) + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Context not found"); + + it->second.present(*device, inSem, outSem); +} + +void LSFG_3_1P::deleteContext(int32_t id) { + if (!instance.has_value() || !device.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + auto it = contexts.find(id); + if (it == contexts.end()) + throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context"); + + vkDeviceWaitIdle(device->device.handle()); + contexts.erase(it); +} + +void LSFG_3_1P::finalize() { + if (!instance.has_value() || !device.has_value()) + return; + + vkDeviceWaitIdle(device->device.handle()); + contexts.clear(); + device.reset(); + instance.reset(); +} diff --git a/framegen/v3.1p_src/shaders/alpha.cpp b/framegen/v3.1p_src/shaders/alpha.cpp new file mode 100644 index 0000000..cd04df7 --- /dev/null +++ b/framegen/v3.1p_src/shaders/alpha.cpp @@ -0,0 +1,138 @@ +#include +#include + +#include "v3_1p/shaders/alpha.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include +#include +#include + +using namespace LSFG_3_1P::Shaders; + +Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "p_alpha[0]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_alpha[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_alpha[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_alpha[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "p_alpha[0]"), + vk.shaders.getPipeline(vk.device, "p_alpha[1]"), + vk.shaders.getPipeline(vk.device, "p_alpha[2]"), + vk.shaders.getPipeline(vk.device, "p_alpha[3]") + }}; + this->sampler = vk.resources.getSampler(vk.device); + for (size_t i = 0; i < 3; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i)); + for (size_t i = 0; i < 3; i++) + this->lastDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(3)); + + // create internal images/outputs + const VkExtent2D extent = this->inImg.getExtent(); + const VkExtent2D halfExtent = { + .width = (extent.width + 1) >> 1, + .height = (extent.height + 1) >> 1 + }; + this->tempImg1 = Core::Image(vk.device, halfExtent); + this->tempImg2 = Core::Image(vk.device, halfExtent); + + const VkExtent2D quarterExtent = { + .width = (halfExtent.width + 1) >> 1, + .height = (halfExtent.height + 1) >> 1 + }; + for (size_t i = 0; i < 2; i++) { + this->tempImgs3.at(i) = Core::Image(vk.device, quarterExtent); + for (size_t j = 0; j < 3; j++) + this->outImgs.at(j).at(i) = Core::Image(vk.device, quarterExtent); + } + + // hook up shaders + this->descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImg1) + .build(); + this->descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImg1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImg2) + .build(); + this->descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs3) + .build(); + for (size_t i = 0; i < 3; i++) + this->lastDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i)) + .build(); +} + +void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto halfExtent = this->tempImg1.getExtent(); + uint32_t threadsX = (halfExtent.width + 7) >> 3; + uint32_t threadsY = (halfExtent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImg) + .addR2W(this->tempImg1) + .build(); + + this->pipelines.at(0).bind(buf); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImg1) + .addR2W(this->tempImg2) + .build(); + + this->pipelines.at(1).bind(buf); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third pass + const auto quarterExtent = this->tempImgs3.at(0).getExtent(); + threadsX = (quarterExtent.width + 7) >> 3; + threadsY = (quarterExtent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->tempImg2) + .addR2W(this->tempImgs3) + .build(); + + this->pipelines.at(2).bind(buf); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs3) + .addR2W(this->outImgs.at(frameCount % 3)) + .build(); + + this->pipelines.at(3).bind(buf); + this->lastDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1p_src/shaders/beta.cpp b/framegen/v3.1p_src/shaders/beta.cpp new file mode 100644 index 0000000..f8451ae --- /dev/null +++ b/framegen/v3.1p_src/shaders/beta.cpp @@ -0,0 +1,162 @@ +#include +#include + +#include "v3_1p/shaders/beta.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include +#include +#include +#include + +using namespace LSFG_3_1P::Shaders; + +Beta::Beta(Vulkan& vk, std::array, 3> inImgs) + : inImgs(std::move(inImgs)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "p_beta[0]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_beta[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_beta[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_beta[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_beta[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "p_beta[0]"), + vk.shaders.getPipeline(vk.device, "p_beta[1]"), + vk.shaders.getPipeline(vk.device, "p_beta[2]"), + vk.shaders.getPipeline(vk.device, "p_beta[3]"), + vk.shaders.getPipeline(vk.device, "p_beta[4]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + for (size_t i = 0; i < 3; i++) + this->firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(0)); + for (size_t i = 0; i < 4; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i + 1)); + this->buffer = vk.resources.getBuffer(vk.device, 0.5F); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs.at(0).at(0).getExtent(); + for (size_t i = 0; i < 2; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + } + + for (size_t i = 0; i < 6; i++) + this->outImgs.at(i) = Core::Image(vk.device, + { extent.width >> i, extent.height >> i }, + VK_FORMAT_R8_UNORM); + + // hook up shaders + for (size_t i = 0; i < 3; i++) { + this->firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 1) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + } + this->descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + this->descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .build(); +} + +void Beta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto extent = this->tempImgs1.at(0).getExtent(); + uint32_t threadsX = (extent.width + 7) >> 3; + uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs.at(0)) + .addW2R(this->inImgs.at(1)) + .addW2R(this->inImgs.at(2)) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(0).bind(buf); + this->firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth pass + threadsX = (extent.width + 31) >> 5; + threadsY = (extent.height + 31) >> 5; + + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->outImgs) + .build(); + + this->pipelines.at(4).bind(buf); + this->descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1p_src/shaders/delta.cpp b/framegen/v3.1p_src/shaders/delta.cpp new file mode 100644 index 0000000..df06d38 --- /dev/null +++ b/framegen/v3.1p_src/shaders/delta.cpp @@ -0,0 +1,323 @@ +#include +#include + +#include "v3_1p/shaders/delta.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include +#include +#include +#include +#include + +using namespace LSFG_3_1P::Shaders; + +Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg1, + std::optional optImg2) + : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), + optImg1(std::move(optImg1)), optImg2(std::move(optImg2)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "p_delta[0]", + { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_delta[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_delta[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_delta[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_delta[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_delta[5]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_delta[6]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_delta[7]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_delta[8]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_delta[9]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "p_delta[0]"), + vk.shaders.getPipeline(vk.device, "p_delta[1]"), + vk.shaders.getPipeline(vk.device, "p_delta[2]"), + vk.shaders.getPipeline(vk.device, "p_delta[3]"), + vk.shaders.getPipeline(vk.device, "p_delta[4]"), + vk.shaders.getPipeline(vk.device, "p_delta[5]"), + vk.shaders.getPipeline(vk.device, "p_delta[6]"), + vk.shaders.getPipeline(vk.device, "p_delta[7]"), + vk.shaders.getPipeline(vk.device, "p_delta[8]"), + vk.shaders.getPipeline(vk.device, "p_delta[9]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + this->samplers.at(2) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); + for (size_t i = 0; i < 3; i++) + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + for (size_t i = 0; i < 2; i++) + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + + this->outImg1 = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + this->outImg2 = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + + // hook up shaders + for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(pass_idx + 1) / static_cast(vk.generationCount + 1), + false, !this->optImg1.has_value()); + for (size_t i = 0; i < 3; i++) { + pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(0)); + pass.firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + } + pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(1)); + pass.descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(2)); + pass.descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .build(); + pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(3)); + pass.descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(4)); + pass.descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1) + .build(); + for (size_t i = 0; i < 3; i++) { + pass.sixthDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(5)); + pass.sixthDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0)) + .build(); + } + pass.descriptorSets.at(4) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(6)); + pass.descriptorSets.at(4).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .build(); + pass.descriptorSets.at(5) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(7)); + pass.descriptorSets.at(5).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0)) + .build(); + pass.descriptorSets.at(6) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(8)); + pass.descriptorSets.at(6).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .build(); + pass.descriptorSets.at(7) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(9)); + pass.descriptorSets.at(7).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2) + .build(); + } +} + +void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx, + bool last) { + auto& pass = this->passes.at(pass_idx); + + // first shader + const auto extent = this->tempImgs1.at(0).getExtent(); + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg1) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(0).bind(buf); + pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addW2R(this->optImg1) + .addW2R(this->inImg2) + .addR2W(this->outImg1) + .build(); + + this->pipelines.at(4).bind(buf); + pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); + + // sixth shader + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg1) + .addW2R(this->optImg2) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(5).bind(buf); + pass.sixthDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(5)); + buf.dispatch(threadsX, threadsY, 1); + + if (!last) + return; + + // seventh shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .build(); + + this->pipelines.at(6).bind(buf); + pass.descriptorSets.at(4).bind(buf, this->pipelines.at(6)); + buf.dispatch(threadsX, threadsY, 1); + + // eighth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addR2W(this->tempImgs2) + .build(); + this->pipelines.at(7).bind(buf); + pass.descriptorSets.at(5).bind(buf, this->pipelines.at(7)); + buf.dispatch(threadsX, threadsY, 1); + + // ninth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .build(); + + this->pipelines.at(8).bind(buf); + pass.descriptorSets.at(6).bind(buf, this->pipelines.at(8)); + buf.dispatch(threadsX, threadsY, 1); + + // tenth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addR2W(this->outImg2) + .build(); + + this->pipelines.at(9).bind(buf); + pass.descriptorSets.at(7).bind(buf, this->pipelines.at(9)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1p_src/shaders/gamma.cpp b/framegen/v3.1p_src/shaders/gamma.cpp new file mode 100644 index 0000000..ae87e79 --- /dev/null +++ b/framegen/v3.1p_src/shaders/gamma.cpp @@ -0,0 +1,189 @@ +#include +#include + +#include "v3_1p/shaders/gamma.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include +#include +#include +#include +#include + +using namespace LSFG_3_1P::Shaders; + +Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg) + : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), + optImg(std::move(optImg)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "p_gamma[0]", + { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_gamma[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_gamma[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_gamma[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "p_gamma[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "p_gamma[0]"), + vk.shaders.getPipeline(vk.device, "p_gamma[1]"), + vk.shaders.getPipeline(vk.device, "p_gamma[2]"), + vk.shaders.getPipeline(vk.device, "p_gamma[3]"), + vk.shaders.getPipeline(vk.device, "p_gamma[4]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + this->samplers.at(2) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); + for (size_t i = 0; i < 3; i++) + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + for (size_t i = 0; i < 2; i++) + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + + this->outImg = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + + // hook up shaders + for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(pass_idx + 1) / static_cast(vk.generationCount + 1), + !this->optImg.has_value()); + for (size_t i = 0; i < 3; i++) { + pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(0)); + pass.firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + } + pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(1)); + pass.descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(2)); + pass.descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .build(); + pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(3)); + pass.descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(4)); + pass.descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .build(); + } +} + +void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first shader + const auto extent = this->tempImgs1.at(0).getExtent(); + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(0).bind(buf); + pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .build(); + + this->pipelines.at(2).bind(buf); + pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addW2R(this->optImg) + .addW2R(this->inImg2) + .addR2W(this->outImg) + .build(); + + this->pipelines.at(4).bind(buf); + pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1p_src/shaders/generate.cpp b/framegen/v3.1p_src/shaders/generate.cpp new file mode 100644 index 0000000..5f511da --- /dev/null +++ b/framegen/v3.1p_src/shaders/generate.cpp @@ -0,0 +1,83 @@ +#include +#include + +#include "v3_1p/shaders/generate.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include +#include +#include +#include + +using namespace LSFG_3_1P::Shaders; + +Generate::Generate(Vulkan& vk, + Core::Image inImg1, Core::Image inImg2, + Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, + const std::vector& fds, VkFormat format) + : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)), + inImg3(std::move(inImg3)), inImg4(std::move(inImg4)), + inImg5(std::move(inImg5)) { + // create resources + this->shaderModule = vk.shaders.getShader(vk.device, "p_generate", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); + this->pipeline = vk.shaders.getPipeline(vk.device, "p_generate"); + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS); + + // create internal images/outputs + const VkExtent2D extent = this->inImg1.getExtent(); + for (size_t i = 0; i < vk.generationCount; i++) + this->outImgs.emplace_back(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, fds.empty() ? -1 : fds.at(i)); + + // hook up shaders + for (size_t i = 0; i < vk.generationCount; i++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(i + 1) / static_cast(vk.generationCount + 1)); + for (size_t j = 0; j < 2; j++) { + pass.descriptorSet.at(j) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModule); + pass.descriptorSet.at(j).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg2 : this->inImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg1 : this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg4) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg5) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i)) + .build(); + } + } +} + +void Generate::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first pass + const auto extent = this->inImg1.getExtent(); + const uint32_t threadsX = (extent.width + 15) >> 4; + const uint32_t threadsY = (extent.height + 15) >> 4; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImg1) + .addW2R(this->inImg2) + .addW2R(this->inImg3) + .addW2R(this->inImg4) + .addW2R(this->inImg5) + .addR2W(this->outImgs.at(pass_idx)) + .build(); + + this->pipeline.bind(buf); + pass.descriptorSet.at(frameCount % 2).bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/framegen/v3.1p_src/shaders/mipmaps.cpp b/framegen/v3.1p_src/shaders/mipmaps.cpp new file mode 100644 index 0000000..531b34f --- /dev/null +++ b/framegen/v3.1p_src/shaders/mipmaps.cpp @@ -0,0 +1,66 @@ +#include +#include + +#include "v3_1p/shaders/mipmaps.hpp" +#include "common/utils.hpp" +#include "core/image.hpp" +#include "core/commandbuffer.hpp" + +#include +#include +#include + +using namespace LSFG_3_1P::Shaders; + +Mipmaps::Mipmaps(Vulkan& vk, + Core::Image inImg_0, Core::Image inImg_1) + : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) { + // create resources + this->shaderModule = vk.shaders.getShader(vk.device, "p_mipmaps", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); + this->pipeline = vk.shaders.getPipeline(vk.device, "p_mipmaps"); + this->buffer = vk.resources.getBuffer(vk.device); + this->sampler = vk.resources.getSampler(vk.device); + for (size_t i = 0; i < 2; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModule); + + // create outputs + const VkExtent2D flowExtent{ + .width = static_cast( + static_cast(this->inImg_0.getExtent().width) / vk.flowScale), + .height = static_cast( + static_cast(this->inImg_0.getExtent().height) / vk.flowScale) + }; + for (size_t i = 0; i < 7; i++) + this->outImgs.at(i) = Core::Image(vk.device, + { flowExtent.width >> i, flowExtent.height >> i }, + VK_FORMAT_R8_UNORM); + + // hook up shaders + for (size_t fc = 0; fc < 2; fc++) + this->descriptorSets.at(fc).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg_0 : this->inImg_1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .build(); +} + +void Mipmaps::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto flowExtent = this->outImgs.at(0).getExtent(); + const uint32_t threadsX = (flowExtent.width + 63) >> 6; + const uint32_t threadsY = (flowExtent.height + 63) >> 6; + + Utils::BarrierBuilder(buf) + .addW2R((frameCount % 2 == 0) ? this->inImg_0 : this->inImg_1) + .addR2W(this->outImgs) + .build(); + + this->pipeline.bind(buf); + this->descriptorSets.at(frameCount % 2).bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/include/config/config.hpp b/include/config/config.hpp new file mode 100644 index 0000000..eef37da --- /dev/null +++ b/include/config/config.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace Config { + + /// lsfg-vk configuration + struct Configuration { + /// Whether lsfg-vk should be loaded in the first place. + bool enable{false}; + /// Path to Lossless.dll. + std::string dll; + + /// The frame generation muliplier + size_t multiplier{2}; + /// The internal flow scale factor + float flowScale{1.0F}; + /// Whether performance mode is enabled + bool performance{false}; + /// Whether HDR is enabled + bool hdr{false}; + + /// Experimental flag for overriding the synchronization method. + VkPresentModeKHR e_present; + + /// Path to the configuration file. + std::filesystem::path config_file; + /// File timestamp of the configuration file + std::chrono::time_point timestamp; + }; + + /// Active configuration. Must be set in main.cpp. + extern Configuration activeConf; + + /// + /// Read the configuration file while preserving the previous configuration + /// in case of an error. + /// + /// @param file The path to the configuration file. + /// + /// @throws std::runtime_error if an error occurs while loading the configuration file. + /// + void updateConfig(const std::string& file); + + /// + /// Get the configuration for a game. + /// + /// @param name The name of the executable to fetch. + /// @return The configuration for the game or global configuration. + /// + /// @throws std::runtime_error if the configuration is invalid. + /// + Configuration getConfig(const std::pair& name); + +} diff --git a/include/config/default_conf.hpp b/include/config/default_conf.hpp new file mode 100644 index 0000000..d6facc6 --- /dev/null +++ b/include/config/default_conf.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +const std::string DEFAULT_CONFIG = R"(version = 1 +[global] +# override the location of Lossless Scaling +# dll = "/games/Lossless Scaling/Lossless.dll" + +# [[game]] # example entry +# exe = "Game.exe" +# +# multiplier = 3 +# flow_scale = 0.7 +# performance_mode = true +# hdr_mode = false +# +# experimental_present_mode = "fifo" + +[[game]] # default vkcube entry +exe = "vkcube" + +multiplier = 4 +performance_mode = true + +[[game]] # default benchmark entry +exe = "benchmark" + +multiplier = 4 +performance_mode = false + +[[game]] # override Genshin Impact +exe = "Genshin" + +multiplier = 3 +)"; diff --git a/include/context.hpp b/include/context.hpp new file mode 100644 index 0000000..3438e48 --- /dev/null +++ b/include/context.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include "hooks.hpp" +#include "mini/commandbuffer.hpp" +#include "mini/commandpool.hpp" +#include "mini/image.hpp" +#include "mini/semaphore.hpp" + +#include + +#include +#include +#include +#include + +/// +/// This class is the frame generation context. There should be one instance per swapchain. +/// +class LsContext { +public: + /// + /// Create the swapchain context. + /// + /// @param info The device information to use. + /// @param swapchain The Vulkan swapchain to use. + /// @param extent The extent of the swapchain images. + /// @param swapchainImages The swapchain images to use. + /// + /// @throws LSFG::vulkan_error if any Vulkan call fails. + /// + LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, + VkExtent2D extent, const std::vector& swapchainImages); + + /// + /// Custom present logic. + /// + /// @param info The device information to use. + /// @param pNext Unknown pointer set in the present info structure. + /// @param queue The Vulkan queue to present the frame on. + /// @param gameRenderSemaphores The semaphores to wait on before presenting. + /// @param presentIdx The index of the swapchain image to present. + /// @return The result of the Vulkan present operation, which can be VK_SUCCESS or VK_SUBOPTIMAL_KHR. + /// + /// @throws LSFG::vulkan_error if any Vulkan call fails. + /// + VkResult present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue, + const std::vector& gameRenderSemaphores, uint32_t presentIdx); + + // Non-copyable, trivially moveable and destructible + LsContext(const LsContext&) = delete; + LsContext& operator=(const LsContext&) = delete; + LsContext(LsContext&&) = default; + LsContext& operator=(LsContext&&) = default; + ~LsContext() = default; +private: + VkSwapchainKHR swapchain; + std::vector swapchainImages; + VkExtent2D extent; + + std::shared_ptr lsfgCtxId; // lsfg context id + Mini::Image frame_0, frame_1; // frames shared with lsfg. write to frame_0 when fc % 2 == 0 + std::vector out_n; // output images shared with lsfg, indexed by framegen id + + Mini::CommandPool cmdPool; + uint64_t frameIdx{0}; + + struct RenderPassInfo { + Mini::CommandBuffer preCopyBuf; // copy from swapchain image to frame_0/frame_1 + std::array preCopySemaphores; // signal when preCopyBuf is done + + std::vector renderSemaphores; // signal when lsfg is done with frame n + + std::vector acquireSemaphores; // signal for swapchain image n + + std::vector postCopyBufs; // copy from out_n to swapchain image + std::vector postCopySemaphores; // signal when postCopyBuf is done + std::vector prevPostCopySemaphores; // signal for previous postCopyBuf + }; // data for a single render pass + std::array passInfos; // allocate 8 because why not +}; diff --git a/include/extract/extract.hpp b/include/extract/extract.hpp new file mode 100644 index 0000000..812a2bd --- /dev/null +++ b/include/extract/extract.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +namespace Extract { + + /// + /// Extract all known shaders. + /// + /// @throws std::runtime_error if shader extraction fails. + /// + void extractShaders(); + + /// + /// Get a shader by name. + /// + /// @param name The name of the shader to get. + /// @return The shader bytecode. + /// + /// @throws std::runtime_error if the shader is not found. + /// + std::vector getShader(const std::string& name); + +} diff --git a/include/extract/trans.hpp b/include/extract/trans.hpp new file mode 100644 index 0000000..9da1df9 --- /dev/null +++ b/include/extract/trans.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace Extract { + + /// + /// Translate DXBC bytecode to SPIR-V bytecode. + /// + /// @param bytecode The DXBC bytecode to translate. + /// @return The translated SPIR-V bytecode. + /// + std::vector translateShader(std::vector bytecode); + +} diff --git a/include/hooks.hpp b/include/hooks.hpp new file mode 100644 index 0000000..e38212c --- /dev/null +++ b/include/hooks.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace Hooks { + + /// Vulkan device information structure. + struct DeviceInfo { + VkDevice device; + VkPhysicalDevice physicalDevice; + std::pair queue; // graphics family + }; + + /// Map of hooked Vulkan functions. + extern std::unordered_map hooks; + +} diff --git a/include/layer.hpp b/include/layer.hpp new file mode 100644 index 0000000..ce12818 --- /dev/null +++ b/include/layer.hpp @@ -0,0 +1,224 @@ +#pragma once + +#include + +#include + +namespace Layer { + /// Call to the original vkCreateInstance function. + VkResult ovkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance); + /// Call to the original vkDestroyInstance function. + void ovkDestroyInstance( + VkInstance instance, + const VkAllocationCallbacks* pAllocator); + + /// Call to the original vkCreateDevice function. + VkResult ovkCreateDevice( + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice); + /// Call to the original vkDestroyDevice function. + void ovkDestroyDevice( + VkDevice device, + const VkAllocationCallbacks* pAllocator); + + /// Call to the original vkSetDeviceLoaderData function. + VkResult ovkSetDeviceLoaderData( + VkDevice device, + void* object); + + /// Call to the original vkGetInstanceProcAddr function. + PFN_vkVoidFunction ovkGetInstanceProcAddr( + VkInstance instance, + const char* pName); + /// Call to the original vkGetDeviceProcAddr function. + PFN_vkVoidFunction ovkGetDeviceProcAddr( + VkDevice device, + const char* pName); + + /// Call to the original vkGetPhysicalDeviceQueueFamilyProperties function. + void ovkGetPhysicalDeviceQueueFamilyProperties( + VkPhysicalDevice physicalDevice, + uint32_t* pQueueFamilyPropertyCount, + VkQueueFamilyProperties* pQueueFamilyProperties); + /// Call to the original vkGetPhysicalDeviceMemoryProperties function. + void ovkGetPhysicalDeviceMemoryProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceMemoryProperties* pMemoryProperties); + /// Call to the original vkGetPhysicalDeviceProperties function. + void ovkGetPhysicalDeviceProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceProperties* pProperties); + /// Call to the original vkGetPhysicalDeviceSurfaceCapabilitiesKHR function. + VkResult ovkGetPhysicalDeviceSurfaceCapabilitiesKHR( + VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface, + VkSurfaceCapabilitiesKHR* pSurfaceCapabilities); + + /// Call to the original vkCreateSwapchainKHR function. + VkResult ovkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain); + /// Call to the original vkQueuePresentKHR function. + VkResult ovkQueuePresentKHR( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo); + /// Call to the original vkDestroySwapchainKHR function. + void ovkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator); + + /// Call to the original vkGetSwapchainImagesKHR function. + VkResult ovkGetSwapchainImagesKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint32_t* pSwapchainImageCount, + VkImage* pSwapchainImages); + + /// Call to the original vkAllocateCommandBuffers function. + VkResult ovkAllocateCommandBuffers( + VkDevice device, + const VkCommandBufferAllocateInfo* pAllocateInfo, + VkCommandBuffer* pCommandBuffers); + /// Call to the original vkFreeCommandBuffers function. + void ovkFreeCommandBuffers( + VkDevice device, + VkCommandPool commandPool, + uint32_t commandBufferCount, + const VkCommandBuffer* pCommandBuffers); + + /// Call to the original vkBeginCommandBuffer function. + VkResult ovkBeginCommandBuffer( + VkCommandBuffer commandBuffer, + const VkCommandBufferBeginInfo* pBeginInfo); + /// Call to the original vkEndCommandBuffer function. + VkResult ovkEndCommandBuffer( + VkCommandBuffer commandBuffer); + + /// Call to the original vkCreateCommandPool function. + VkResult ovkCreateCommandPool( + VkDevice device, + const VkCommandPoolCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkCommandPool* pCommandPool); + /// Call to the original vkDestroyCommandPool function. + void ovkDestroyCommandPool( + VkDevice device, + VkCommandPool commandPool, + const VkAllocationCallbacks* pAllocator); + + /// Call to the original vkCreateImage function. + VkResult ovkCreateImage( + VkDevice device, + const VkImageCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkImage* pImage); + /// Call to the original vkDestroyImage function. + void ovkDestroyImage( + VkDevice device, + VkImage image, + const VkAllocationCallbacks* pAllocator); + + /// Call to the original vkGetImageMemoryRequirements function. + void ovkGetImageMemoryRequirements( + VkDevice device, + VkImage image, + VkMemoryRequirements* pMemoryRequirements); + /// Call to the original vkBindImageMemory function. + VkResult ovkBindImageMemory( + VkDevice device, + VkImage image, + VkDeviceMemory memory, + VkDeviceSize memoryOffset); + /// Call to the original vkAllocateMemory function. + VkResult ovkAllocateMemory( + VkDevice device, + const VkMemoryAllocateInfo* pAllocateInfo, + const VkAllocationCallbacks* pAllocator, + VkDeviceMemory* pMemory); + /// Call to the original vkFreeMemory function. + void ovkFreeMemory( + VkDevice device, + VkDeviceMemory memory, + const VkAllocationCallbacks* pAllocator); + + /// Call to the original vkCreateSemaphore function. + VkResult ovkCreateSemaphore( + VkDevice device, + const VkSemaphoreCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSemaphore* pSemaphore); + /// Call to the original vkDestroySemaphore function. + void ovkDestroySemaphore( + VkDevice device, + VkSemaphore semaphore, + const VkAllocationCallbacks* pAllocator); + + /// Call to the original vkGetMemoryFdKHR function. + VkResult ovkGetMemoryFdKHR( + VkDevice device, + const VkMemoryGetFdInfoKHR* pGetFdInfo, + int* pFd); + /// Call to the original vkGetSemaphoreFdKHR function. + VkResult ovkGetSemaphoreFdKHR( + VkDevice device, + const VkSemaphoreGetFdInfoKHR* pGetFdInfo, + int* pFd); + + /// Call to the original vkGetDeviceQueue function. + void ovkGetDeviceQueue( + VkDevice device, + uint32_t queueFamilyIndex, + uint32_t queueIndex, + VkQueue* pQueue); + /// Call to the original vkQueueSubmit function. + VkResult ovkQueueSubmit( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo* pSubmits, + VkFence fence); + + /// Call to the original vkCmdPipelineBarrier function. + void ovkCmdPipelineBarrier( + VkCommandBuffer commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + uint32_t memoryBarrierCount, + const VkMemoryBarrier* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + const VkBufferMemoryBarrier* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + const VkImageMemoryBarrier* pImageMemoryBarriers); + /// Call to the original vkCmdBlitImage function. + void ovkCmdBlitImage( + VkCommandBuffer commandBuffer, + VkImage srcImage, + VkImageLayout srcImageLayout, + VkImage dstImage, + VkImageLayout dstImageLayout, + uint32_t regionCount, + const VkImageBlit* pRegions, + VkFilter filter); + + /// Call to the original vkAcquireNextImageKHR function. + VkResult ovkAcquireNextImageKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint64_t timeout, + VkSemaphore semaphore, + VkFence fence, + uint32_t* pImageIndex); +} + +/// Symbol definition for Vulkan instance layer. +extern "C" PFN_vkVoidFunction layer_vkGetInstanceProcAddr(VkInstance instance, const char* pName); +/// Symbol definition for Vulkan device layer. +extern "C" PFN_vkVoidFunction layer_vkGetDeviceProcAddr(VkDevice device, const char* pName); diff --git a/include/mini/commandbuffer.hpp b/include/mini/commandbuffer.hpp new file mode 100644 index 0000000..2189596 --- /dev/null +++ b/include/mini/commandbuffer.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include "mini/commandpool.hpp" + +#include + +#include +#include + +namespace Mini { + + /// State of the command buffer. + enum class CommandBufferState { + /// Command buffer is not initialized or has been destroyed. + Invalid, + /// Command buffer has been created. + Empty, + /// Command buffer recording has started. + Recording, + /// Command buffer recording has ended. + Full, + /// Command buffer has been submitted to a queue. + Submitted + }; + + /// + /// C++ wrapper class for a Vulkan command buffer. + /// + /// This class manages the lifetime of a Vulkan command buffer. + /// + class CommandBuffer { + public: + CommandBuffer() noexcept = default; + + /// + /// Create the command buffer. + /// + /// @param device Vulkan device + /// @param pool Vulkan command pool + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + CommandBuffer(VkDevice device, const CommandPool& pool); + + /// + /// Begin recording commands in the command buffer. + /// + /// @throws std::logic_error if the command buffer is in Empty state + /// @throws LSFG::vulkan_error if beginning the command buffer fails. + /// + void begin(); + + /// + /// End recording commands in the command buffer. + /// + /// @throws std::logic_error if the command buffer is not in Recording state + /// @throws LSFG::vulkan_error if ending the command buffer fails. + /// + void end(); + + /// + /// Submit the command buffer to a queue. + /// + /// @param queue Vulkan queue to submit to + /// @param waitSemaphores Semaphores to wait on before executing the command buffer + /// @param signalSemaphores Semaphores to signal after executing the command buffer + /// + /// @throws std::logic_error if the command buffer is not in Full state. + /// @throws LSFG::vulkan_error if submission fails. + /// + void submit(VkQueue queue, + const std::vector& waitSemaphores = {}, + const std::vector& signalSemaphores = {}); + + /// Get the state of the command buffer. + [[nodiscard]] CommandBufferState getState() const { return *this->state; } + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->commandBuffer; } + + /// Trivially copyable, moveable and destructible + CommandBuffer(const CommandBuffer&) noexcept = default; + CommandBuffer& operator=(const CommandBuffer&) noexcept = default; + CommandBuffer(CommandBuffer&&) noexcept = default; + CommandBuffer& operator=(CommandBuffer&&) noexcept = default; + ~CommandBuffer() = default; + private: + std::shared_ptr state; + std::shared_ptr commandBuffer; + }; + +} diff --git a/include/mini/commandpool.hpp b/include/mini/commandpool.hpp new file mode 100644 index 0000000..08c0295 --- /dev/null +++ b/include/mini/commandpool.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include +#include + +namespace Mini { + + /// + /// C++ wrapper class for a Vulkan command pool. + /// + /// This class manages the lifetime of a Vulkan command pool. + /// + class CommandPool { + public: + CommandPool() noexcept = default; + + /// + /// Create the command pool. + /// + /// @param device Vulkan device + /// @param graphicsFamilyIdx Index of the graphics queue family + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + CommandPool(VkDevice device, uint32_t graphicsFamilyIdx); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->commandPool; } + + /// Trivially copyable, moveable and destructible + CommandPool(const CommandPool&) noexcept = default; + CommandPool& operator=(const CommandPool&) noexcept = default; + CommandPool(CommandPool&&) noexcept = default; + CommandPool& operator=(CommandPool&&) noexcept = default; + ~CommandPool() = default; + private: + std::shared_ptr commandPool; + }; + +} diff --git a/include/mini/image.hpp b/include/mini/image.hpp new file mode 100644 index 0000000..4c159cd --- /dev/null +++ b/include/mini/image.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include + +namespace Mini { + + /// + /// C++ wrapper class for a Vulkan image. + /// + /// This class manages the lifetime of a Vulkan image. + /// + class Image { + public: + Image() noexcept = default; + + /// + /// Create the image and export the backing fd + /// + /// @param device Vulkan device + /// @param physicalDevice Vulkan physical device + /// @param extent Extent of the image in pixels. + /// @param format Vulkan format of the image + /// @param usage Usage flags for the image + /// @param aspectFlags Aspect flags for the image view + /// @param fd Pointer to an integer where the file descriptor will be stored. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Image(VkDevice device, VkPhysicalDevice physicalDevice, VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->image; } + /// Get the Vulkan device memory handle. + [[nodiscard]] auto getMemory() const { return *this->memory; } + /// Get the extent of the image. + [[nodiscard]] VkExtent2D getExtent() const { return this->extent; } + /// Get the format of the image. + [[nodiscard]] VkFormat getFormat() const { return this->format; } + /// Get the aspect flags of the image. + [[nodiscard]] VkImageAspectFlags getAspectFlags() const { return this->aspectFlags; } + + /// Trivially copyable, moveable and destructible + Image(const Image&) noexcept = default; + Image& operator=(const Image&) noexcept = default; + Image(Image&&) noexcept = default; + Image& operator=(Image&&) noexcept = default; + ~Image() = default; + private: + std::shared_ptr image; + std::shared_ptr memory; + + VkExtent2D extent{}; + VkFormat format{}; + VkImageAspectFlags aspectFlags{}; + }; + +} diff --git a/include/mini/semaphore.hpp b/include/mini/semaphore.hpp new file mode 100644 index 0000000..03df4d9 --- /dev/null +++ b/include/mini/semaphore.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include + +namespace Mini { + + /// + /// C++ wrapper class for a Vulkan semaphore. + /// + /// This class manages the lifetime of a Vulkan semaphore. + /// + class Semaphore { + public: + Semaphore() noexcept = default; + + /// + /// Create the semaphore. + /// + /// @param device Vulkan device + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Semaphore(VkDevice device); + + /// + /// Import a semaphore. + /// + /// @param device Vulkan device + /// @param fd File descriptor to import the semaphore from. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Semaphore(VkDevice device, int* fd); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->semaphore; } + + // Trivially copyable, moveable and destructible + Semaphore(const Semaphore&) noexcept = default; + Semaphore& operator=(const Semaphore&) noexcept = default; + Semaphore(Semaphore&&) noexcept = default; + Semaphore& operator=(Semaphore&&) noexcept = default; + ~Semaphore() = default; + private: + std::shared_ptr semaphore; + }; + +} diff --git a/include/utils/benchmark.hpp b/include/utils/benchmark.hpp new file mode 100644 index 0000000..fc7f481 --- /dev/null +++ b/include/utils/benchmark.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace Benchmark { + + /// + /// Run the benchmark. + /// + /// @param width The width of the benchmark. + /// @param height The height of the benchmark. + /// + [[noreturn]] void run(uint32_t width, uint32_t height); + +} diff --git a/include/utils/utils.hpp b/include/utils/utils.hpp new file mode 100644 index 0000000..0dc8605 --- /dev/null +++ b/include/utils/utils.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace Utils { + + /// + /// Find a queue in the physical device that supports the given queue flags. + /// + /// @param device The Vulkan device to use for queue retrieval. + /// @param physicalDevice The physical device to search in. + /// @param desc The device creation info, used to determine enabled queue families. + /// @param flags The queue flags to search for (e.g., VK_QUEUE_GRAPHICS_BIT). + /// @return Pair of queue family index and queue handle. + /// + /// @throws LSFG::vulkan_error if no suitable queue is found. + /// + std::pair findQueue(VkDevice device, VkPhysicalDevice physicalDevice, + VkDeviceCreateInfo* desc, VkQueueFlags flags); + + /// + /// Get the UUID of the physical device. + /// + /// @param physicalDevice The physical device to get the UUID from. + /// @return The UUID of the physical device. + /// + uint64_t getDeviceUUID(VkPhysicalDevice physicalDevice); + + /// + /// Get the max image count for a swapchain. + /// + /// @param physicalDevice The physical device to query. + /// @param surface The surface to query the capabilities for. + /// @return The maximum image count for the swapchain. + /// + uint32_t getMaxImageCount(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface); + + /// + /// Ensure a list of extensions is present in the given array. + /// + /// @param extensions The array of extensions to check. + /// @param requiredExtensions The list of required extensions to ensure are present. + /// + std::vector addExtensions(const char* const* extensions, size_t count, + const std::vector& requiredExtensions); + + /// + /// Copy an image from source to destination in a command buffer. + /// + /// @param buf The command buffer to record the copy operation into. + /// @param src The source image to copy from. + /// @param dst The destination image to copy to. + /// @param width The width of the image to copy. + /// @param height The height of the image to copy. + /// @param pre The pipeline stage to wait on. + /// @param post The pipeline stage to provide after the copy. + /// @param makeSrcPresentable If true, the source image will be made presentable after the copy. + /// @param makeDstPresentable If true, the destination image will be made presentable after the copy. + /// + void copyImage(VkCommandBuffer buf, + VkImage src, VkImage dst, + uint32_t width, uint32_t height, + VkPipelineStageFlags pre, VkPipelineStageFlags post, + bool makeSrcPresentable, bool makeDstPresentable); + + /// + /// Log a message at most n times. + /// + /// @param id The identifier for the log message. + /// @param n The maximum number of times to log the message. + /// @param message The message to log. + /// + void logLimitN(const std::string& id, size_t n, const std::string& message); + + /// + /// Reset the log limit for a given identifier. + /// + /// @param id The identifier for the log message. + /// + void resetLimitN(const std::string& id) noexcept; + + /// + /// Get the process name of the current executable. + /// + /// @return The name of the process. + /// + std::pair getProcessName(); + + /// + /// Get the configuration file path. + /// + /// @return The path to the configuration file. + /// + std::string getConfigFile(); + +} diff --git a/scripts/build/appimage.sh b/scripts/build/appimage.sh new file mode 100755 index 0000000..cd167a9 --- /dev/null +++ b/scripts/build/appimage.sh @@ -0,0 +1,72 @@ +#!/bin/sh +set -eux + +# AppImage build script provided by @Samueru-sama +# (with removed aarch64 supported and removed update functionality, temporarily) + +URUNTIME="https://github.com/VHSgunzo/uruntime/releases/latest/download/uruntime-appimage-dwarfs-x86_64" +URUNTIME_LITE="https://github.com/VHSgunzo/uruntime/releases/latest/download/uruntime-appimage-dwarfs-lite-x86_64" +SHARUN="https://github.com/VHSgunzo/sharun/releases/latest/download/sharun-x86_64-aio" + +LIBXML_URL="https://github.com/pkgforge-dev/llvm-libs-debloated/releases/download/continuous/libxml2-iculess-x86_64.pkg.tar.zst" +MESA_URL="https://github.com/pkgforge-dev/llvm-libs-debloated/releases/download/continuous/mesa-mini-x86_64.pkg.tar.zst" +LLVM_URL="https://github.com/pkgforge-dev/llvm-libs-debloated/releases/download/continuous/llvm-libs-nano-x86_64.pkg.tar.zst" + +VERSION=$(awk -F'=|"' '/^version/{print $3}' ./Cargo.toml) +echo "$VERSION-dev" > ~/version + +# grab required resources +wget -O sharun-aio "$SHARUN" +chmod +x sharun-aio + +wget -O uruntime "$URUNTIME" +wget -O uruntime-lite "$URUNTIME_LITE" +chmod +x uruntime uruntime-lite + +# build lsfg-vk-ui +echo "Building lsfg-vk-ui..." +cargo build --release # doesn't compile with debloated llvm + +# install debloated dependencies +echo "Installing debloated packages..." +wget -O libxml2.pkg.tar.zst "$LIBXML_URL" +wget -O mesa.pkg.tar.zst "$MESA_URL" +wget -O llvm-libs.pkg.tar.zst "$LLVM_URL" + +#pacman -U --noconfirm *.pkg.tar.zst +rm -fv *.pkg.tar.zst + +# deploy app directory +echo "Deploying app directory..." +mkdir -p AppDir +cp -v rsc/*.desktop AppDir +cp -v rsc/icon.png AppDir/lsfg-vk-ui.png +cp -v rsc/icon.png AppDir/.DirIcon +mkdir -p AppDir/shared/bin +mv -v target/release/lsfg-vk-ui AppDir/shared/bin + +cd AppDir + xvfb-run -a ../sharun-aio l -p -v -e -s -k \ + shared/bin/lsfg-vk-ui \ + /usr/lib/gdk-pixbuf-*/*/loaders/* \ + /usr/lib/gio/modules/libdconfsettings.so + ln -fv ./sharun ./AppRun + ./sharun -g +cd .. + +# create appimage +echo "Generating app image..." +./uruntime --appimage-mkdwarfs -f \ + --set-owner 0 --set-group 0 \ + --no-history --no-create-timestamp \ + --compression zstd:level=22 -S26 -B8 \ + --header uruntime-lite \ + -i ./AppDir -o "./lsfg-vk-ui.AppImage" + +# cleanup +echo "Cleaning up..." +rm -rf AppDir +rm -f sharun-aio uruntime uruntime-lite + +# done +echo "Done" diff --git a/scripts/flatpak/VkLayer_LS_frame_generation.patch b/scripts/flatpak/VkLayer_LS_frame_generation.patch new file mode 100644 index 0000000..7536718 --- /dev/null +++ b/scripts/flatpak/VkLayer_LS_frame_generation.patch @@ -0,0 +1,13 @@ +diff --git a/VkLayer_LS_frame_generation.json b/VkLayer_LS_frame_generation.json +index ece2a5f..774a027 100644 +--- a/VkLayer_LS_frame_generation.json ++++ b/VkLayer_LS_frame_generation.json +@@ -4,7 +4,7 @@ + "name": "VK_LAYER_LS_frame_generation", + "type": "GLOBAL", + "api_version": "1.4.313", +- "library_path": "liblsfg-vk.so", ++ "library_path": "/usr/lib/extensions/vulkan/lsfgvk/lib/liblsfg-vk.so", + "implementation_version": "1", + "description": "Lossless Scaling frame generation layer", + "functions": { diff --git a/scripts/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_23.08.yml b/scripts/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_23.08.yml new file mode 100644 index 0000000..5ff2d12 --- /dev/null +++ b/scripts/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_23.08.yml @@ -0,0 +1,30 @@ +id: org.freedesktop.Platform.VulkanLayer.lsfgvk +branch: '23.08' +runtime: org.freedesktop.Platform +runtime-version: '23.08' +sdk: org.freedesktop.Sdk +build-extension: true + +sdk-extensions: + - org.freedesktop.Sdk.Extension.llvm18 + +build-options: + prefix: /usr/lib/extensions/vulkan/lsfgvk + +modules: + - name: lsfg-vk + buildsystem: cmake-ninja + build-options: + append-path: /usr/lib/sdk/llvm18/bin + prepend-ld-library-path: /usr/lib/sdk/llvm18/lib + config-opts: + - -DCMAKE_BUILD_TYPE=Release + - -DCMAKE_C_COMPILER=clang + - -DCMAKE_CXX_COMPILER=clang++ + - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On + sources: + - type: git + url: https://github.com/PancakeTAS/lsfg-vk.git + branch: release + - type: patch + path: VkLayer_LS_frame_generation.patch diff --git a/scripts/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_24.08.yml b/scripts/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_24.08.yml new file mode 100644 index 0000000..156e94c --- /dev/null +++ b/scripts/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_24.08.yml @@ -0,0 +1,30 @@ +id: org.freedesktop.Platform.VulkanLayer.lsfgvk +branch: '24.08' +runtime: org.freedesktop.Platform +runtime-version: '24.08' +sdk: org.freedesktop.Sdk +build-extension: true + +sdk-extensions: + - org.freedesktop.Sdk.Extension.llvm20 + +build-options: + prefix: /usr/lib/extensions/vulkan/lsfgvk + +modules: + - name: lsfg-vk + buildsystem: cmake-ninja + build-options: + append-path: /usr/lib/sdk/llvm20/bin + prepend-ld-library-path: /usr/lib/sdk/llvm20/lib + config-opts: + - -DCMAKE_BUILD_TYPE=Release + - -DCMAKE_C_COMPILER=clang + - -DCMAKE_CXX_COMPILER=clang++ + - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=On + sources: + - type: git + url: https://github.com/PancakeTAS/lsfg-vk.git + branch: release + - type: patch + path: VkLayer_LS_frame_generation.patch diff --git a/scripts/package/alpm.PKGINFO b/scripts/package/alpm.PKGINFO new file mode 100644 index 0000000..578c549 --- /dev/null +++ b/scripts/package/alpm.PKGINFO @@ -0,0 +1,12 @@ +pkgname = lsfg-vk +pkgbase = lsfg-vk +pkgver = ${VERSION}-1 +pkgdesc = "Lossless Scaling Frame Generation on Linux via DXVK/Vulkan" +url = https://discord.gg/losslessscaling +packager = "PancakeTAS " +arch = x86_64 +license = MIT +depend = vulkan-icd-loader +depend = libadwaita +depend = gtk4 +depend = glibc diff --git a/scripts/package/dpkg.control b/scripts/package/dpkg.control new file mode 100644 index 0000000..4f0a0f6 --- /dev/null +++ b/scripts/package/dpkg.control @@ -0,0 +1,12 @@ +Package: lsfg-vk +Description: Lossless Scaling Frame Generation on Linux via DXVK/Vulkan +Homepage: https://discord.gg/losslessscaling +Maintainer: "PancakeTAS " +Version: ${VERSION}-1 +Architecture: amd64 +Section: utils +Priority: optional +Depends: libc6 (>= 2.38-1), + libvulkan1, + libadwaita-1-0, + libgtk-4-1 diff --git a/scripts/package/package.sh b/scripts/package/package.sh new file mode 100755 index 0000000..2d24d24 --- /dev/null +++ b/scripts/package/package.sh @@ -0,0 +1,73 @@ +#!/bin/bash +if [ -z "$VERSION" ]; then + echo "VERSION environment variable is not set." + exit 1 +fi + +set -eux + +# set permission bits +chmod 755 bin/lsfg-vk-ui +chmod 755 lib/liblsfg-vk.so +chmod 644 share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json +chmod 644 share/applications/lsfg-vk-ui.desktop +chmod 644 share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png + +# build alpm package +echo "Building ALPM package..." + +mkdir -pv alpm +envsubst < scripts/package/alpm.PKGINFO > alpm/.PKGINFO + +mkdir -pv alpm/usr/{bin,lib,share/vulkan/implicit_layer.d,share/applications,share/icons/hicolor/256x256/apps} +cp -v bin/lsfg-vk-ui alpm/usr/bin/lsfg-vk-ui +cp -v lib/liblsfg-vk.so alpm/usr/lib/liblsfg-vk.so +cp -v share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json \ + alpm/usr/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json +cp -v share/applications/lsfg-vk-ui.desktop \ + alpm/usr/share/applications/lsfg-vk-ui.desktop +cp -v share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png \ + alpm/usr/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png + +tar -cvzf "lsfg-vk-$VERSION.x86_64.tar.zst" -C alpm \ + .PKGINFO usr + +# build dpkg package +echo "Building DEB package..." + +mkdir -pv deb/DEBIAN +envsubst < scripts/package/dpkg.control > deb/DEBIAN/control + +mkdir -pv deb/usr/{bin,lib,share/vulkan/implicit_layer.d,share/applications,share/icons/hicolor/256x256/apps} +cp -v bin/lsfg-vk-ui deb/usr/bin/lsfg-vk-ui +cp -v lib/liblsfg-vk.so deb/usr/lib/liblsfg-vk.so +cp -v share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json \ + deb/usr/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json +cp -v share/applications/lsfg-vk-ui.desktop \ + deb/usr/share/applications/lsfg-vk-ui.desktop +cp -v share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png \ + deb/usr/share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png + +dpkg-deb --root-owner-group --build deb "lsfg-vk-$VERSION.x86_64.deb" + +# build rpm package +echo "Building RPM package..." + +mkdir -pv rpm +envsubst < scripts/package/rpm.spec > rpm/lsfg-vk.spec + +mkdir -pv rpm/SOURCES +cp -v bin/lsfg-vk-ui rpm/SOURCES +cp -v lib/liblsfg-vk.so rpm/SOURCES +cp -v share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json \ + rpm/SOURCES +cp -v share/applications/lsfg-vk-ui.desktop \ + rpm/SOURCES/lsfg-vk-ui.desktop +cp -v share/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png \ + rpm/SOURCES/gay.pancake.lsfg-vk-ui.png + +rpmbuild -bb rpm/lsfg-vk.spec --define "_topdir $(pwd)/rpm" +mv -v "rpm/RPMS/x86_64/lsfg-vk-$VERSION-1.x86_64.rpm" "lsfg-vk-$VERSION.x86_64.rpm" + +# cleanup +rm -rf alpm deb rpm diff --git a/scripts/package/rpm.spec b/scripts/package/rpm.spec new file mode 100644 index 0000000..859c7d4 --- /dev/null +++ b/scripts/package/rpm.spec @@ -0,0 +1,33 @@ +Name: lsfg-vk +Summary: Lossless Scaling Frame Generation on Linux via DXVK/Vulkan +Version: ${VERSION} +Release: 1%{?dist} +URL: https://discord.gg/losslessscaling +Packager: "PancakeTAS " +License: MIT +Group: Applications/System +BuildArch: x86_64 + +Requires: glibc >= 2.38 +Requires: vulkan-loader +Requires: libadwaita +Requires: gtk4 + +%global __strip /bin/true + +%description +Lossless Scaling Frame Generation on Linux via DXVK/Vulkan. + +%install +install -Dm755 %{_sourcedir}/lsfg-vk-ui %{buildroot}%{_bindir}/lsfg-vk-ui +install -Dm755 %{_sourcedir}/liblsfg-vk.so %{buildroot}%{_libdir}/liblsfg-vk.so +install -Dm644 %{_sourcedir}/VkLayer_LS_frame_generation.json %{buildroot}%{_datadir}/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json +install -Dm644 %{_sourcedir}/lsfg-vk-ui.desktop %{buildroot}%{_datadir}/applications/lsfg-vk-ui.desktop +install -Dm644 %{_sourcedir}/gay.pancake.lsfg-vk-ui.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png + +%files +%{_bindir}/lsfg-vk-ui +%{_libdir}/liblsfg-vk.so +%{_datadir}/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json +%{_datadir}/applications/lsfg-vk-ui.desktop +%{_datadir}/icons/hicolor/256x256/apps/gay.pancake.lsfg-vk-ui.png diff --git a/src/config/config.cpp b/src/config/config.cpp new file mode 100644 index 0000000..ea98a81 --- /dev/null +++ b/src/config/config.cpp @@ -0,0 +1,160 @@ +#include "config/config.hpp" +#include "common/exception.hpp" + +#include "config/default_conf.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Config; + +namespace { + Configuration globalConf{}; + std::optional> gameConfs; +} + +Configuration Config::activeConf{}; + +namespace { + /// Turn a string into a VkPresentModeKHR enum value. + VkPresentModeKHR into_present(const std::string& mode) { + if (mode == "fifo" || mode == "vsync") + return VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR; + if (mode == "mailbox") + return VkPresentModeKHR::VK_PRESENT_MODE_MAILBOX_KHR; + if (mode == "immediate") + return VkPresentModeKHR::VK_PRESENT_MODE_IMMEDIATE_KHR; + return VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR; + } +} + +void Config::updateConfig(const std::string& file) { + if (!std::filesystem::exists(file)) { + std::cerr << "lsfg-vk: Placing default configuration file at " << file << '\n'; + const auto parent = std::filesystem::path(file).parent_path(); + if (!std::filesystem::exists(parent)) + if (!std::filesystem::create_directories(parent)) + throw std::runtime_error("Unable to create configuration directory at " + parent.string()); + + std::ofstream out(file); + if (!out.is_open()) + throw std::runtime_error("Unable to create configuration file at " + file); + out << DEFAULT_CONFIG; + out.close(); + } + + // parse config file + std::optional parsed; + try { + parsed.emplace(toml::parse(file)); + if (!parsed->contains("version")) + throw std::runtime_error("Configuration file is missing 'version' field"); + if (parsed->at("version").as_integer() != 1) + throw std::runtime_error("Configuration file version is not supported, expected 1"); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Unable to parse configuration file", e); + } + auto& toml = *parsed; + + // parse global configuration + const toml::value globalTable = toml::find_or_default(toml, "global"); + const Configuration global{ + .dll = toml::find_or(globalTable, "dll", std::string()), + .config_file = file, + .timestamp = std::filesystem::last_write_time(file) + }; + + // validate global configuration + if (global.multiplier < 2) + throw std::runtime_error("Global Multiplier cannot be less than 2"); + if (global.flowScale < 0.25F || global.flowScale > 1.0F) + throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); + + // parse game-specific configuration + std::unordered_map games; + const toml::value gamesList = toml::find_or_default(toml, "game"); + for (const auto& gameTable : gamesList.as_array()) { + if (!gameTable.is_table()) + throw std::runtime_error("Invalid game configuration entry"); + if (!gameTable.contains("exe")) + throw std::runtime_error("Game override missing 'exe' field"); + + const std::string exe = toml::find(gameTable, "exe"); + Configuration game{ + .enable = true, + .dll = global.dll, + .multiplier = toml::find_or(gameTable, "multiplier", 2U), + .flowScale = toml::find_or(gameTable, "flow_scale", 1.0F), + .performance = toml::find_or(gameTable, "performance_mode", false), + .hdr = toml::find_or(gameTable, "hdr_mode", false), + .e_present = into_present(toml::find_or(gameTable, "experimental_present_mode", "")), + .config_file = file, + .timestamp = global.timestamp + }; + + // validate the configuration + if (game.multiplier < 1) + throw std::runtime_error("Multiplier cannot be less than 1"); + if (game.flowScale < 0.25F || game.flowScale > 1.0F) + throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); + games[exe] = std::move(game); + } + + // store configurations + globalConf = global; + gameConfs = std::move(games); +} + +Configuration Config::getConfig(const std::pair& name) { + // process legacy environment variables + if (std::getenv("LSFG_LEGACY")) { + Configuration conf{ + .enable = true, + .multiplier = 2, + .flowScale = 1.0F, + .e_present = VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR + }; + + const char* dll = std::getenv("LSFG_DLL_PATH"); + if (dll) conf.dll = std::string(dll); + const char* multiplier = std::getenv("LSFG_MULTIPLIER"); + if (multiplier) conf.multiplier = std::stoul(multiplier); + const char* flow_scale = std::getenv("LSFG_FLOW_SCALE"); + if (flow_scale) conf.flowScale = std::stof(flow_scale); + const char* performance = std::getenv("LSFG_PERFORMANCE_MODE"); + if (performance) conf.performance = std::string(performance) == "1"; + const char* hdr = std::getenv("LSFG_HDR_MODE"); + if (hdr) conf.hdr = std::string(hdr) == "1"; + const char* e_present = std::getenv("LSFG_EXPERIMENTAL_PRESENT_MODE"); + if (e_present) conf.e_present = into_present(std::string(e_present)); + + return conf; + } + + // process new configuration system + if (!gameConfs.has_value()) + return globalConf; + + const auto& games = *gameConfs; + auto it = std::ranges::find_if(games, [&name](const auto& pair) { + return name.first.ends_with(pair.first) || (name.second == pair.first); + }); + if (it != games.end()) + return it->second; + + return globalConf; +} diff --git a/src/context.cpp b/src/context.cpp new file mode 100644 index 0000000..e00804a --- /dev/null +++ b/src/context.cpp @@ -0,0 +1,238 @@ +#include "context.hpp" +#include "config/config.hpp" +#include "common/exception.hpp" +#include "extract/extract.hpp" +#include "extract/trans.hpp" +#include "utils/utils.hpp" +#include "hooks.hpp" +#include "layer.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, + VkExtent2D extent, const std::vector& swapchainImages) + : swapchain(swapchain), swapchainImages(swapchainImages), + extent(extent) { + // get updated configuration + auto& conf = Config::activeConf; + if (!conf.config_file.empty() + && ( + !std::filesystem::exists(conf.config_file) + || conf.timestamp != std::filesystem::last_write_time(conf.config_file) + )) { + std::cerr << "lsfg-vk: Rereading configuration, as it is no longer valid.\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // reread configuration + const std::string file = Utils::getConfigFile(); + const auto name = Utils::getProcessName(); + try { + Config::updateConfig(file); + conf = Config::getConfig(name); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: Failed to update configuration, continuing using old:\n"; + std::cerr << "- " << e.what() << '\n'; + } + + LSFG_3_1P::finalize(); + LSFG_3_1::finalize(); + + // print config + std::cerr << "lsfg-vk: Reloaded configuration for " << name.second << ":\n"; + if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n'; + std::cerr << " Multiplier: " << conf.multiplier << '\n'; + std::cerr << " Flow Scale: " << conf.flowScale << '\n'; + std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; + std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; + if (conf.e_present != 2) std::cerr << " ! Present Mode: " << conf.e_present << '\n'; + + if (conf.multiplier <= 1) return; + } + // we could take the format from the swapchain, + // but honestly this is safer. + const VkFormat format = conf.hdr + ? VK_FORMAT_R8G8B8A8_UNORM + : VK_FORMAT_R16G16B16A16_SFLOAT; + + // prepare textures for lsfg + std::array fds{}; + this->frame_0 = Mini::Image(info.device, info.physicalDevice, + extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, + &fds.at(0)); + this->frame_1 = Mini::Image(info.device, info.physicalDevice, + extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, + &fds.at(1)); + + std::vector outFds(conf.multiplier - 1); + for (size_t i = 0; i < (conf.multiplier - 1); ++i) + this->out_n.emplace_back(info.device, info.physicalDevice, + extent, format, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_ASPECT_COLOR_BIT, + &outFds.at(i)); + + // initialize lsfg + auto* lsfgInitialize = LSFG_3_1::initialize; + auto* lsfgCreateContext = LSFG_3_1::createContext; + auto* lsfgDeleteContext = LSFG_3_1::deleteContext; + if (conf.performance) { + lsfgInitialize = LSFG_3_1P::initialize; + lsfgCreateContext = LSFG_3_1P::createContext; + lsfgDeleteContext = LSFG_3_1P::deleteContext; + } + + setenv("DISABLE_LSFG", "1", 1); // NOLINT + + lsfgInitialize( + Utils::getDeviceUUID(info.physicalDevice), + conf.hdr, 1.0F / conf.flowScale, conf.multiplier - 1, + [](const std::string& name) { + auto dxbc = Extract::getShader(name); + auto spirv = Extract::translateShader(dxbc); + return spirv; + } + ); + + this->lsfgCtxId = std::shared_ptr( + new int32_t(lsfgCreateContext(fds.at(0), fds.at(1), outFds, extent, format)), + [lsfgDeleteContext = lsfgDeleteContext](const int32_t* id) { + lsfgDeleteContext(*id); + } + ); + + unsetenv("DISABLE_LSFG"); // NOLINT + + // prepare render passes + this->cmdPool = Mini::CommandPool(info.device, info.queue.first); + for (size_t i = 0; i < 8; i++) { + auto& pass = this->passInfos.at(i); + pass.renderSemaphores.resize(conf.multiplier - 1); + pass.acquireSemaphores.resize(conf.multiplier - 1); + pass.postCopyBufs.resize(conf.multiplier - 1); + pass.postCopySemaphores.resize(conf.multiplier - 1); + pass.prevPostCopySemaphores.resize(conf.multiplier - 1); + } +} + +VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue, + const std::vector& gameRenderSemaphores, uint32_t presentIdx) { + const auto& conf = Config::activeConf; + auto& pass = this->passInfos.at(this->frameIdx % 8); + + // 1. copy swapchain image to frame_0/frame_1 + int preCopySemaphoreFd{}; + pass.preCopySemaphores.at(0) = Mini::Semaphore(info.device, &preCopySemaphoreFd); + pass.preCopySemaphores.at(1) = Mini::Semaphore(info.device); + pass.preCopyBuf = Mini::CommandBuffer(info.device, this->cmdPool); + pass.preCopyBuf.begin(); + + Utils::copyImage(pass.preCopyBuf.handle(), + this->swapchainImages.at(presentIdx), + this->frameIdx % 2 == 0 ? this->frame_0.handle() : this->frame_1.handle(), + this->extent.width, this->extent.height, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + true, false); + + pass.preCopyBuf.end(); + + std::vector gameRenderSemaphores2 = gameRenderSemaphores; + if (this->frameIdx > 0) + gameRenderSemaphores2.emplace_back(this->passInfos.at((this->frameIdx - 1) % 8) + .preCopySemaphores.at(1).handle()); + pass.preCopyBuf.submit(info.queue.second, + gameRenderSemaphores2, + { pass.preCopySemaphores.at(0).handle(), + pass.preCopySemaphores.at(1).handle() }); + + // 2. render intermediary frames + std::vector renderSemaphoreFds(conf.multiplier - 1); + for (size_t i = 0; i < (conf.multiplier - 1); ++i) + pass.renderSemaphores.at(i) = Mini::Semaphore(info.device, &renderSemaphoreFds.at(i)); + + if (conf.performance) + LSFG_3_1P::presentContext(*this->lsfgCtxId, + preCopySemaphoreFd, + renderSemaphoreFds); + else + LSFG_3_1::presentContext(*this->lsfgCtxId, + preCopySemaphoreFd, + renderSemaphoreFds); + + for (size_t i = 0; i < (conf.multiplier - 1); i++) { + // 3. acquire next swapchain image + pass.acquireSemaphores.at(i) = Mini::Semaphore(info.device); + uint32_t imageIdx{}; + auto res = Layer::ovkAcquireNextImageKHR(info.device, this->swapchain, UINT64_MAX, + pass.acquireSemaphores.at(i).handle(), VK_NULL_HANDLE, &imageIdx); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image"); + + // 4. copy output image to swapchain image + pass.postCopySemaphores.at(i) = Mini::Semaphore(info.device); + pass.prevPostCopySemaphores.at(i) = Mini::Semaphore(info.device); + pass.postCopyBufs.at(i) = Mini::CommandBuffer(info.device, this->cmdPool); + pass.postCopyBufs.at(i).begin(); + + Utils::copyImage(pass.postCopyBufs.at(i).handle(), + this->out_n.at(i).handle(), + this->swapchainImages.at(imageIdx), + this->extent.width, this->extent.height, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + false, true); + + pass.postCopyBufs.at(i).end(); + pass.postCopyBufs.at(i).submit(info.queue.second, + { pass.acquireSemaphores.at(i).handle(), + pass.renderSemaphores.at(i).handle() }, + { pass.postCopySemaphores.at(i).handle(), + pass.prevPostCopySemaphores.at(i).handle() }); + + // 5. present swapchain image + std::vector waitSemaphores{ pass.postCopySemaphores.at(i).handle() }; + if (i != 0) waitSemaphores.emplace_back(pass.prevPostCopySemaphores.at(i - 1).handle()); + + const VkPresentInfoKHR presentInfo{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = i == 0 ? pNext : nullptr, // only set on first present + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphores.data(), + .swapchainCount = 1, + .pSwapchains = &this->swapchain, + .pImageIndices = &imageIdx, + }; + res = Layer::ovkQueuePresentKHR(queue, &presentInfo); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + throw LSFG::vulkan_error(res, "Failed to present swapchain image"); + } + + // 6. present actual next frame + VkSemaphore lastPrevPostCopySemaphore = + pass.prevPostCopySemaphores.at(conf.multiplier - 1 - 1).handle(); + const VkPresentInfoKHR presentInfo{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &lastPrevPostCopySemaphore, + .swapchainCount = 1, + .pSwapchains = &this->swapchain, + .pImageIndices = &presentIdx, + }; + auto res = Layer::ovkQueuePresentKHR(queue, &presentInfo); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + throw LSFG::vulkan_error(res, "Failed to present swapchain image"); + + this->frameIdx++; + return res; +} diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp new file mode 100644 index 0000000..35d1635 --- /dev/null +++ b/src/extract/extract.cpp @@ -0,0 +1,148 @@ +#include "extract/extract.hpp" +#include "config/config.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Extract; + +const std::unordered_map nameIdxTable = {{ + { "mipmaps", 255 }, + { "alpha[0]", 267 }, + { "alpha[1]", 268 }, + { "alpha[2]", 269 }, + { "alpha[3]", 270 }, + { "beta[0]", 275 }, + { "beta[1]", 276 }, + { "beta[2]", 277 }, + { "beta[3]", 278 }, + { "beta[4]", 279 }, + { "gamma[0]", 257 }, + { "gamma[1]", 259 }, + { "gamma[2]", 260 }, + { "gamma[3]", 261 }, + { "gamma[4]", 262 }, + { "delta[0]", 257 }, + { "delta[1]", 263 }, + { "delta[2]", 264 }, + { "delta[3]", 265 }, + { "delta[4]", 266 }, + { "delta[5]", 258 }, + { "delta[6]", 271 }, + { "delta[7]", 272 }, + { "delta[8]", 273 }, + { "delta[9]", 274 }, + { "generate", 256 }, + { "p_mipmaps", 255 }, + { "p_alpha[0]", 290 }, + { "p_alpha[1]", 291 }, + { "p_alpha[2]", 292 }, + { "p_alpha[3]", 293 }, + { "p_beta[0]", 298 }, + { "p_beta[1]", 299 }, + { "p_beta[2]", 300 }, + { "p_beta[3]", 301 }, + { "p_beta[4]", 302 }, + { "p_gamma[0]", 280 }, + { "p_gamma[1]", 282 }, + { "p_gamma[2]", 283 }, + { "p_gamma[3]", 284 }, + { "p_gamma[4]", 285 }, + { "p_delta[0]", 280 }, + { "p_delta[1]", 286 }, + { "p_delta[2]", 287 }, + { "p_delta[3]", 288 }, + { "p_delta[4]", 289 }, + { "p_delta[5]", 281 }, + { "p_delta[6]", 294 }, + { "p_delta[7]", 295 }, + { "p_delta[8]", 296 }, + { "p_delta[9]", 297 }, + { "p_generate", 256 }, +}}; + +namespace { + auto& shaders() { + static std::unordered_map> shaderData; + return shaderData; + } + + int on_resource(void*, const peparse::resource& res) { + if (res.type != peparse::RT_RCDATA || res.buf == nullptr || res.buf->bufLen <= 0) + return 0; + std::vector resource_data(res.buf->bufLen); + std::copy_n(res.buf->buf, res.buf->bufLen, resource_data.data()); + shaders()[res.name] = resource_data; + return 0; + } + + const std::vector PATHS{{ + ".local/share/Steam/steamapps/common", + ".steam/steam/steamapps/common", + ".steam/debian-installation/steamapps/common", + ".var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common", + "snap/steam/common/.local/share/Steam/steamapps/common" + }}; + + std::string getDllPath() { + // overriden path + std::string dllPath = Config::activeConf.dll; + if (!dllPath.empty()) + return dllPath; + // home based paths + const char* home = getenv("HOME"); + const std::string homeStr = home ? home : ""; + for (const auto& base : PATHS) { + const std::filesystem::path path = + std::filesystem::path(homeStr) / base / "Lossless Scaling" / "Lossless.dll"; + if (std::filesystem::exists(path)) + return path.string(); + } + // xdg home + const char* dataDir = getenv("XDG_DATA_HOME"); + if (dataDir && *dataDir != '\0') + return std::string(dataDir) + "/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; + // final fallback + return "Lossless.dll"; + } +} + +void Extract::extractShaders() { + if (!shaders().empty()) + return; + + // parse the dll + peparse::parsed_pe* dll = peparse::ParsePEFromFile(getDllPath().c_str()); + if (!dll) + throw std::runtime_error("Unable to read Lossless.dll, is it installed?"); + peparse::IterRsrc(dll, on_resource, nullptr); + peparse::DestructParsedPE(dll); + + // ensure all shaders are present + for (const auto& [name, idx] : nameIdxTable) + if (shaders().find(idx) == shaders().end()) + throw std::runtime_error("Shader not found: " + name + ".\n- Is Lossless Scaling up to date?"); +} + +std::vector Extract::getShader(const std::string& name) { + if (shaders().empty()) + throw std::runtime_error("Shaders are not loaded."); + + auto hit = nameIdxTable.find(name); + if (hit == nameIdxTable.end()) + throw std::runtime_error("Shader hash not found: " + name); + + auto sit = shaders().find(hit->second); + if (sit == shaders().end()) + throw std::runtime_error("Shader not found: " + name); + + return sit->second; +} diff --git a/src/extract/trans.cpp b/src/extract/trans.cpp new file mode 100644 index 0000000..1e513c5 --- /dev/null +++ b/src/extract/trans.cpp @@ -0,0 +1,76 @@ +#include "extract/trans.hpp" + +#include + +#include +#include +#include + +#include +#include +#include +#include + +using namespace Extract; + +struct BindingOffsets { + uint32_t bindingIndex{}; + uint32_t bindingOffset{}; + uint32_t setIndex{}; + uint32_t setOffset{}; +}; + +std::vector Extract::translateShader(std::vector bytecode) { + // compile the shader + dxvk::DxbcReader reader(reinterpret_cast(bytecode.data()), bytecode.size()); + dxvk::DxbcModule module(reader); + const dxvk::DxbcModuleInfo info{}; + auto code = module.compile(info, "CS"); + + // find all bindings + std::vector bindingOffsets; + std::vector varIds; + for (auto ins : code) { + if (ins.opCode() == spv::OpDecorate) { + if (ins.arg(2) == spv::DecorationBinding) { + const uint32_t varId = ins.arg(1); + bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1))); + bindingOffsets[varId].bindingIndex = ins.arg(3); + bindingOffsets[varId].bindingOffset = ins.offset() + 3; + varIds.push_back(varId); + } + + if (ins.arg(2) == spv::DecorationDescriptorSet) { + const uint32_t varId = ins.arg(1); + bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1))); + bindingOffsets[varId].setIndex = ins.arg(3); + bindingOffsets[varId].setOffset = ins.offset() + 3; + } + } + + if (ins.opCode() == spv::OpFunction) + break; + } + + std::vector validBindings; + for (const auto varId : varIds) { + auto info = bindingOffsets[varId]; + + if (info.bindingOffset) + validBindings.push_back(info); + } + + // patch binding offset + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + for (size_t i = 0; i < validBindings.size(); i++) + code.data()[validBindings.at(i).bindingOffset] // NOLINT + = static_cast(i); + #pragma clang diagnostic pop + + // return the new bytecode + std::vector spirvBytecode(code.size()); + std::copy_n(reinterpret_cast(code.data()), + code.size(), spirvBytecode.data()); + return spirvBytecode; +} diff --git a/src/hooks.cpp b/src/hooks.cpp new file mode 100644 index 0000000..bd1368e --- /dev/null +++ b/src/hooks.cpp @@ -0,0 +1,325 @@ +#include "hooks.hpp" +#include "common/exception.hpp" +#include "config/config.hpp" +#include "utils/utils.hpp" +#include "context.hpp" +#include "layer.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Hooks; + +namespace { + + /// + /// Add extensions to the instance create info. + /// + VkResult myvkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) { + auto extensions = Utils::addExtensions( + pCreateInfo->ppEnabledExtensionNames, + pCreateInfo->enabledExtensionCount, + { + "VK_KHR_get_physical_device_properties2", + "VK_KHR_external_memory_capabilities", + "VK_KHR_external_semaphore_capabilities" + } + ); + VkInstanceCreateInfo createInfo = *pCreateInfo; + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + auto res = Layer::ovkCreateInstance(&createInfo, pAllocator, pInstance); + if (res == VK_ERROR_EXTENSION_NOT_PRESENT) + throw std::runtime_error( + "Required Vulkan instance extensions are not present." + "Your GPU driver is not supported."); + return res; + } + + /// Map of devices to related information. + std::unordered_map deviceToInfo; + + /// + /// Add extensions to the device create info. + /// (function pointers are not initialized yet) + /// + VkResult myvkCreateDevicePre( + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice) { + // add extensions + auto extensions = Utils::addExtensions( + pCreateInfo->ppEnabledExtensionNames, + pCreateInfo->enabledExtensionCount, + { + "VK_KHR_external_memory", + "VK_KHR_external_memory_fd", + "VK_KHR_external_semaphore", + "VK_KHR_external_semaphore_fd" + } + ); + VkDeviceCreateInfo createInfo = *pCreateInfo; + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + auto res = Layer::ovkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice); + if (res == VK_ERROR_EXTENSION_NOT_PRESENT) + throw std::runtime_error( + "Required Vulkan device extensions are not present." + "Your GPU driver is not supported."); + return res; + } + + /// + /// Add related device information after the device is created. + /// + VkResult myvkCreateDevicePost( + VkPhysicalDevice physicalDevice, + VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks*, + VkDevice* pDevice) { + deviceToInfo.emplace(*pDevice, DeviceInfo { + .device = *pDevice, + .physicalDevice = physicalDevice, + .queue = Utils::findQueue(*pDevice, physicalDevice, pCreateInfo, VK_QUEUE_GRAPHICS_BIT) + }); + return VK_SUCCESS; + } + + /// Erase the device information when the device is destroyed. + void myvkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) noexcept { + deviceToInfo.erase(device); + Layer::ovkDestroyDevice(device, pAllocator); + } + + std::unordered_map swapchains; + std::unordered_map swapchainToDeviceTable; + std::unordered_map swapchainToPresent; + + /// + /// Adjust swapchain creation parameters and create a swapchain context. + /// + VkResult myvkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain) noexcept { + // find device + auto it = deviceToInfo.find(device); + if (it == deviceToInfo.end()) { + Utils::logLimitN("swapMap", 5, "Device not found in map"); + return Layer::ovkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + } + Utils::resetLimitN("swapMap"); + auto& deviceInfo = it->second; + + // increase amount of images in swapchain + VkSwapchainCreateInfoKHR createInfo = *pCreateInfo; + const auto maxImages = Utils::getMaxImageCount( + deviceInfo.physicalDevice, pCreateInfo->surface); + createInfo.minImageCount = createInfo.minImageCount + 1 + + static_cast(deviceInfo.queue.first); + if (createInfo.minImageCount > maxImages) { + createInfo.minImageCount = maxImages; + Utils::logLimitN("swapCount", 10, + "Requested image count (" + + std::to_string(pCreateInfo->minImageCount) + ") " + "exceeds maximum allowed (" + + std::to_string(maxImages) + "). " + "Continuing with maximum allowed image count. " + "This might lead to performance degradation."); + } else { + Utils::resetLimitN("swapCount"); + } + + // allow copy operations on swapchain images + createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + + // enforce present mode + createInfo.presentMode = Config::activeConf.e_present; + + // retire potential old swapchain + if (pCreateInfo->oldSwapchain) { + swapchains.erase(pCreateInfo->oldSwapchain); + swapchainToDeviceTable.erase(pCreateInfo->oldSwapchain); + } + + // create swapchain + auto res = Layer::ovkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); + if (res != VK_SUCCESS) + return res; // can't be caused by lsfg-vk (yet) + + try { + swapchainToPresent.emplace(*pSwapchain, createInfo.presentMode); + + // get all swapchain images + uint32_t imageCount{}; + res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr); + if (res != VK_SUCCESS || imageCount == 0) + throw LSFG::vulkan_error(res, "Failed to get swapchain image count"); + + std::vector swapchainImages(imageCount); + res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, + &imageCount, swapchainImages.data()); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to get swapchain images"); + + // create swapchain context + swapchainToDeviceTable.emplace(*pSwapchain, device); + swapchains.emplace(*pSwapchain, LsContext( + deviceInfo, *pSwapchain, pCreateInfo->imageExtent, + swapchainImages + )); + + std::cerr << "lsfg-vk: Swapchain context " << + (createInfo.oldSwapchain ? "recreated" : "created") + << " (using " << imageCount << " images).\n"; + + Utils::resetLimitN("swapCtxCreate"); + } catch (const std::exception& e) { + Utils::logLimitN("swapCtxCreate", 5, + "An error occurred while creating the swapchain wrapper:\n" + "- " + std::string(e.what())); + return VK_SUCCESS; // swapchain is still valid + } + return VK_SUCCESS; + } + + /// + /// Update presentation parameters and present the next frame(s). + /// + VkResult myvkQueuePresentKHR( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo) noexcept { + // find swapchain device + auto it = swapchainToDeviceTable.find(*pPresentInfo->pSwapchains); + if (it == swapchainToDeviceTable.end()) { + Utils::logLimitN("swapMap", 5, + "Swapchain not found in map"); + return Layer::ovkQueuePresentKHR(queue, pPresentInfo); + } + + // find device info + auto it2 = deviceToInfo.find(it->second); + if (it2 == deviceToInfo.end()) { + Utils::logLimitN("swapMap", 5, + "Device not found in map"); + return Layer::ovkQueuePresentKHR(queue, pPresentInfo); + } + auto& deviceInfo = it2->second; + + // find swapchain context + auto it3 = swapchains.find(*pPresentInfo->pSwapchains); + if (it3 == swapchains.end()) { + Utils::logLimitN("swapMap", 5, + "Swapchain context not found in map"); + return Layer::ovkQueuePresentKHR(queue, pPresentInfo); + } + auto& swapchain = it3->second; + + // find present mode + auto it4 = swapchainToPresent.find(*pPresentInfo->pSwapchains); + if (it4 == swapchainToPresent.end()) { + Utils::logLimitN("swapMap", 5, + "Swapchain present mode not found in map"); + return Layer::ovkQueuePresentKHR(queue, pPresentInfo); + } + auto& present = it4->second; + + // enforce present mode | NOLINTBEGIN + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + const VkSwapchainPresentModeInfoEXT* presentModeInfo = + reinterpret_cast(pPresentInfo->pNext); + while (presentModeInfo) { + if (presentModeInfo->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT) { + for (size_t i = 0; i < presentModeInfo->swapchainCount; i++) + const_cast(presentModeInfo->pPresentModes)[i] = + present; + } + presentModeInfo = + reinterpret_cast(presentModeInfo->pNext); + } + #pragma clang diagnostic pop + + // NOLINTEND | present the next frame + VkResult res{}; // might return VK_SUBOPTIMAL_KHR + try { + // ensure config is valid + auto& conf = Config::activeConf; + if (!conf.config_file.empty() + && ( + !std::filesystem::exists(conf.config_file) + || conf.timestamp != std::filesystem::last_write_time(conf.config_file) + )) { + Layer::ovkQueuePresentKHR(queue, pPresentInfo); + return VK_ERROR_OUT_OF_DATE_KHR; + } + + // ensure present mode is still valid + if (present != conf.e_present) { + Layer::ovkQueuePresentKHR(queue, pPresentInfo); + return VK_ERROR_OUT_OF_DATE_KHR; + } + + // skip if disabled + if (conf.multiplier <= 1) + return Layer::ovkQueuePresentKHR(queue, pPresentInfo); + + // present the swapchain + std::vector semaphores(pPresentInfo->waitSemaphoreCount); + std::copy_n(pPresentInfo->pWaitSemaphores, semaphores.size(), semaphores.data()); + + res = swapchain.present(deviceInfo, pPresentInfo->pNext, + queue, semaphores, *pPresentInfo->pImageIndices); + + Utils::resetLimitN("swapPresent"); + } catch (const std::exception& e) { + Utils::logLimitN("swapPresent", 5, + "An error occurred while presenting the swapchain:\n" + "- " + std::string(e.what())); + return VK_ERROR_INITIALIZATION_FAILED; + } + return res; + } + + /// Erase the swapchain context and mapping when the swapchain is destroyed. + void myvkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator) noexcept { + swapchains.erase(swapchain); + swapchainToDeviceTable.erase(swapchain); + swapchainToPresent.erase(swapchain); + Layer::ovkDestroySwapchainKHR(device, swapchain, pAllocator); + } +} + +std::unordered_map Hooks::hooks = { + // instance hooks + {"vkCreateInstance", reinterpret_cast(myvkCreateInstance)}, + + // device hooks + {"vkCreateDevicePre", reinterpret_cast(myvkCreateDevicePre)}, + {"vkCreateDevicePost", reinterpret_cast(myvkCreateDevicePost)}, + {"vkDestroyDevice", reinterpret_cast(myvkDestroyDevice)}, + + // swapchain hooks + {"vkCreateSwapchainKHR", reinterpret_cast(myvkCreateSwapchainKHR)}, + {"vkQueuePresentKHR", reinterpret_cast(myvkQueuePresentKHR)}, + {"vkDestroySwapchainKHR", reinterpret_cast(myvkDestroySwapchainKHR)} +}; diff --git a/src/layer.cpp b/src/layer.cpp new file mode 100644 index 0000000..9c2694b --- /dev/null +++ b/src/layer.cpp @@ -0,0 +1,535 @@ +#include "layer.hpp" +#include "common/exception.hpp" +#include "config/config.hpp" +#include "hooks.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +namespace { + PFN_vkCreateInstance next_vkCreateInstance{}; + PFN_vkDestroyInstance next_vkDestroyInstance{}; + + PFN_vkCreateDevice next_vkCreateDevice{}; + PFN_vkDestroyDevice next_vkDestroyDevice{}; + + PFN_vkSetDeviceLoaderData next_vSetDeviceLoaderData{}; + + PFN_vkGetInstanceProcAddr next_vkGetInstanceProcAddr{}; + PFN_vkGetDeviceProcAddr next_vkGetDeviceProcAddr{}; + + PFN_vkGetPhysicalDeviceQueueFamilyProperties next_vkGetPhysicalDeviceQueueFamilyProperties{}; + PFN_vkGetPhysicalDeviceMemoryProperties next_vkGetPhysicalDeviceMemoryProperties{}; + PFN_vkGetPhysicalDeviceProperties next_vkGetPhysicalDeviceProperties{}; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR{}; + + PFN_vkCreateSwapchainKHR next_vkCreateSwapchainKHR{}; + PFN_vkQueuePresentKHR next_vkQueuePresentKHR{}; + PFN_vkDestroySwapchainKHR next_vkDestroySwapchainKHR{}; + PFN_vkGetSwapchainImagesKHR next_vkGetSwapchainImagesKHR{}; + PFN_vkAllocateCommandBuffers next_vkAllocateCommandBuffers{}; + PFN_vkFreeCommandBuffers next_vkFreeCommandBuffers{}; + PFN_vkBeginCommandBuffer next_vkBeginCommandBuffer{}; + PFN_vkEndCommandBuffer next_vkEndCommandBuffer{}; + PFN_vkCreateCommandPool next_vkCreateCommandPool{}; + PFN_vkDestroyCommandPool next_vkDestroyCommandPool{}; + PFN_vkCreateImage next_vkCreateImage{}; + PFN_vkDestroyImage next_vkDestroyImage{}; + PFN_vkGetImageMemoryRequirements next_vkGetImageMemoryRequirements{}; + PFN_vkBindImageMemory next_vkBindImageMemory{}; + PFN_vkAllocateMemory next_vkAllocateMemory{}; + PFN_vkFreeMemory next_vkFreeMemory{}; + PFN_vkCreateSemaphore next_vkCreateSemaphore{}; + PFN_vkDestroySemaphore next_vkDestroySemaphore{}; + PFN_vkGetMemoryFdKHR next_vkGetMemoryFdKHR{}; + PFN_vkGetSemaphoreFdKHR next_vkGetSemaphoreFdKHR{}; + PFN_vkGetDeviceQueue next_vkGetDeviceQueue{}; + PFN_vkQueueSubmit next_vkQueueSubmit{}; + PFN_vkCmdPipelineBarrier next_vkCmdPipelineBarrier{}; + PFN_vkCmdBlitImage next_vkCmdBlitImage{}; + PFN_vkAcquireNextImageKHR next_vkAcquireNextImageKHR{}; + + template + bool initInstanceFunc(VkInstance instance, const char* name, T* func) { + *func = reinterpret_cast(next_vkGetInstanceProcAddr(instance, name)); + if (!*func) { + std::cerr << "(no function pointer for " << name << ")\n"; + return false; + } + return true; + } + + template + bool initDeviceFunc(VkDevice device, const char* name, T* func) { + *func = reinterpret_cast(next_vkGetDeviceProcAddr(device, name)); + if (!*func) { + std::cerr << "(no function pointer for " << name << ")\n"; + return false; + } + return true; + } +} + +namespace { + VkResult layer_vkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) { + try { + // prepare layer | NOLINTBEGIN + auto* layerDesc = const_cast( + reinterpret_cast(pCreateInfo->pNext)); + while (layerDesc && (layerDesc->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO + || layerDesc->function != VK_LAYER_LINK_INFO)) { + layerDesc = const_cast( + reinterpret_cast(layerDesc->pNext)); + } + if (!layerDesc) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "No layer creation info found in pNext chain"); + + next_vkGetInstanceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetInstanceProcAddr; + layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; + + bool success = initInstanceFunc(nullptr, "vkCreateInstance", &next_vkCreateInstance); + if (!success) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "Failed to get instance function pointer for vkCreateInstance"); + + // NOLINTEND | skip initialization if the layer is disabled + if (!Config::activeConf.enable) { + auto res = next_vkCreateInstance(pCreateInfo, pAllocator, pInstance); + initInstanceFunc(*pInstance, "vkCreateDevice", &next_vkCreateDevice); + return res; + } + + // create instance + try { + auto* createInstanceHook = reinterpret_cast( + Hooks::hooks["vkCreateInstance"]); + auto res = createInstanceHook(pCreateInfo, pAllocator, pInstance); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unknown error"); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Failed to create Vulkan instance", e); + } + + // get relevant function pointers from the next layer + success = true; + success &= initInstanceFunc(*pInstance, + "vkDestroyInstance", &next_vkDestroyInstance); + success &= initInstanceFunc(*pInstance, + "vkCreateDevice", &next_vkCreateDevice); // workaround mesa bug + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceQueueFamilyProperties", &next_vkGetPhysicalDeviceQueueFamilyProperties); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceMemoryProperties", &next_vkGetPhysicalDeviceMemoryProperties); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceProperties", &next_vkGetPhysicalDeviceProperties); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceSurfaceCapabilitiesKHR", &next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR); + if (!success) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "Failed to get instance function pointers"); + + std::cerr << "lsfg-vk: Vulkan instance layer initialized successfully.\n"; + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred while initializing the Vulkan instance layer:\n"; + std::cerr << "- " << e.what() << '\n'; + return VK_ERROR_INITIALIZATION_FAILED; + } + return VK_SUCCESS; + } + + VkResult layer_vkCreateDevice( // NOLINTBEGIN + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice) { + try { + // prepare layer | NOLINTBEGIN + auto* layerDesc = const_cast( + reinterpret_cast(pCreateInfo->pNext)); + while (layerDesc && (layerDesc->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO + || layerDesc->function != VK_LAYER_LINK_INFO)) { + layerDesc = const_cast( + reinterpret_cast(layerDesc->pNext)); + } + if (!layerDesc) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "No layer creation info found in pNext chain"); + + next_vkGetDeviceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetDeviceProcAddr; + layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; + + auto* layerDesc2 = const_cast( + reinterpret_cast(pCreateInfo->pNext)); + while (layerDesc2 && (layerDesc2->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO + || layerDesc2->function != VK_LOADER_DATA_CALLBACK)) { + layerDesc2 = const_cast( + reinterpret_cast(layerDesc2->pNext)); + } + if (!layerDesc2) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "No layer device loader data found in pNext chain"); + + next_vSetDeviceLoaderData = layerDesc2->u.pfnSetDeviceLoaderData; + + // NOLINTEND | skip initialization if the layer is disabled + if (!Config::activeConf.enable) + return next_vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); + + // create device + try { + auto* createDeviceHook = reinterpret_cast( + Hooks::hooks["vkCreateDevicePre"]); + auto res = createDeviceHook(physicalDevice, pCreateInfo, pAllocator, pDevice); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unknown error"); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Failed to create Vulkan device", e); + } + + // get relevant function pointers from the next layer + bool success = true; + success &= initDeviceFunc(*pDevice, "vkDestroyDevice", &next_vkDestroyDevice); + success &= initDeviceFunc(*pDevice, "vkCreateSwapchainKHR", &next_vkCreateSwapchainKHR); + success &= initDeviceFunc(*pDevice, "vkQueuePresentKHR", &next_vkQueuePresentKHR); + success &= initDeviceFunc(*pDevice, "vkDestroySwapchainKHR", &next_vkDestroySwapchainKHR); + success &= initDeviceFunc(*pDevice, "vkGetSwapchainImagesKHR", &next_vkGetSwapchainImagesKHR); + success &= initDeviceFunc(*pDevice, "vkAllocateCommandBuffers", &next_vkAllocateCommandBuffers); + success &= initDeviceFunc(*pDevice, "vkFreeCommandBuffers", &next_vkFreeCommandBuffers); + success &= initDeviceFunc(*pDevice, "vkBeginCommandBuffer", &next_vkBeginCommandBuffer); + success &= initDeviceFunc(*pDevice, "vkEndCommandBuffer", &next_vkEndCommandBuffer); + success &= initDeviceFunc(*pDevice, "vkCreateCommandPool", &next_vkCreateCommandPool); + success &= initDeviceFunc(*pDevice, "vkDestroyCommandPool", &next_vkDestroyCommandPool); + success &= initDeviceFunc(*pDevice, "vkCreateImage", &next_vkCreateImage); + success &= initDeviceFunc(*pDevice, "vkDestroyImage", &next_vkDestroyImage); + success &= initDeviceFunc(*pDevice, "vkGetImageMemoryRequirements", &next_vkGetImageMemoryRequirements); + success &= initDeviceFunc(*pDevice, "vkBindImageMemory", &next_vkBindImageMemory); + success &= initDeviceFunc(*pDevice, "vkGetMemoryFdKHR", &next_vkGetMemoryFdKHR); + success &= initDeviceFunc(*pDevice, "vkAllocateMemory", &next_vkAllocateMemory); + success &= initDeviceFunc(*pDevice, "vkFreeMemory", &next_vkFreeMemory); + success &= initDeviceFunc(*pDevice, "vkCreateSemaphore", &next_vkCreateSemaphore); + success &= initDeviceFunc(*pDevice, "vkDestroySemaphore", &next_vkDestroySemaphore); + success &= initDeviceFunc(*pDevice, "vkGetSemaphoreFdKHR", &next_vkGetSemaphoreFdKHR); + success &= initDeviceFunc(*pDevice, "vkGetDeviceQueue", &next_vkGetDeviceQueue); + success &= initDeviceFunc(*pDevice, "vkQueueSubmit", &next_vkQueueSubmit); + success &= initDeviceFunc(*pDevice, "vkCmdPipelineBarrier", &next_vkCmdPipelineBarrier); + success &= initDeviceFunc(*pDevice, "vkCmdBlitImage", &next_vkCmdBlitImage); + success &= initDeviceFunc(*pDevice, "vkAcquireNextImageKHR", &next_vkAcquireNextImageKHR); + if (!success) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "Failed to get device function pointers"); + + auto postCreateDeviceHook = reinterpret_cast( + Hooks::hooks["vkCreateDevicePost"]); + auto res = postCreateDeviceHook(physicalDevice, pCreateInfo, pAllocator, pDevice); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unknown error"); + + std::cerr << "lsfg-vk: Vulkan device layer initialized successfully.\n"; + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred while initializing the Vulkan device layer:\n"; + std::cerr << "- " << e.what() << '\n'; + return VK_ERROR_INITIALIZATION_FAILED; + } + return VK_SUCCESS; + } // NOLINTEND +} + +const std::unordered_map layerFunctions = { + { "vkCreateInstance", + reinterpret_cast(&layer_vkCreateInstance) }, + { "vkCreateDevice", + reinterpret_cast(&layer_vkCreateDevice) }, + { "vkGetInstanceProcAddr", + reinterpret_cast(&layer_vkGetInstanceProcAddr) }, + { "vkGetDeviceProcAddr", + reinterpret_cast(&layer_vkGetDeviceProcAddr) }, +}; + +PFN_vkVoidFunction layer_vkGetInstanceProcAddr(VkInstance instance, const char* pName) { + const std::string name(pName); + auto it = layerFunctions.find(name); + if (it != layerFunctions.end()) + return it->second; + + it = Hooks::hooks.find(name); + if (it != Hooks::hooks.end() && Config::activeConf.enable) + return it->second; + + return next_vkGetInstanceProcAddr(instance, pName); +} + +PFN_vkVoidFunction layer_vkGetDeviceProcAddr(VkDevice device, const char* pName) { + const std::string name(pName); + auto it = layerFunctions.find(name); + if (it != layerFunctions.end()) + return it->second; + + it = Hooks::hooks.find(name); + if (it != Hooks::hooks.end() && Config::activeConf.enable) + return it->second; + + return next_vkGetDeviceProcAddr(device, pName); +} + +// original functions +namespace Layer { + VkResult ovkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) { + return next_vkCreateInstance(pCreateInfo, pAllocator, pInstance); + } + void ovkDestroyInstance( + VkInstance instance, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyInstance(instance, pAllocator); + } + + VkResult ovkCreateDevice( + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice) { + return next_vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); + } + void ovkDestroyDevice( + VkDevice device, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyDevice(device, pAllocator); + } + + VkResult ovkSetDeviceLoaderData(VkDevice device, void* object) { + return next_vSetDeviceLoaderData(device, object); + } + + PFN_vkVoidFunction ovkGetInstanceProcAddr( + VkInstance instance, + const char* pName) { + return next_vkGetInstanceProcAddr(instance, pName); + } + PFN_vkVoidFunction ovkGetDeviceProcAddr( + VkDevice device, + const char* pName) { + return next_vkGetDeviceProcAddr(device, pName); + } + + void ovkGetPhysicalDeviceQueueFamilyProperties( + VkPhysicalDevice physicalDevice, + uint32_t* pQueueFamilyPropertyCount, + VkQueueFamilyProperties* pQueueFamilyProperties) { + next_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties); + } + void ovkGetPhysicalDeviceMemoryProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceMemoryProperties* pMemoryProperties) { + next_vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties); + } + void ovkGetPhysicalDeviceProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceProperties* pProperties) { + next_vkGetPhysicalDeviceProperties(physicalDevice, pProperties); + } + VkResult ovkGetPhysicalDeviceSurfaceCapabilitiesKHR( + VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface, + VkSurfaceCapabilitiesKHR* pSurfaceCapabilities) { + return next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, pSurfaceCapabilities); + } + + VkResult ovkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain) { + return next_vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + } + VkResult ovkQueuePresentKHR( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo) { + return next_vkQueuePresentKHR(queue, pPresentInfo); + } + void ovkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroySwapchainKHR(device, swapchain, pAllocator); + } + + VkResult ovkGetSwapchainImagesKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint32_t* pSwapchainImageCount, + VkImage* pSwapchainImages) { + return next_vkGetSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, pSwapchainImages); + } + + VkResult ovkAllocateCommandBuffers( + VkDevice device, + const VkCommandBufferAllocateInfo* pAllocateInfo, + VkCommandBuffer* pCommandBuffers) { + return next_vkAllocateCommandBuffers(device, pAllocateInfo, pCommandBuffers); + } + void ovkFreeCommandBuffers( + VkDevice device, + VkCommandPool commandPool, + uint32_t commandBufferCount, + const VkCommandBuffer* pCommandBuffers) { + next_vkFreeCommandBuffers(device, commandPool, commandBufferCount, pCommandBuffers); + } + + VkResult ovkBeginCommandBuffer( + VkCommandBuffer commandBuffer, + const VkCommandBufferBeginInfo* pBeginInfo) { + return next_vkBeginCommandBuffer(commandBuffer, pBeginInfo); + } + VkResult ovkEndCommandBuffer( + VkCommandBuffer commandBuffer) { + return next_vkEndCommandBuffer(commandBuffer); + } + + VkResult ovkCreateCommandPool( + VkDevice device, + const VkCommandPoolCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkCommandPool* pCommandPool) { + return next_vkCreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool); + } + void ovkDestroyCommandPool( + VkDevice device, + VkCommandPool commandPool, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyCommandPool(device, commandPool, pAllocator); + } + + VkResult ovkCreateImage( + VkDevice device, + const VkImageCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkImage* pImage) { + return next_vkCreateImage(device, pCreateInfo, pAllocator, pImage); + } + void ovkDestroyImage( + VkDevice device, + VkImage image, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyImage(device, image, pAllocator); + } + + void ovkGetImageMemoryRequirements( + VkDevice device, + VkImage image, + VkMemoryRequirements* pMemoryRequirements) { + next_vkGetImageMemoryRequirements(device, image, pMemoryRequirements); + } + VkResult ovkBindImageMemory( + VkDevice device, + VkImage image, + VkDeviceMemory memory, + VkDeviceSize memoryOffset) { + return next_vkBindImageMemory(device, image, memory, memoryOffset); + } + + VkResult ovkAllocateMemory( + VkDevice device, + const VkMemoryAllocateInfo* pAllocateInfo, + const VkAllocationCallbacks* pAllocator, + VkDeviceMemory* pMemory) { + return next_vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); + } + void ovkFreeMemory( + VkDevice device, + VkDeviceMemory memory, + const VkAllocationCallbacks* pAllocator) { + next_vkFreeMemory(device, memory, pAllocator); + } + + VkResult ovkCreateSemaphore( + VkDevice device, + const VkSemaphoreCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSemaphore* pSemaphore) { + return next_vkCreateSemaphore(device, pCreateInfo, pAllocator, pSemaphore); + } + void ovkDestroySemaphore( + VkDevice device, + VkSemaphore semaphore, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroySemaphore(device, semaphore, pAllocator); + } + + VkResult ovkGetMemoryFdKHR( + VkDevice device, + const VkMemoryGetFdInfoKHR* pGetFdInfo, + int* pFd) { + return next_vkGetMemoryFdKHR(device, pGetFdInfo, pFd); + } + VkResult ovkGetSemaphoreFdKHR( + VkDevice device, + const VkSemaphoreGetFdInfoKHR* pGetFdInfo, + int* pFd) { + return next_vkGetSemaphoreFdKHR(device, pGetFdInfo, pFd); + } + + void ovkGetDeviceQueue( + VkDevice device, + uint32_t queueFamilyIndex, + uint32_t queueIndex, + VkQueue* pQueue) { + next_vkGetDeviceQueue(device, queueFamilyIndex, queueIndex, pQueue); + } + VkResult ovkQueueSubmit( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo* pSubmits, + VkFence fence) { + return next_vkQueueSubmit(queue, submitCount, pSubmits, fence); + } + + void ovkCmdPipelineBarrier( + VkCommandBuffer commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + uint32_t memoryBarrierCount, + const VkMemoryBarrier* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + const VkBufferMemoryBarrier* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + const VkImageMemoryBarrier* pImageMemoryBarriers) { + next_vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, dependencyFlags, + memoryBarrierCount, pMemoryBarriers, + bufferMemoryBarrierCount, pBufferMemoryBarriers, + imageMemoryBarrierCount, pImageMemoryBarriers); + } + void ovkCmdBlitImage( + VkCommandBuffer commandBuffer, + VkImage srcImage, + VkImageLayout srcImageLayout, + VkImage dstImage, + VkImageLayout dstImageLayout, + uint32_t regionCount, + const VkImageBlit* pRegions, + VkFilter filter) { + next_vkCmdBlitImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions, filter); + } + + VkResult ovkAcquireNextImageKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint64_t timeout, + VkSemaphore semaphore, + VkFence fence, + uint32_t* pImageIndex) { + return next_vkAcquireNextImageKHR(device, swapchain, timeout, semaphore, fence, pImageIndex); + } +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ef9eb07 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,126 @@ +#include "config/config.hpp" +#include "extract/extract.hpp" +#include "utils/benchmark.hpp" +#include "utils/utils.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + __attribute__((constructor)) void lsfgvk_init() { + std::cerr << std::unitbuf; + + // read configuration + const std::string file = Utils::getConfigFile(); + try { + Config::updateConfig(file); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, IGNORING:\n"; + std::cerr << "- " << e.what() << '\n'; + return; // default configuration will unload + } + + const auto name = Utils::getProcessName(); + try { + Config::activeConf = Config::getConfig(name); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: The configuration for " << name.second << " is invalid, IGNORING:\n"; + std::cerr << e.what() << '\n'; + return; // default configuration will unload + } + + // exit silently if not enabled + auto& conf = Config::activeConf; + if (!conf.enable && name.second != "benchmark") + return; // default configuration will unload + + // print config + std::cerr << "lsfg-vk: Loaded configuration for " << name.second << ":\n"; + if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n'; + std::cerr << " Multiplier: " << conf.multiplier << '\n'; + std::cerr << " Flow Scale: " << conf.flowScale << '\n'; + std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; + std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; + if (conf.e_present != 2) std::cerr << " ! Present Mode: " << conf.e_present << '\n'; + + // remove mesa var in favor of config + unsetenv("MESA_VK_WSI_PRESENT_MODE"); // NOLINT + + // write latest file + try { + std::ofstream latest("/tmp/lsfg-vk_last", std::ios::trunc); + if (!latest.is_open()) + throw std::runtime_error("Failed to open /tmp/lsfg-vk_last for writing"); + latest << "exe: " << name.first << '\n'; + latest << "comm: " << name.second << '\n'; + latest << "pid: " << getpid() << '\n'; + if (!latest.good()) + throw std::runtime_error("Failed to write to /tmp/lsfg-vk_last"); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred while trying to write the latest file, exiting:\n"; + std::cerr << "- " << e.what() << '\n'; + exit(EXIT_FAILURE); + } + + // load shaders + try { + Extract::extractShaders(); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred while trying to extract the shaders, exiting:\n"; + std::cerr << "- " << e.what() << '\n'; + exit(EXIT_FAILURE); + } + std::cerr << "lsfg-vk: Shaders extracted successfully.\n"; + + // run benchmark if requested + const char* benchmark_flag = std::getenv("LSFG_BENCHMARK"); + if (!benchmark_flag) + return; + + const std::string resolution(benchmark_flag); + uint32_t width{}; + uint32_t height{}; + try { + const size_t x = resolution.find('x'); + if (x == std::string::npos) + throw std::runtime_error("Unable to find 'x' in benchmark string"); + + const std::string width_str = resolution.substr(0, x); + const std::string height_str = resolution.substr(x + 1); + if (width_str.empty() || height_str.empty()) + throw std::runtime_error("Invalid resolution"); + + const int32_t w = std::stoi(width_str); + const int32_t h = std::stoi(height_str); + if (w < 0 || h < 0) + throw std::runtime_error("Resolution cannot be negative"); + + width = static_cast(w); + height = static_cast(h); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred while trying to parse the resolution, exiting:\n"; + std::cerr << "- " << e.what() << '\n'; + exit(EXIT_FAILURE); + } + + std::thread benchmark([width, height]() { + try { + Benchmark::run(width, height); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred during the benchmark:\n"; + std::cerr << "- " << e.what() << '\n'; + exit(EXIT_FAILURE); + } + }); + benchmark.detach(); + conf.enable = false; + } +} diff --git a/src/mini/commandbuffer.cpp b/src/mini/commandbuffer.cpp new file mode 100644 index 0000000..3b0e49a --- /dev/null +++ b/src/mini/commandbuffer.cpp @@ -0,0 +1,91 @@ +#include "mini/commandbuffer.hpp" +#include "mini/commandpool.hpp" +#include "common/exception.hpp" +#include "layer.hpp" + +#include + +#include +#include +#include +#include + +using namespace Mini; + +CommandBuffer::CommandBuffer(VkDevice device, const CommandPool& pool) { + // create command buffer + const VkCommandBufferAllocateInfo desc{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = pool.handle(), + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1 + }; + VkCommandBuffer commandBufferHandle{}; + auto res = Layer::ovkAllocateCommandBuffers(device, &desc, &commandBufferHandle); + if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to allocate command buffer"); + res = Layer::ovkSetDeviceLoaderData(device, commandBufferHandle); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to set device loader data for command buffer"); + + // store command buffer in shared ptr + this->state = std::make_shared(CommandBufferState::Empty); + this->commandBuffer = std::shared_ptr( + new VkCommandBuffer(commandBufferHandle), + [dev = device, pool = pool.handle()](VkCommandBuffer* cmdBuffer) { + Layer::ovkFreeCommandBuffers(dev, pool, 1, cmdBuffer); + } + ); +} + +void CommandBuffer::begin() { + if (*this->state != CommandBufferState::Empty) + throw std::logic_error("Command buffer is not in Empty state"); + + const VkCommandBufferBeginInfo beginInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; + auto res = Layer::ovkBeginCommandBuffer(*this->commandBuffer, &beginInfo); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to begin command buffer"); + + *this->state = CommandBufferState::Recording; +} + +void CommandBuffer::end() { + if (*this->state != CommandBufferState::Recording) + throw std::logic_error("Command buffer is not in Recording state"); + + auto res = Layer::ovkEndCommandBuffer(*this->commandBuffer); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to end command buffer"); + + *this->state = CommandBufferState::Full; +} + +void CommandBuffer::submit(VkQueue queue, + const std::vector& waitSemaphores, + const std::vector& signalSemaphores) { + if (*this->state != CommandBufferState::Full) + throw std::logic_error("Command buffer is not in Full state"); + + const std::vector waitStages(waitSemaphores.size(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + + const VkSubmitInfo submitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphores.data(), + .pWaitDstStageMask = waitStages.data(), + .commandBufferCount = 1, + .pCommandBuffers = &(*this->commandBuffer), + .signalSemaphoreCount = static_cast(signalSemaphores.size()), + .pSignalSemaphores = signalSemaphores.data() + }; + auto res = Layer::ovkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to submit command buffer"); + + *this->state = CommandBufferState::Submitted; +} diff --git a/src/mini/commandpool.cpp b/src/mini/commandpool.cpp new file mode 100644 index 0000000..e43c7db --- /dev/null +++ b/src/mini/commandpool.cpp @@ -0,0 +1,30 @@ +#include "mini/commandpool.hpp" +#include "common/exception.hpp" +#include "layer.hpp" + +#include + +#include +#include + +using namespace Mini; + +CommandPool::CommandPool(VkDevice device, uint32_t graphicsFamilyIdx) { + // create command pool + const VkCommandPoolCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .queueFamilyIndex = graphicsFamilyIdx + }; + VkCommandPool commandPoolHandle{}; + auto res = Layer::ovkCreateCommandPool(device, &desc, nullptr, &commandPoolHandle); + if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create command pool"); + + // store command pool in shared ptr + this->commandPool = std::shared_ptr( + new VkCommandPool(commandPoolHandle), + [dev = device](VkCommandPool* commandPoolHandle) { + Layer::ovkDestroyCommandPool(dev, *commandPoolHandle, nullptr); + } + ); +} diff --git a/src/mini/image.cpp b/src/mini/image.cpp new file mode 100644 index 0000000..978c67c --- /dev/null +++ b/src/mini/image.cpp @@ -0,0 +1,112 @@ +#include "mini/image.hpp" +#include "common/exception.hpp" +#include "layer.hpp" + +#include + +#include +#include +#include + +using namespace Mini; + +Image::Image(VkDevice device, VkPhysicalDevice physicalDevice, + VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd) + : extent(extent), format(format), aspectFlags(aspectFlags) { + // create image + const VkExternalMemoryImageCreateInfo externalInfo{ + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR + }; + const VkImageCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &externalInfo, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = { + .width = extent.width, + .height = extent.height, + .depth = 1 + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VkImage imageHandle{}; + auto res = Layer::ovkCreateImage(device, &desc, nullptr, &imageHandle); + if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create Vulkan image"); + + // find memory type + VkPhysicalDeviceMemoryProperties memProps; + Layer::ovkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps); + + VkMemoryRequirements memReqs; + Layer::ovkGetImageMemoryRequirements(device, imageHandle, &memReqs); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + std::optional memType{}; + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN + (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + memType.emplace(i); + break; + } // NOLINTEND + } + if (!memType.has_value()) + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image"); +#pragma clang diagnostic pop + + // allocate and bind memory + const VkMemoryDedicatedAllocateInfoKHR dedicatedInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR, + .image = imageHandle, + }; + const VkExportMemoryAllocateInfo exportInfo{ + .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, + .pNext = &dedicatedInfo, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR + }; + const VkMemoryAllocateInfo allocInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &exportInfo, + .allocationSize = memReqs.size, + .memoryTypeIndex = memType.value() + }; + VkDeviceMemory memoryHandle{}; + res = Layer::ovkAllocateMemory(device, &allocInfo, nullptr, &memoryHandle); + if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image"); + + res = Layer::ovkBindImageMemory(device, imageHandle, memoryHandle, 0); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image"); + + // obtain the sharing fd + const VkMemoryGetFdInfoKHR fdInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, + .memory = memoryHandle, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR, + }; + res = Layer::ovkGetMemoryFdKHR(device, &fdInfo, fd); + if (res != VK_SUCCESS || *fd < 0) + throw LSFG::vulkan_error(res, "Failed to obtain sharing fd for Vulkan image"); + + // store objects in shared ptr + this->image = std::shared_ptr( + new VkImage(imageHandle), + [dev = device](VkImage* img) { + Layer::ovkDestroyImage(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device](VkDeviceMemory* mem) { + Layer::ovkFreeMemory(dev, *mem, nullptr); + } + ); +} diff --git a/src/mini/semaphore.cpp b/src/mini/semaphore.cpp new file mode 100644 index 0000000..cd03031 --- /dev/null +++ b/src/mini/semaphore.cpp @@ -0,0 +1,62 @@ +#include "mini/semaphore.hpp" +#include "common/exception.hpp" +#include "layer.hpp" + +#include + +#include + +using namespace Mini; + +Semaphore::Semaphore(VkDevice device) { + // create semaphore + const VkSemaphoreCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO + }; + VkSemaphore semaphoreHandle{}; + auto res = Layer::ovkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle); + if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create semaphore"); + + // store semaphore in shared ptr + this->semaphore = std::shared_ptr( + new VkSemaphore(semaphoreHandle), + [dev = device](VkSemaphore* semaphoreHandle) { + Layer::ovkDestroySemaphore(dev, *semaphoreHandle, nullptr); + } + ); +} + +Semaphore::Semaphore(VkDevice device, int* fd) { + // create semaphore + const VkExportSemaphoreCreateInfo exportInfo{ + .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT + }; + const VkSemaphoreCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &exportInfo + }; + VkSemaphore semaphoreHandle{}; + auto res = Layer::ovkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle); + if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create semaphore"); + + // export semaphore to fd + const VkSemaphoreGetFdInfoKHR fdInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, + .semaphore = semaphoreHandle, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT + }; + res = Layer::ovkGetSemaphoreFdKHR(device, &fdInfo, fd); + if (res != VK_SUCCESS || *fd < 0) + throw LSFG::vulkan_error(res, "Unable to export semaphore to fd"); + + // store semaphore in shared ptr + this->semaphore = std::shared_ptr( + new VkSemaphore(semaphoreHandle), + [dev = device](VkSemaphore* semaphoreHandle) { + Layer::ovkDestroySemaphore(dev, *semaphoreHandle, nullptr); + } + ); +} diff --git a/src/utils/benchmark.cpp b/src/utils/benchmark.cpp new file mode 100644 index 0000000..0f0d0fd --- /dev/null +++ b/src/utils/benchmark.cpp @@ -0,0 +1,96 @@ +#include "utils/benchmark.hpp" +#include "config/config.hpp" +#include "extract/extract.hpp" +#include "extract/trans.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Benchmark; + +void Benchmark::run(uint32_t width, uint32_t height) { + const auto& conf = Config::activeConf; + + auto* lsfgInitialize = LSFG_3_1::initialize; + auto* lsfgCreateContext = LSFG_3_1::createContext; + auto* lsfgPresentContext = LSFG_3_1::presentContext; + if (conf.performance) { + lsfgInitialize = LSFG_3_1P::initialize; + lsfgCreateContext = LSFG_3_1P::createContext; + lsfgPresentContext = LSFG_3_1P::presentContext; + } + + // create the benchmark context + const char* lsfgDeviceUUID = std::getenv("LSFG_DEVICE_UUID"); + const uint64_t deviceUUID = lsfgDeviceUUID + ? std::stoull(std::string(lsfgDeviceUUID), nullptr, 16) : 0x1463ABAC; + + setenv("DISABLE_LSFG", "1", 1); // NOLINT + + Extract::extractShaders(); + lsfgInitialize( + deviceUUID, // some magic number if not given + conf.hdr, 1.0F / conf.flowScale, conf.multiplier - 1, + [](const std::string& name) -> std::vector { + auto dxbc = Extract::getShader(name); + auto spirv = Extract::translateShader(dxbc); + return spirv; + } + ); + const int32_t ctx = lsfgCreateContext(-1, -1, {}, + { .width = width, .height = height }, + conf.hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM + ); + + unsetenv("DISABLE_LSFG"); // NOLINT + + // run the benchmark (run 8*n + 1 so the fences are waited on) + const auto now = std::chrono::high_resolution_clock::now(); + const uint64_t iterations = 8 * 500UL; + + std::cerr << "lsfg-vk: Benchmark started, running " << iterations << " iterations...\n"; + for (uint64_t count = 0; count < iterations + 1; count++) { + lsfgPresentContext(ctx, -1, {}); + + if (count % 50 == 0 && count > 0) + std::cerr << "lsfg-vk: " + << std::setprecision(2) << std::fixed + << static_cast(count) / static_cast(iterations) * 100.0F + << "% done (" << count + 1 << "/" << iterations << ")\r"; + } + const auto then = std::chrono::high_resolution_clock::now(); + + // print results + const auto ms = std::chrono::duration_cast(then - now).count(); + + const auto perIteration = static_cast(ms) / static_cast(iterations); + + const uint64_t totalGen = (conf.multiplier - 1) * iterations; + const auto genFps = static_cast(totalGen) / (static_cast(ms) / 1000.0F); + + const uint64_t totalFrames = iterations * conf.multiplier; + const auto totalFps = static_cast(totalFrames) / (static_cast(ms) / 1000.0F); + + std::cerr << "lsfg-vk: Benchmark completed in " << ms << " ms\n"; + std::cerr << " Time taken per real frame: " + << std::setprecision(2) << std::fixed << perIteration << " ms\n"; + std::cerr << " Generated " << totalGen << " frames in total at " + << std::setprecision(2) << std::fixed << genFps << " FPS\n"; + std::cerr << " Total of " << totalFrames << " frames presented at " + << std::setprecision(2) << std::fixed << totalFps << " FPS\n"; + + // sleep for a second, then exit + std::this_thread::sleep_for(std::chrono::seconds(1)); + _exit(0); +} diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp new file mode 100644 index 0000000..a83e80d --- /dev/null +++ b/src/utils/utils.cpp @@ -0,0 +1,250 @@ +#include "utils/utils.hpp" +#include "common/exception.hpp" +#include "layer.hpp" + +#include +#include +#include // NOLINT +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Utils; + +std::pair Utils::findQueue(VkDevice device, VkPhysicalDevice physicalDevice, + VkDeviceCreateInfo* desc, VkQueueFlags flags) { + std::vector enabledQueues(desc->queueCreateInfoCount); + std::copy_n(desc->pQueueCreateInfos, enabledQueues.size(), enabledQueues.data()); + + uint32_t familyCount{}; + Layer::ovkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr); + std::vector families(familyCount); + Layer::ovkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, + families.data()); + + std::optional idx; + for (const auto& queueInfo : enabledQueues) { + if ((queueInfo.queueFamilyIndex < families.size()) && + (families[queueInfo.queueFamilyIndex].queueFlags & flags)) { + idx = queueInfo.queueFamilyIndex; + break; + } + } + if (!idx.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No suitable queue found"); + + VkQueue queue{}; + Layer::ovkGetDeviceQueue(device, *idx, 0, &queue); + + auto res = Layer::ovkSetDeviceLoaderData(device, queue); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to set device loader data for queue"); + + return { *idx, queue }; +} + +uint64_t Utils::getDeviceUUID(VkPhysicalDevice physicalDevice) { + VkPhysicalDeviceProperties properties{}; + Layer::ovkGetPhysicalDeviceProperties(physicalDevice, &properties); + + return static_cast(properties.vendorID) << 32 | properties.deviceID; +} + +uint32_t Utils::getMaxImageCount(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface) { + VkSurfaceCapabilitiesKHR capabilities{}; + auto res = Layer::ovkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, + surface, &capabilities); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to get surface capabilities"); + if (capabilities.maxImageCount == 0) + return 999; // :3 + return capabilities.maxImageCount; +} + +std::vector Utils::addExtensions(const char* const* extensions, size_t count, + const std::vector& requiredExtensions) { + std::vector ext(count); + std::copy_n(extensions, count, ext.data()); + + for (const auto& e : requiredExtensions) { + auto it = std::ranges::find_if(ext, + [e](const char* extName) { + return std::string(extName) == std::string(e); + }); + if (it == ext.end()) + ext.push_back(e); + } + + return ext; +} + +void Utils::copyImage(VkCommandBuffer buf, + VkImage src, VkImage dst, + uint32_t width, uint32_t height, + VkPipelineStageFlags pre, VkPipelineStageFlags post, + bool makeSrcPresentable, bool makeDstPresentable) { + const VkImageMemoryBarrier srcBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .image = src, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + const VkImageMemoryBarrier dstBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = dst, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + const std::vector barriers = { srcBarrier, dstBarrier }; + Layer::ovkCmdPipelineBarrier(buf, + pre, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, nullptr, 0, nullptr, + static_cast(barriers.size()), barriers.data()); + + const VkImageBlit imageBlit{ + .srcSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 + }, + .srcOffsets = { + { 0, 0, 0 }, + { static_cast(width), static_cast(height), 1 } + }, + .dstSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 + }, + .dstOffsets = { + { 0, 0, 0 }, + { static_cast(width), static_cast(height), 1 } + } + }; + Layer::ovkCmdBlitImage( + buf, + src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &imageBlit, + VK_FILTER_NEAREST + ); + + if (makeSrcPresentable) { + const VkImageMemoryBarrier presentBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .image = src, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + Layer::ovkCmdPipelineBarrier(buf, + VK_PIPELINE_STAGE_TRANSFER_BIT, post, 0, + 0, nullptr, 0, nullptr, + 1, &presentBarrier); + } + + if (makeDstPresentable) { + const VkImageMemoryBarrier presentBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .image = dst, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + Layer::ovkCmdPipelineBarrier(buf, + VK_PIPELINE_STAGE_TRANSFER_BIT, post, 0, + 0, nullptr, 0, nullptr, + 1, &presentBarrier); + } +} + +namespace { + auto& logCounts() { + static std::unordered_map map; + return map; + } +} + +void Utils::logLimitN(const std::string& id, size_t n, const std::string& message) { + auto& count = logCounts()[id]; + if (count <= n) + std::cerr << "lsfg-vk: " << message << '\n'; + if (count == n) + std::cerr << "(above message has been repeated " << n << " times, suppressing further)\n"; + count++; +} + +void Utils::resetLimitN(const std::string& id) noexcept { + logCounts().erase(id); +} + +std::pair Utils::getProcessName() { + const char* process_name = std::getenv("LSFG_PROCESS"); + if (process_name && *process_name != '\0') + return { process_name, process_name }; + + const char* benchmark_flag = std::getenv("LSFG_BENCHMARK"); + if (benchmark_flag) + return { "benchmark", "benchmark" }; + std::array exe{}; + + const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1); + if (exe_len <= 0) + return { "Unknown Process", "unknown" }; + exe.at(static_cast(exe_len)) = '\0'; + + std::ifstream comm_file("/proc/self/comm"); + if (!comm_file.is_open()) + return { std::string(exe.data()), "unknown" }; + std::array comm{}; + comm_file.read(comm.data(), 256); + comm.at(static_cast(comm_file.gcount())) = '\0'; + std::string comm_str(comm.data()); + if (comm_str.back() == '\n') + comm_str.pop_back(); + + return{ std::string(exe.data()), comm_str }; +} + +std::string Utils::getConfigFile() { + const char* configFile = std::getenv("LSFG_CONFIG"); + if (configFile && *configFile != '\0') + return{configFile}; + const char* xdgPath = std::getenv("XDG_CONFIG_HOME"); + if (xdgPath && *xdgPath != '\0') + return std::string(xdgPath) + "/lsfg-vk/conf.toml"; + const char* homePath = std::getenv("HOME"); + if (homePath && *homePath != '\0') + return std::string(homePath) + "/.config/lsfg-vk/conf.toml"; + return "/etc/lsfg-vk/conf.toml"; +} diff --git a/thirdparty/dxbc b/thirdparty/dxbc new file mode 160000 index 0000000..78ab59a --- /dev/null +++ b/thirdparty/dxbc @@ -0,0 +1 @@ +Subproject commit 78ab59a8aaeb43cd1b0a5e91ba86722433a10b78 diff --git a/thirdparty/pe-parse b/thirdparty/pe-parse new file mode 160000 index 0000000..31ac596 --- /dev/null +++ b/thirdparty/pe-parse @@ -0,0 +1 @@ +Subproject commit 31ac5966503689d5693cd9fb520bd525a8710e17 diff --git a/thirdparty/toml11 b/thirdparty/toml11 new file mode 160000 index 0000000..be08ba2 --- /dev/null +++ b/thirdparty/toml11 @@ -0,0 +1 @@ +Subproject commit be08ba2be2a964edcdb3d3e3ea8d100abc26f286 diff --git a/thirdparty/volk b/thirdparty/volk new file mode 160000 index 0000000..be3dbd4 --- /dev/null +++ b/thirdparty/volk @@ -0,0 +1 @@ +Subproject commit be3dbd49bf77052665e96b6c7484af855e7e5f67 diff --git a/ui/Cargo.lock b/ui/Cargo.lock new file mode 100644 index 0000000..4696f79 --- /dev/null +++ b/ui/Cargo.lock @@ -0,0 +1,1411 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cairo-rs" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6466a563dea2e99f59f6ffbb749fd0bdf75764f5e6e93976b5e7bd73c4c9efb" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "libc", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab7e9f13c802625aad1ad2b4ae3989f4ce9339ff388f335a6f109f9338705e2" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0390889d58f934f01cd49736275b4c2da15bcfc328c78ff2349907e6cabf22" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688dc7eaf551dbac1f5b11d000d089c3db29feb25562455f47c1a2080cc60bda" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5af1823d3d1cb72616873ba0a593bd440eb92da700fdfb047505a21ee3ec3e10" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk4" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a67b064d2f35e649232455c7724f56f977555d2608c43300eabc530eaa4e359" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk4-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk4-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2edbda0d879eb85317bdb49a3da591ed70a804a10776e358ef416be38c6db2c5" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gio" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273d64c833fbbf7cd86c4cdced893c5d3f2f5d6aeb30fd0c30d172456ce8be2e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "pin-project-lite", + "smallvec", +] + +[[package]] +name = "gio-sys" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8130f5810a839d74afc3a929c34a700bf194972bb034f2ecfe639682dd13cc" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.60.2", +] + +[[package]] +name = "glib" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "690e8bcf8a819b5911d6ae79879226191d01253a4f602748072603defd5b9553" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-build-tools" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86aebe63bb050d4918cb1d629880cb35fcba7ccda6f6fc0ec1beffdaa1b9d5c3" +dependencies = [ + "gio", +] + +[[package]] +name = "glib-macros" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e772291ebea14c28eb11bb75741f62f4a4894f25e60ce80100797b6b010ef0f9" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2be4c74454fb4a6bd3328320737d0fa3d6939e2d570f5d846da00cb222f6a0" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "gobject-sys" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab318a786f9abd49d388013b9161fa0ef8218ea6118ee7111c95e62186f7d31f" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "graphene-rs" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0487f78e8a772ec89020458fbabadd1332bc1e3236ca1c63ef1d61afd4e5f2cc" +dependencies = [ + "glib", + "graphene-sys", + "libc", +] + +[[package]] +name = "graphene-sys" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "270cefb6b270fcb2ef9708c3a35c0e25c2e831dac28d75c4f87e5ad3540c9543" +dependencies = [ + "glib-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gsk4" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5dbe33ceed6fc20def67c03d36e532f5a4a569ae437ae015a7146094f31e10c" +dependencies = [ + "cairo-rs", + "gdk4", + "glib", + "graphene-rs", + "gsk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gsk4-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d76011d55dd19fde16ffdedee08877ae6ec942818cfa7bc08a91259bc0b9fc9" +dependencies = [ + "cairo-sys-rs", + "gdk4-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk4" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938d68ad43080ad5ee710c30d467c1bc022ee5947856f593855691d726305b3e" +dependencies = [ + "cairo-rs", + "field-offset", + "futures-channel", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "graphene-rs", + "gsk4", + "gtk4-macros", + "gtk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gtk4-macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0912d2068695633002b92c5966edc108b2e4f54b58c509d1eeddd4cbceb7315c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gtk4-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a923bdcf00e46723801162de24432cbce38a6810e0178a2d0b6dd4ecc26a1c74" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "gsk4-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libadwaita" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df6715d1257bd8c093295b77a276ed129d73543b10304fec5829ced5d5b7c41" +dependencies = [ + "gdk4", + "gio", + "glib", + "gtk4", + "libadwaita-sys", + "libc", + "pango", +] + +[[package]] +name = "libadwaita-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf8950090cc180250cdb1ff859a39748feeda7a53a9f28ead3a17a14cc37ae2" +dependencies = [ + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk4-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.2", +] + +[[package]] +name = "libproc" +version = "0.14.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78a09b56be5adbcad5aa1197371688dc6bb249a26da3bca2011ee2fb987ebfb" +dependencies = [ + "bindgen", + "errno", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lsfg-vk-ui" +version = "0.9.0" +dependencies = [ + "anyhow", + "glib-build-tools", + "gtk4", + "libadwaita", + "proc-maps", + "procfs", + "serde", + "toml 0.9.2", +] + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pango" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4803f086c4f49163c31ac14db162112a22401c116435080e4be8678c507d61" +dependencies = [ + "gio", + "glib", + "libc", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66872b3cfd328ad6d1a4f89ebd5357119bd4c592a4ddbb8f6bc2386f8ce7b898" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-maps" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db44c5aa60e193a25fcd93bb9ed27423827e8f118897866f946e2cf936c44fb" +dependencies = [ + "anyhow", + "bindgen", + "libc", + "libproc", + "mach2", + "winapi", +] + +[[package]] +name = "procfs" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" +dependencies = [ + "bitflags", + "chrono", + "flate2", + "hex", + "procfs-core", + "rustix", +] + +[[package]] +name = "procfs-core" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" +dependencies = [ + "bitflags", + "chrono", + "hex", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "7.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 0.8.23", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] diff --git a/ui/Cargo.toml b/ui/Cargo.toml new file mode 100644 index 0000000..70d88fb --- /dev/null +++ b/ui/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "lsfg-vk-ui" +version = "0.9.0" +edition = "2021" + +[dependencies] +gtk = { version = "0.10.0", package = "gtk4", features = ["v4_10"] } +adw = { version = "0.8.0", package = "libadwaita", features = ["v1_4"] } +serde = { version = "1.0", features = ["derive"] } +toml = "0.9.2" +anyhow = "1.0" +procfs = "0.17.0" +proc-maps = "0.4.0" + +[build-dependencies] +glib-build-tools = "0.21.0" diff --git a/ui/build.rs b/ui/build.rs new file mode 100644 index 0000000..7ad38a0 --- /dev/null +++ b/ui/build.rs @@ -0,0 +1,7 @@ +fn main() { + glib_build_tools::compile_resources( + &["rsc"], + "rsc/lsfg-vk.xml", + "lsfg-vk.gresource", + ); +} diff --git a/ui/rsc/entry/entry.ui b/ui/rsc/entry/entry.ui new file mode 100644 index 0000000..6c066e3 --- /dev/null +++ b/ui/rsc/entry/entry.ui @@ -0,0 +1,33 @@ + + + + diff --git a/ui/rsc/gay.pancake.lsfg-vk-ui.desktop b/ui/rsc/gay.pancake.lsfg-vk-ui.desktop new file mode 100644 index 0000000..5b7ada2 --- /dev/null +++ b/ui/rsc/gay.pancake.lsfg-vk-ui.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=lsfg-vk Configuration Window +Comment=Easy to use configuration editor for lsfg-vk. +Exec=lsfg-vk-ui %U +Icon=gay.pancake.lsfg-vk-ui +Terminal=false +Categories=GTK;Settings; +Keywords=gaming;graphics;configuration; +StartupNotify=true +StartupWMClass=gay.pancake.lsfg-vk-ui +MimeType=application/x-lsfg-profile; diff --git a/ui/rsc/icon.png b/ui/rsc/icon.png new file mode 100644 index 0000000..5e008b5 Binary files /dev/null and b/ui/rsc/icon.png differ diff --git a/ui/rsc/lsfg-vk.xml b/ui/rsc/lsfg-vk.xml new file mode 100644 index 0000000..0d02470 --- /dev/null +++ b/ui/rsc/lsfg-vk.xml @@ -0,0 +1,16 @@ + + + + entry/entry.ui + pane/main.ui + pane/sidebar.ui + popup/process.ui + popup/process_entry.ui + pref/dropdown.ui + pref/number.ui + pref/entry.ui + pref/slider.ui + pref/switch.ui + window.ui + + diff --git a/ui/rsc/pane/main.ui b/ui/rsc/pane/main.ui new file mode 100644 index 0000000..4ded0b2 --- /dev/null +++ b/ui/rsc/pane/main.ui @@ -0,0 +1,118 @@ + + + + diff --git a/ui/rsc/pane/sidebar.ui b/ui/rsc/pane/sidebar.ui new file mode 100644 index 0000000..bb49530 --- /dev/null +++ b/ui/rsc/pane/sidebar.ui @@ -0,0 +1,61 @@ + + + + diff --git a/ui/rsc/popup/process.ui b/ui/rsc/popup/process.ui new file mode 100644 index 0000000..e486cad --- /dev/null +++ b/ui/rsc/popup/process.ui @@ -0,0 +1,39 @@ + + + + diff --git a/ui/rsc/popup/process_entry.ui b/ui/rsc/popup/process_entry.ui new file mode 100644 index 0000000..4a7eb80 --- /dev/null +++ b/ui/rsc/popup/process_entry.ui @@ -0,0 +1,18 @@ + + + + diff --git a/ui/rsc/pref/dropdown.ui b/ui/rsc/pref/dropdown.ui new file mode 100644 index 0000000..263b179 --- /dev/null +++ b/ui/rsc/pref/dropdown.ui @@ -0,0 +1,52 @@ + + + + diff --git a/ui/rsc/pref/entry.ui b/ui/rsc/pref/entry.ui new file mode 100644 index 0000000..4211c01 --- /dev/null +++ b/ui/rsc/pref/entry.ui @@ -0,0 +1,61 @@ + + + + diff --git a/ui/rsc/pref/number.ui b/ui/rsc/pref/number.ui new file mode 100644 index 0000000..314d3ce --- /dev/null +++ b/ui/rsc/pref/number.ui @@ -0,0 +1,61 @@ + + + + diff --git a/ui/rsc/pref/slider.ui b/ui/rsc/pref/slider.ui new file mode 100644 index 0000000..a790f11 --- /dev/null +++ b/ui/rsc/pref/slider.ui @@ -0,0 +1,61 @@ + + + + diff --git a/ui/rsc/pref/switch.ui b/ui/rsc/pref/switch.ui new file mode 100644 index 0000000..e5fa6c1 --- /dev/null +++ b/ui/rsc/pref/switch.ui @@ -0,0 +1,52 @@ + + + + diff --git a/ui/rsc/window.ui b/ui/rsc/window.ui new file mode 100644 index 0000000..3294055 --- /dev/null +++ b/ui/rsc/window.ui @@ -0,0 +1,23 @@ + + + + diff --git a/ui/src/config.rs b/ui/src/config.rs new file mode 100644 index 0000000..3c8ba67 --- /dev/null +++ b/ui/src/config.rs @@ -0,0 +1,171 @@ +use std::sync::{Arc, OnceLock, RwLock}; + +use anyhow::Context; + +pub mod structs; +pub use structs::*; + +/// Find the configuration file path based on environment variables +fn find_config_file() -> String { + if let Some(path) = std::env::var("LSFG_CONFIG").ok() { + return path; + } + + if let Some(xdg) = std::env::var("XDG_CONFIG_HOME").ok() { + return format!("{}/lsfg-vk/conf.toml", xdg); + } + + if let Some(home) = std::env::var("HOME").ok() { + return format!("{}/.config/lsfg-vk/conf.toml", home); + } + + "conf.toml".to_string() +} + +static CONFIG: OnceLock>> = OnceLock::new(); +static CONFIG_WRITER: OnceLock> = OnceLock::new(); + +pub fn default_config() -> TomlConfig { + TomlConfig { + version: 1, + global: TomlGlobal { + dll: None, + }, + game: vec![ + TomlGame { + exe: String::from("vkcube"), + multiplier: Multiplier::from(4), + flow_scale: FlowScale::from(0.7), + performance_mode: true, + hdr_mode: false, + experimental_present_mode: PresentMode::Vsync, + }, + TomlGame { + exe: String::from("benchmark"), + multiplier: Multiplier::from(4), + flow_scale: FlowScale::from(1.0), + performance_mode: true, + hdr_mode: false, + experimental_present_mode: PresentMode::Vsync, + }, + TomlGame { + exe: String::from("Genshin"), + multiplier: Multiplier::from(3), + flow_scale: FlowScale::from(1.0), + performance_mode: false, + hdr_mode: false, + experimental_present_mode: PresentMode::Vsync, + }, + ] + } +} + +/// +/// Load the configuration from the file and create a writer. +/// +pub fn load_config() -> Result<(), anyhow::Error> { + // load the configuration file + let path = find_config_file(); + if !std::path::Path::new(&path).exists() { + let conf = default_config(); + save_config(&conf) + .context("Failed to create default configuration")?; + } + let data = std::fs::read(path) + .context("Failed to read conf.toml")?; + let mut config: TomlConfig = toml::from_slice(&data) + .context("Failed to parse conf.toml")?; + + // remove duplicate entries + config.game.sort_by_key(|e| e.exe.clone()); + config.game.dedup_by_key(|e| e.exe.clone()); + config.game.retain(|e| !e.exe.is_empty()); + + // create the configuration writer thread + let (tx, rx) = std::sync::mpsc::channel::<()>(); + CONFIG.set(Arc::new(RwLock::new(config))) + .ok().context("Failed to set configuration state")?; + CONFIG_WRITER.set(tx) + .ok().context("Failed to set configuration writer")?; + + std::thread::spawn(move || { + let config = CONFIG.get().unwrap(); + loop { + // wait for a signal to write the configuration + if let Err(_) = rx.recv() { + break; + } + + // wait a bit to avoid excessive writes + std::thread::sleep(std::time::Duration::from_millis(200)); + + // empty the channel + while rx.try_recv().is_ok() {} + + // write the configuration + if let Ok(config) = config.try_read() { + if let Err(e) = save_config(&config) { + eprintln!("Failed to save configuration: {}", e); + } + } else { + eprintln!("Failed to read configuration state"); + } + } + }); + Ok(()) +} + +/// +/// Get a snapshot of the current configuration +/// +pub fn get_config() -> Result { + let conf = CONFIG.get() + .expect("Configuration not loaded") + .try_read() + .map(|config| config.clone()); + if let Ok(config) = conf { + return Ok(config) + } + + anyhow::bail!("Failed to read configuration state") +} + +/// +/// Safely edit the configuration. +/// +pub fn edit_config(f: F) -> Result<(), anyhow::Error> +where + F: FnOnce(&mut TomlConfig) +{ + let mut config = CONFIG.get() + .expect("Configuration not loaded") + .write() + .map_err(|_| anyhow::anyhow!("Failed to acquire write lock on configuration"))?; + + f(&mut config); + + CONFIG_WRITER.get().unwrap().send(()) + .context("Failed to send configuration update signal") +} + +/// +/// Save the configuration to the file +/// +/// # Arguments +/// +/// `config` - The configuration to save +/// +pub fn save_config(config: &TomlConfig) -> Result<(), anyhow::Error> { + let path = find_config_file(); + + let parent = std::path::Path::new(&path).parent() + .context("Failed to get parent directory of config path")?; + std::fs::create_dir_all(parent) + .context("Failed to create config directory")?; + + let data = toml::to_string(config) + .context("Failed to serialize conf.toml")?; + std::fs::write(path, data) + .context("Failed to write conf.toml")?; + Ok(()) +} diff --git a/ui/src/config/structs.rs b/ui/src/config/structs.rs new file mode 100644 index 0000000..6c973f4 --- /dev/null +++ b/ui/src/config/structs.rs @@ -0,0 +1,93 @@ +use serde::{Deserialize, Serialize}; + +// multiplier +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Multiplier(i64); +impl Default for Multiplier { + fn default() -> Self { Multiplier(2) } +} +impl From for Multiplier { + fn from(value: i64) -> Self { Multiplier(value) } +} +impl Into for Multiplier { + fn into(self) -> f64 { self.0 as f64 } +} + +// flow scale +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct FlowScale(f64); +impl Default for FlowScale { + fn default() -> Self { FlowScale(1.0) } +} +impl From for FlowScale { + fn from(value: f64) -> Self { FlowScale(value) } +} +impl Into for FlowScale { + fn into(self) -> f64 { self.0 } +} + +// present mode +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum PresentMode { + #[serde(rename = "fifo", alias = "vsync")] + Vsync, + #[serde(rename = "immediate")] + Immediate, + #[serde(rename = "mailbox")] + Mailbox, +} +impl Default for PresentMode { + fn default() -> Self { PresentMode::Vsync } +} +impl From for PresentMode { + fn from(value: i64) -> Self { + match value { + 0 => PresentMode::Vsync, + 1 => PresentMode::Mailbox, + 2 => PresentMode::Immediate, + _ => PresentMode::Vsync, + } + } +} +impl Into for PresentMode { + fn into(self) -> u32 { + match self { + PresentMode::Vsync => 0, + PresentMode::Mailbox => 1, + PresentMode::Immediate => 2, + } + } +} + +/// Global configuration for the application +#[derive(Debug, Default, Clone, Deserialize, Serialize)] +pub struct TomlGlobal { + pub dll: Option +} + +/// Game-specific configuration +#[derive(Debug, Default, Clone, Deserialize, Serialize)] +pub struct TomlGame { + pub exe: String, + + #[serde(default)] + pub multiplier: Multiplier, + #[serde(default)] + pub flow_scale: FlowScale, + #[serde(default)] + pub performance_mode: bool, + #[serde(default)] + pub hdr_mode: bool, + #[serde(default)] + pub experimental_present_mode: PresentMode +} + +/// Main configuration structure +#[derive(Debug, Default, Clone, Deserialize, Serialize)] +pub struct TomlConfig { + pub version: i64, + #[serde(default)] + pub global: TomlGlobal, + #[serde(default)] + pub game: Vec +} diff --git a/ui/src/main.rs b/ui/src/main.rs new file mode 100644 index 0000000..d89c919 --- /dev/null +++ b/ui/src/main.rs @@ -0,0 +1,37 @@ +use std::sync::{Arc, OnceLock, RwLock}; + +use gtk::{gio, prelude::*}; +use adw; + +mod ui; +mod wrapper; +mod config; +mod utils; + +const APP_ID: &str = "gay.pancake.lsfg-vk"; + +#[derive(Debug)] +struct State { + selected_game: Option +} + +static STATE: OnceLock>> = OnceLock::new(); + +fn main() { + gio::resources_register_include!("lsfg-vk.gresource") + .expect("Failed to register resources"); + config::load_config() + .expect("Failed to load configuration"); + + // prepare the application state + STATE.set(Arc::new(RwLock::new(State { + selected_game: None + }))).expect("Failed to set application state"); + + // start the application + let app = adw::Application::builder() + .application_id(APP_ID) + .build(); + app.connect_activate(ui::build); + app.run(); +} diff --git a/ui/src/ui.rs b/ui/src/ui.rs new file mode 100644 index 0000000..c0eaac1 --- /dev/null +++ b/ui/src/ui.rs @@ -0,0 +1,42 @@ +use adw::{self, subclass::prelude::ObjectSubclassIsExt}; +use gtk::prelude::{WidgetExt, EditableExt, GtkWindowExt}; + +use crate::config; +use crate::wrapper; + +pub mod entry_handler; +pub mod main_handler; +pub mod sidebar_handler; + +pub fn build(app: &adw::Application) { + // create the main window + let window = wrapper::Window::new(app); + window.set_application(Some(app)); + let imp = window.imp(); + + // load profiles from configuration + let config = config::get_config().unwrap(); + for game in config.game.iter() { + let entry = wrapper::entry::Entry::new(); + entry.set_exe(game.exe.clone()); + entry_handler::add_entry(entry, imp.sidebar.imp().profiles.clone()); + } + + if let Some(dll_path) = config.global.dll { + imp.main.imp().dll.imp().entry.set_text(&dll_path); + } + + // register handlers on sidebar pane. + sidebar_handler::register_signals(&imp.sidebar, imp.main.clone()); + + // register handlers on main pane. + main_handler::register_signals(imp.sidebar.clone(), &imp.main); + + // activate the first profile if available + if let Some(entry) = imp.sidebar.imp().profiles.row_at_index(0) { + entry.activate(); + } + + // present the window + window.present(); +} diff --git a/ui/src/ui/entry_handler.rs b/ui/src/ui/entry_handler.rs new file mode 100644 index 0000000..707fc86 --- /dev/null +++ b/ui/src/ui/entry_handler.rs @@ -0,0 +1,54 @@ +use adw::subclass::prelude::ObjectSubclassIsExt; +use gtk::{gio, glib::object::CastNone, prelude::{ButtonExt, ListBoxRowExt, WidgetExt}}; + +use crate::{config, wrapper::entry, STATE}; + +/// +/// Register signals for removing presets when adding a new entry. +/// +pub fn add_entry(entry_: entry::Entry, profiles_: gtk::ListBox) { + let entry = entry_.clone(); + let profiles = profiles_.clone(); + entry_.imp().delete.connect_clicked(move |btn| { + // prompt for confirmation + let dialog = gtk::AlertDialog::builder() + .message("Delete Profile") + .detail("Are you sure you want to delete this profile?") + .buttons(vec!["Cancel".to_string(), "Delete".to_string()]) + .cancel_button(0) + .default_button(1) + .modal(true) + .build(); + let window = btn.root() + .and_downcast::() + .expect("Button root is not a Window"); + + let profiles = profiles.clone(); + let entry = entry.clone(); + dialog.choose(Some(&window), gio::Cancellable::NONE, move |result| { + if result.is_err() || result.unwrap() != 1 { + return; + } + + // remove config entry + let _ = config::edit_config(|config| { + config.game.remove(entry.index() as usize); + }); + + // remove ui entry + profiles.remove(&entry); + + // select next entry + let state = STATE.get().unwrap().clone(); + if let Ok(mut state) = state.write() { + state.selected_game = None; + } + + if let Some(entry) = profiles.row_at_index(0) { + entry.activate(); + } + }); + }); + + profiles_.append(&entry_); +} diff --git a/ui/src/ui/main_handler.rs b/ui/src/ui/main_handler.rs new file mode 100644 index 0000000..d33d28a --- /dev/null +++ b/ui/src/ui/main_handler.rs @@ -0,0 +1,170 @@ +use adw::subclass::prelude::ObjectSubclassIsExt; +use gtk::{gio::{self, prelude::FileExt}, glib::object::CastNone, prelude::{ButtonExt, EditableExt, GtkWindowExt, ListBoxRowExt, RangeExt, WidgetExt}}; + +use crate::{config, utils, wrapper::{entry, pane, popup}, STATE}; + +// update the currently selected game configuration +fn update_game(update: F) { + if let Ok(state) = STATE.get().unwrap().try_read() { + if let Some(selected_game) = state.selected_game { + let _ = config::edit_config(|config| { + update(&mut config.game[selected_game]) + }); + } + } +} + +/// +/// Register signals for preset preferences. +/// +pub fn register_signals(sidebar_: pane::PaneSidebar, main: &pane::PaneMain) { + let main = main.imp(); + let exe = main.profile_name.imp(); + let multiplier = main.multiplier.imp(); + let flow_scale = main.flow_scale.imp(); + let performance_mode = main.performance_mode.imp(); + let hdr_mode = main.hdr_mode.imp(); + let experimental_present_mode = main.experimental_present_mode.imp(); + + // preset opts + let sidebar = sidebar_.clone(); + exe.entry.connect_changed(move |entry| { + let mut exe = entry.text().to_string(); + if exe.trim().is_empty() { + exe = "new preset".to_string(); + } + + // rename list entry + let row_option = sidebar.imp().profiles.selected_row() + .and_downcast::(); + + if let Some(row) = row_option { + row.set_exe(exe.clone()); + } + + // update the game configuration + update_game(|conf| { + conf.exe = exe; + }); + }); + multiplier.number.connect_value_changed(|dropdown| { + update_game(|conf| { + conf.multiplier = (dropdown.value() as i64).into(); + }) + }); + flow_scale.slider.connect_value_changed(|slider| { + update_game(|conf| { + conf.flow_scale = (slider.value() / 100.0).into(); + }); + }); + performance_mode.switch.connect_state_notify(|switch| { + update_game(|conf| { + conf.performance_mode = switch.state(); + }); + }); + hdr_mode.switch.connect_state_notify(|switch| { + update_game(|conf| { + conf.hdr_mode = switch.state(); + }); + }); + experimental_present_mode.dropdown.connect_selected_notify(|dropdown| { + update_game(|conf| { + conf.experimental_present_mode = match dropdown.selected() { + 0 => config::PresentMode::Vsync, + 1 => config::PresentMode::Mailbox, + 2 => config::PresentMode::Immediate, + _ => config::PresentMode::Vsync, + }; + }); + }); + + // global opts + let dll = main.dll.imp(); + dll.entry.connect_changed(|entry| { + let _ = config::edit_config(|config| { + let mut text = entry.text().to_string(); + if text.trim().is_empty() { + config.global.dll = None; + } else { + if text.contains("~") { + let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/")); + text = text.replace("~", &home); + } + config.global.dll = Some(text); + } + }); + }); + + // utility buttons + let entry = dll.entry.clone(); + dll.btn.connect_clicked(move |btn| { + let dialog = gtk::FileDialog::new(); + dialog.set_title("Select Lossless.dll"); + + let filter = gtk::FileFilter::new(); + filter.set_name(Some("Lossless.dll")); + filter.add_pattern("Lossless.dll"); + + let filters = gio::ListStore::new::(); + filters.append(&filter); + dialog.set_filters(Some(&filters)); + dialog.set_default_filter(Some(&filter)); + + let window = btn.root() + .and_downcast::() + .unwrap(); + let entry = entry.clone(); + dialog.open(Some(&window), gio::Cancellable::NONE, move |result| { + if result.is_err() || result.as_ref().unwrap().path().is_none() { + return; + } + + let path = result.unwrap().path().unwrap(); + let path_str = path.to_string_lossy().to_string(); + + entry.set_text(&path_str); + let _ = config::edit_config(|config| { + config.global.dll = Some(path_str); + }); + }); + }); + + let entry = exe.entry.clone(); + exe.btn.connect_clicked(move |btn| { + let window = btn.root() + .and_downcast::() + .unwrap() + .application() + .unwrap(); + let picker = popup::ProcessPicker::new(); + picker.set_application(Some(&window)); + + let list = picker.imp().processes.clone(); + let processes = utils::find_vulkan_processes().unwrap_or_default(); + for process in &processes { + let entry = popup::ProcessEntry::new(); + entry.set_exe(process.0.clone()); + list.append(&entry); + } + + let entry = entry.clone(); + let picker_ = picker.clone(); + picker.imp().processes.connect_row_activated(move |_, row| { + let comm_str = processes[row.index() as usize].1.clone(); + + entry.set_text(&comm_str); + update_game(|conf| { + conf.exe = comm_str; + }); + + picker_.close(); + }); + + let picker_ = picker.clone(); + picker.imp().close.connect_clicked(move |_| { + picker_.close(); + }); + + picker.present(); + }); +} diff --git a/ui/src/ui/sidebar_handler.rs b/ui/src/ui/sidebar_handler.rs new file mode 100644 index 0000000..7d18dc2 --- /dev/null +++ b/ui/src/ui/sidebar_handler.rs @@ -0,0 +1,69 @@ +use adw::subclass::prelude::ObjectSubclassIsExt; +use gtk::prelude::{ButtonExt, EditableExt, ListBoxRowExt, RangeExt, WidgetExt}; + +use crate::{config, ui::entry_handler, wrapper::{pane, entry}, STATE}; + +/// +/// Register signals for adding and selecting presets. +/// +pub fn register_signals(sidebar_: &pane::PaneSidebar, main: pane::PaneMain) { + // activate signal + let state = STATE.get().unwrap().clone(); + sidebar_.imp().profiles.connect_row_activated(move |_, entry| { + // find config entry by index + let index = entry.index() as usize; + let config = config::get_config(); + if config.is_err() { + return; + } + let config = config.unwrap(); + let conf = config.game[index].clone(); + + // update main pane + let main = main.imp(); + let exe = main.profile_name.imp(); + let multiplier = main.multiplier.imp(); + let flow_scale = main.flow_scale.imp(); + let performance_mode = main.performance_mode.imp(); + let hdr_mode = main.hdr_mode.imp(); + let experimental_present_mode = main.experimental_present_mode.imp(); + + // (lock state early, so the ui update doesn't override the config) + if let Ok(mut state) = state.write() { + exe.entry.set_text(&conf.exe); + multiplier.number.set_value(conf.multiplier.into()); + flow_scale.slider.set_value(Into::::into(conf.flow_scale) * 100.0); + performance_mode.switch.set_active(conf.performance_mode); + hdr_mode.switch.set_active(conf.hdr_mode); + experimental_present_mode.dropdown.set_selected(conf.experimental_present_mode.into()); + + // update state + state.selected_game = Some(index); + } + }); + + // create signal + let sidebar = sidebar_.clone(); + sidebar_.imp().create.connect_clicked(move |_| { + // ensure no config entry with the same name exist + let config = config::get_config().unwrap(); + if config.game.iter().any(|e| e.exe == "new profile") { + return; + } + + // create config entry + let mut conf_entry = config::TomlGame::default(); + conf_entry.exe = "new profile".to_string(); + let _ = config::edit_config(|config| { + config.game.push(conf_entry.clone()); + }); + + // add entry to sidebar + let entry = entry::Entry::new(); + entry.set_exe(conf_entry.exe); + entry_handler::add_entry(entry.clone(), sidebar.imp().profiles.clone()); + + // select the new entry + entry.activate(); + }); +} diff --git a/ui/src/utils.rs b/ui/src/utils.rs new file mode 100644 index 0000000..190b503 --- /dev/null +++ b/ui/src/utils.rs @@ -0,0 +1,29 @@ +use procfs::{process, ProcResult}; + +pub fn find_vulkan_processes() -> ProcResult> { + let mut processes = Vec::new(); + let apps = process::all_processes()?; + for app in apps { + let Ok(prc) = app else { continue; }; + + // ensure vulkan is loaded + let Ok(maps) = proc_maps::get_process_maps(prc.pid()) else { + continue; + }; + let result = maps.iter() + .filter_map(|map| map.filename()) + .map(|filename| filename.to_string_lossy().to_string()) + .any(|filename| filename.to_lowercase().contains("vulkan")); + if !result { + continue; + } + + // format process information + let pid = prc.pid(); + let name = prc.stat()?.comm; + let process_info = format!("PID {}: {}", pid, name); + processes.push((process_info, name)); + } + + Ok(processes) +} diff --git a/ui/src/wrapper.rs b/ui/src/wrapper.rs new file mode 100644 index 0000000..a1fdf92 --- /dev/null +++ b/ui/src/wrapper.rs @@ -0,0 +1,79 @@ +use gtk::glib; +use gtk; +use adw; +use gtk::glib::types::StaticTypeExt; + +pub mod entry; +pub mod pane; +pub mod pref; +pub mod popup; + +pub mod imp { + use gtk::subclass::prelude::*; + use adw::subclass::prelude::*; + use crate::wrapper::pane::*; + use gtk::{glib, CompositeTemplate}; + + #[derive(CompositeTemplate, Default)] + #[template(resource = "/gay/pancake/lsfg-vk/window.ui")] + pub struct Window { + #[template_child] + pub main: TemplateChild, + #[template_child] + pub sidebar: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for Window { + const NAME: &'static str = "LSApplicationWindow"; + type Type = super::Window; + type ParentType = adw::ApplicationWindow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for Window { + fn constructed(&self) { + self.parent_constructed(); + } + } + + impl WidgetImpl for Window {} + impl WindowImpl for Window {} + impl ApplicationWindowImpl for Window {} + impl AdwWindowImpl for Window {} + impl AdwApplicationWindowImpl for Window {} +} + +glib::wrapper! { + pub struct Window(ObjectSubclass) + @extends + adw::ApplicationWindow, adw::Window, + gtk::ApplicationWindow, gtk::Window, gtk::Widget, + @implements + gtk::gio::ActionGroup, gtk::gio::ActionMap, + gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, + gtk::Native, gtk::Root, gtk::ShortcutManager; +} + +impl Window { + pub fn new(app: &adw::Application) -> Self { + pref::PrefDropdown::ensure_type(); + pref::PrefEntry::ensure_type(); + pref::PrefNumber::ensure_type(); + pref::PrefSlider::ensure_type(); + pref::PrefSwitch::ensure_type(); + pane::PaneMain::ensure_type(); + pane::PaneSidebar::ensure_type(); + + glib::Object::builder() + .property("application", app) + .build() + } +} diff --git a/ui/src/wrapper/entry.rs b/ui/src/wrapper/entry.rs new file mode 100644 index 0000000..90e2a88 --- /dev/null +++ b/ui/src/wrapper/entry.rs @@ -0,0 +1,18 @@ +use gtk::glib; +use gtk; + +pub mod entry; + +glib::wrapper! { + pub struct Entry(ObjectSubclass) + @extends + gtk::ListBoxRow, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +impl Entry { + pub fn new() -> Self { + glib::Object::new() + } +} diff --git a/ui/src/wrapper/entry/entry.rs b/ui/src/wrapper/entry/entry.rs new file mode 100644 index 0000000..301e974 --- /dev/null +++ b/ui/src/wrapper/entry/entry.rs @@ -0,0 +1,41 @@ +use std::cell::RefCell; + +use gtk::glib; +use gtk::subclass::prelude::*; +use gtk::prelude::*; + +#[derive(gtk::CompositeTemplate, glib::Properties, Default)] +#[properties(wrapper_type = super::Entry)] +#[template(resource = "/gay/pancake/lsfg-vk/entry/entry.ui")] +pub struct Entry { + #[property(get, set)] + exe: RefCell, + + #[template_child] + pub delete: TemplateChild, +} + +#[glib::object_subclass] +impl ObjectSubclass for Entry { + const NAME: &'static str = "LSEntry"; + type Type = super::Entry; + type ParentType = gtk::ListBoxRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for Entry { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for Entry {} +impl ListBoxRowImpl for Entry {} diff --git a/ui/src/wrapper/pane.rs b/ui/src/wrapper/pane.rs new file mode 100644 index 0000000..e9ac12e --- /dev/null +++ b/ui/src/wrapper/pane.rs @@ -0,0 +1,35 @@ +use gtk::glib; +use gtk; +use adw; + +pub mod main; +pub mod sidebar; + +glib::wrapper! { + pub struct PaneMain(ObjectSubclass) + @extends + adw::NavigationPage, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +glib::wrapper! { + pub struct PaneSidebar(ObjectSubclass) + @extends + adw::NavigationPage, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + + +impl PaneMain { + pub fn new() -> Self { + glib::Object::new() + } +} + +impl PaneSidebar { + pub fn new() -> Self { + glib::Object::new() + } +} diff --git a/ui/src/wrapper/pane/main.rs b/ui/src/wrapper/pane/main.rs new file mode 100644 index 0000000..e9b0def --- /dev/null +++ b/ui/src/wrapper/pane/main.rs @@ -0,0 +1,47 @@ +use gtk::glib; +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use crate::wrapper::pref::*; + +#[derive(gtk::CompositeTemplate, Default)] +#[template(resource = "/gay/pancake/lsfg-vk/pane/main.ui")] +pub struct PaneMain { + #[template_child] + pub dll: TemplateChild, + #[template_child] + pub profile_name: TemplateChild, + #[template_child] + pub multiplier: TemplateChild, + #[template_child] + pub flow_scale: TemplateChild, + #[template_child] + pub performance_mode: TemplateChild, + #[template_child] + pub hdr_mode: TemplateChild, + #[template_child] + pub experimental_present_mode: TemplateChild +} + +#[glib::object_subclass] +impl ObjectSubclass for PaneMain { + const NAME: &'static str = "LSPaneMain"; + type Type = super::PaneMain; + type ParentType = adw::NavigationPage; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +impl ObjectImpl for PaneMain { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for PaneMain {} +impl NavigationPageImpl for PaneMain {} diff --git a/ui/src/wrapper/pane/sidebar.rs b/ui/src/wrapper/pane/sidebar.rs new file mode 100644 index 0000000..20ea990 --- /dev/null +++ b/ui/src/wrapper/pane/sidebar.rs @@ -0,0 +1,36 @@ +use gtk::glib; +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; + +#[derive(gtk::CompositeTemplate, Default)] +#[template(resource = "/gay/pancake/lsfg-vk/pane/sidebar.ui")] +pub struct PaneSidebar { + #[template_child] + pub profiles: TemplateChild, + #[template_child] + pub create: TemplateChild +} + +#[glib::object_subclass] +impl ObjectSubclass for PaneSidebar { + const NAME: &'static str = "LSPaneSidebar"; + type Type = super::PaneSidebar; + type ParentType = adw::NavigationPage; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +impl ObjectImpl for PaneSidebar { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for PaneSidebar {} +impl NavigationPageImpl for PaneSidebar {} diff --git a/ui/src/wrapper/popup.rs b/ui/src/wrapper/popup.rs new file mode 100644 index 0000000..64b5bc1 --- /dev/null +++ b/ui/src/wrapper/popup.rs @@ -0,0 +1,38 @@ +use gtk::glib; +use gtk; +use adw; + +pub mod process; +pub mod process_entry; + +glib::wrapper! { + pub struct ProcessPicker(ObjectSubclass) + @extends + adw::ApplicationWindow, adw::Window, + gtk::ApplicationWindow, gtk::Window, gtk::Widget, + @implements + gtk::gio::ActionGroup, gtk::gio::ActionMap, + gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, + gtk::Native, gtk::Root, gtk::ShortcutManager; +} + +glib::wrapper! { + pub struct ProcessEntry(ObjectSubclass) + @extends + gtk::ListBoxRow, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + + +impl ProcessPicker { + pub fn new() -> Self { + glib::Object::new() + } +} + +impl ProcessEntry { + pub fn new() -> Self { + glib::Object::new() + } +} diff --git a/ui/src/wrapper/popup/process.rs b/ui/src/wrapper/popup/process.rs new file mode 100644 index 0000000..3ef4d59 --- /dev/null +++ b/ui/src/wrapper/popup/process.rs @@ -0,0 +1,39 @@ +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use gtk::{glib, CompositeTemplate}; + +#[derive(CompositeTemplate, Default)] +#[template(resource = "/gay/pancake/lsfg-vk/popup/process.ui")] +pub struct ProcessPicker { + #[template_child] + pub processes: TemplateChild, + #[template_child] + pub close: TemplateChild, +} + +#[glib::object_subclass] +impl ObjectSubclass for ProcessPicker { + const NAME: &'static str = "LSProcessPicker"; + type Type = super::ProcessPicker; + type ParentType = adw::ApplicationWindow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +impl ObjectImpl for ProcessPicker { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for ProcessPicker {} +impl WindowImpl for ProcessPicker {} +impl ApplicationWindowImpl for ProcessPicker {} +impl AdwWindowImpl for ProcessPicker {} +impl AdwApplicationWindowImpl for ProcessPicker {} diff --git a/ui/src/wrapper/popup/process_entry.rs b/ui/src/wrapper/popup/process_entry.rs new file mode 100644 index 0000000..4509a2e --- /dev/null +++ b/ui/src/wrapper/popup/process_entry.rs @@ -0,0 +1,38 @@ +use std::cell::RefCell; + +use gtk::glib; +use gtk::subclass::prelude::*; +use gtk::prelude::*; + +#[derive(gtk::CompositeTemplate, glib::Properties, Default)] +#[properties(wrapper_type = super::ProcessEntry)] +#[template(resource = "/gay/pancake/lsfg-vk/popup/process_entry.ui")] +pub struct ProcessEntry { + #[property(get, set)] + exe: RefCell, +} + +#[glib::object_subclass] +impl ObjectSubclass for ProcessEntry { + const NAME: &'static str = "LSProcessEntry"; + type Type = super::ProcessEntry; + type ParentType = gtk::ListBoxRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for ProcessEntry { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for ProcessEntry {} +impl ListBoxRowImpl for ProcessEntry {} diff --git a/ui/src/wrapper/pref.rs b/ui/src/wrapper/pref.rs new file mode 100644 index 0000000..7541955 --- /dev/null +++ b/ui/src/wrapper/pref.rs @@ -0,0 +1,79 @@ +use gtk::glib; +use gtk; +use adw; + +pub mod dropdown; +pub mod entry; +pub mod number; +pub mod slider; +pub mod switch; + +glib::wrapper! { + pub struct PrefDropdown(ObjectSubclass) + @extends + adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +glib::wrapper! { + pub struct PrefSwitch(ObjectSubclass) + @extends + adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +glib::wrapper! { + pub struct PrefNumber(ObjectSubclass) + @extends + adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +glib::wrapper! { + pub struct PrefSlider(ObjectSubclass) + @extends + adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +glib::wrapper! { + pub struct PrefEntry(ObjectSubclass) + @extends + adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +impl PrefDropdown { + pub fn new() -> Self { + glib::Object::new() + } +} + +impl PrefSwitch { + pub fn new() -> Self { + glib::Object::new() + } +} + +impl PrefNumber { + pub fn new() -> Self { + glib::Object::new() + } +} + +impl PrefSlider { + pub fn new() -> Self { + glib::Object::new() + } +} + +impl PrefEntry { + pub fn new() -> Self { + glib::Object::new() + } +} diff --git a/ui/src/wrapper/pref/dropdown.rs b/ui/src/wrapper/pref/dropdown.rs new file mode 100644 index 0000000..2020a6a --- /dev/null +++ b/ui/src/wrapper/pref/dropdown.rs @@ -0,0 +1,49 @@ +use std::cell::RefCell; + +use gtk::glib; +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use adw::prelude::*; + +#[derive(gtk::CompositeTemplate, glib::Properties, Default)] +#[properties(wrapper_type = super::PrefDropdown)] +#[template(resource = "/gay/pancake/lsfg-vk/pref/dropdown.ui")] +pub struct PrefDropdown { + #[property(get, set)] + opt_name: RefCell, + #[property(get, set)] + opt_subtitle: RefCell, + #[property(get, set)] + default_selection: RefCell, + #[property(get, set)] + options: RefCell, + + #[template_child] + pub dropdown: TemplateChild, +} + +#[glib::object_subclass] +impl ObjectSubclass for PrefDropdown { + const NAME: &'static str = "LSPrefDropdown"; + type Type = super::PrefDropdown; + type ParentType = adw::PreferencesRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for PrefDropdown { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for PrefDropdown {} +impl ListBoxRowImpl for PrefDropdown {} +impl PreferencesRowImpl for PrefDropdown {} diff --git a/ui/src/wrapper/pref/entry.rs b/ui/src/wrapper/pref/entry.rs new file mode 100644 index 0000000..f274193 --- /dev/null +++ b/ui/src/wrapper/pref/entry.rs @@ -0,0 +1,53 @@ +use std::cell::RefCell; + +use gtk::glib; +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use adw::prelude::*; + +#[derive(gtk::CompositeTemplate, glib::Properties, Default)] +#[properties(wrapper_type = super::PrefEntry)] +#[template(resource = "/gay/pancake/lsfg-vk/pref/entry.ui")] +pub struct PrefEntry { + #[property(get, set)] + opt_name: RefCell, + #[property(get, set)] + opt_subtitle: RefCell, + #[property(get, set)] + default_text: RefCell, + #[property(get, set)] + tooltip_text: RefCell, + #[property(get, set)] + icon_name: RefCell, + + #[template_child] + pub entry: TemplateChild, + #[template_child] + pub btn: TemplateChild, +} + +#[glib::object_subclass] +impl ObjectSubclass for PrefEntry { + const NAME: &'static str = "LSPrefEntry"; + type Type = super::PrefEntry; + type ParentType = adw::PreferencesRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for PrefEntry { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for PrefEntry {} +impl ListBoxRowImpl for PrefEntry {} +impl PreferencesRowImpl for PrefEntry {} diff --git a/ui/src/wrapper/pref/number.rs b/ui/src/wrapper/pref/number.rs new file mode 100644 index 0000000..fc6bad3 --- /dev/null +++ b/ui/src/wrapper/pref/number.rs @@ -0,0 +1,45 @@ +use std::cell::RefCell; + +use gtk::glib; +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use adw::prelude::*; + +#[derive(gtk::CompositeTemplate, glib::Properties, Default)] +#[properties(wrapper_type = super::PrefNumber)] +#[template(resource = "/gay/pancake/lsfg-vk/pref/number.ui")] +pub struct PrefNumber { + #[property(get, set)] + opt_name: RefCell, + #[property(get, set)] + opt_subtitle: RefCell, + + #[template_child] + pub number: TemplateChild, +} + +#[glib::object_subclass] +impl ObjectSubclass for PrefNumber { + const NAME: &'static str = "LSPrefNumber"; + type Type = super::PrefNumber; + type ParentType = adw::PreferencesRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for PrefNumber { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for PrefNumber {} +impl ListBoxRowImpl for PrefNumber {} +impl PreferencesRowImpl for PrefNumber {} diff --git a/ui/src/wrapper/pref/slider.rs b/ui/src/wrapper/pref/slider.rs new file mode 100644 index 0000000..207cbd0 --- /dev/null +++ b/ui/src/wrapper/pref/slider.rs @@ -0,0 +1,45 @@ +use std::cell::RefCell; + +use gtk::glib; +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use adw::prelude::*; + +#[derive(gtk::CompositeTemplate, glib::Properties, Default)] +#[properties(wrapper_type = super::PrefSlider)] +#[template(resource = "/gay/pancake/lsfg-vk/pref/slider.ui")] +pub struct PrefSlider { + #[property(get, set)] + opt_name: RefCell, + #[property(get, set)] + opt_subtitle: RefCell, + + #[template_child] + pub slider: TemplateChild, +} + +#[glib::object_subclass] +impl ObjectSubclass for PrefSlider { + const NAME: &'static str = "LSPrefSlider"; + type Type = super::PrefSlider; + type ParentType = adw::PreferencesRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for PrefSlider { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for PrefSlider {} +impl ListBoxRowImpl for PrefSlider {} +impl PreferencesRowImpl for PrefSlider {} diff --git a/ui/src/wrapper/pref/switch.rs b/ui/src/wrapper/pref/switch.rs new file mode 100644 index 0000000..7f3d77b --- /dev/null +++ b/ui/src/wrapper/pref/switch.rs @@ -0,0 +1,47 @@ +use std::cell::RefCell; + +use gtk::glib; +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use adw::prelude::*; + +#[derive(gtk::CompositeTemplate, glib::Properties, Default)] +#[properties(wrapper_type = super::PrefSwitch)] +#[template(resource = "/gay/pancake/lsfg-vk/pref/switch.ui")] +pub struct PrefSwitch { + #[property(get, set)] + opt_name: RefCell, + #[property(get, set)] + opt_subtitle: RefCell, + #[property(get, set)] + default_state: RefCell, + + #[template_child] + pub switch: TemplateChild, +} + +#[glib::object_subclass] +impl ObjectSubclass for PrefSwitch { + const NAME: &'static str = "LSPrefSwitch"; + type Type = super::PrefSwitch; + type ParentType = adw::PreferencesRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for PrefSwitch { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for PrefSwitch {} +impl ListBoxRowImpl for PrefSwitch {} +impl PreferencesRowImpl for PrefSwitch {}