v0.9.0 (pre-release)

This commit is contained in:
Pancake 2025-07-26 17:55:26 +02:00 committed by GitHub
commit 7c0c29c1e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
165 changed files with 13442 additions and 38 deletions

View file

@ -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"

1
.gitattributes vendored
View file

@ -1,4 +1,3 @@
*.cpp diff=cpp eol=lf
*.hpp diff=cpp eol=lf
*.md diff=markdown eol=lf
*.cs binary

2
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,2 @@
github: [PancakeTAS]
ko_fi: pancaketas

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -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

View file

@ -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

View file

@ -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?

69
.github/workflows/build.yml vendored Normal file
View file

@ -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

24
.github/workflows/flatpak.yml vendored Normal file
View file

@ -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

47
.github/workflows/package.yml vendored Normal file
View file

@ -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

4
.gitignore vendored
View file

@ -1,7 +1,11 @@
# cmake files
/build
# cargo files
/ui/target
# ide/lsp files
/.zed
/.vscode
/.clangd
/.cache

12
.gitmodules vendored Normal file
View file

@ -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

View file

@ -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)

21
LICENSE.md Normal file
View file

@ -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.

26
README.md Normal file
View file

@ -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.

View file

@ -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"
}
}
}

37
framegen/.clang-tidy Normal file
View file

@ -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"

3
framegen/.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
*.cpp diff=cpp eol=lf
*.hpp diff=cpp eol=lf
*.md diff=markdown eol=lf

9
framegen/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
# cmake files
/build
# ide/lsp files
/.zed
/.vscode
/.clangd
/.cache
/.ccls

66
framegen/CMakeLists.txt Normal file
View file

@ -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()

21
framegen/LICENSE.md Normal file
View file

@ -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.

14
framegen/README.md Normal file
View file

@ -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.

View file

@ -0,0 +1,62 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <exception>
#include <stdexcept>
#include <string>
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;
};
}

View file

@ -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 <vulkan/vulkan_core.h>
#include <optional>
#include <cstddef>
#include <cstdint>
#include <string>
#include <array>
#include <vector>
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<Core::Image>& image) {
if (image.has_value()) this->addR2W(*image); return *this; }
BarrierBuilder& addW2R(std::optional<Core::Image>& image) {
if (image.has_value()) this->addW2R(*image); return *this; }
/// Add a list of resources to the barrier builder.
BarrierBuilder& addR2W(std::vector<Core::Image>& images) {
for (auto& image : images) this->addR2W(image); return *this; }
BarrierBuilder& addW2R(std::vector<Core::Image>& images) {
for (auto& image : images) this->addW2R(image); return *this; }
/// Add an array of resources to the barrier builder.
template<std::size_t N>
BarrierBuilder& addR2W(std::array<Core::Image, N>& images) {
for (auto& image : images) this->addR2W(image); return *this; }
template<std::size_t N>
BarrierBuilder& addW2R(std::array<Core::Image, N>& images) {
for (auto& image : images) this->addW2R(image); return *this; }
/// Finish building the barrier
void build() const;
private:
const Core::CommandBuffer* commandBuffer;
std::vector<VkImageMemoryBarrier2> 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;
};
}

View file

@ -0,0 +1,71 @@
#pragma once
#include "core/device.hpp"
#include <vulkan/vulkan_core.h>
#include <cstddef>
#include <memory>
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<typename T>
Buffer(const Core::Device& device, const T& data, VkBufferUsageFlags usage)
: size(sizeof(T)) {
construct(device, reinterpret_cast<const void*>(&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<VkBuffer> buffer;
std::shared_ptr<VkDeviceMemory> memory;
size_t size{};
};
}

View file

@ -0,0 +1,112 @@
#pragma once
#include "core/commandpool.hpp"
#include "core/fence.hpp"
#include "core/semaphore.hpp"
#include "core/device.hpp"
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <optional>
#include <vector>
#include <memory>
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> fence,
const std::vector<Semaphore>& waitSemaphores = {},
std::optional<std::vector<uint64_t>> waitSemaphoreValues = std::nullopt,
const std::vector<Semaphore>& signalSemaphores = {},
std::optional<std::vector<uint64_t>> 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<CommandBufferState> state;
std::shared_ptr<VkCommandBuffer> commandBuffer;
};
}

View file

@ -0,0 +1,42 @@
#pragma once
#include "core/device.hpp"
#include <vulkan/vulkan_core.h>
#include <memory>
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<VkCommandPool> commandPool;
};
}

View file

@ -0,0 +1,42 @@
#pragma once
#include "core/device.hpp"
#include <vulkan/vulkan_core.h>
#include <memory>
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<VkDescriptorPool> descriptorPool;
};
}

View file

@ -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 <vulkan/vulkan_core.h>
#include <vector>
#include <cstddef>
#include <array>
#include <optional>
#include <memory>
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<VkDescriptorSet> 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<Image>& images) {
for (const auto& image : images) this->add(type, image); return *this; }
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector<Sampler>& samplers) {
for (const auto& sampler : samplers) this->add(type, sampler); return *this; }
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector<Buffer>& buffers) {
for (const auto& buffer : buffers) this->add(type, buffer); return *this; }
/// Add an array of resources
template<std::size_t N>
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array<Image, N>& images) {
for (const auto& image : images) this->add(type, image); return *this; }
template<std::size_t N>
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array<Sampler, N>& samplers) {
for (const auto& sampler : samplers) this->add(type, sampler); return *this; }
template<std::size_t N>
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array<Buffer, N>& 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>& image) {
if (image.has_value()) this->add(type, *image); else this->add(type); return *this; }
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional<Sampler>& sampler) {
if (sampler.has_value()) this->add(type, *sampler); else this->add(type); return *this; }
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional<Buffer>& 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<VkWriteDescriptorSet> entries;
};
}

View file

@ -0,0 +1,53 @@
#pragma once
#include "core/instance.hpp"
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <memory>
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<VkDevice> device;
VkPhysicalDevice physicalDevice{};
uint32_t computeFamilyIdx{0};
VkQueue computeQueue{};
};
}

View file

@ -0,0 +1,63 @@
#pragma once
#include "core/device.hpp"
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <memory>
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<VkFence> fence;
};
}

View file

@ -0,0 +1,87 @@
#pragma once
#include "core/device.hpp"
#include <vulkan/vulkan_core.h>
#include <memory>
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<VkImage> image;
std::shared_ptr<VkDeviceMemory> memory;
std::shared_ptr<VkImageView> view;
std::shared_ptr<VkImageLayout> layout;
VkExtent2D extent{};
VkFormat format{};
VkImageAspectFlags aspectFlags{};
};
}

View file

@ -0,0 +1,36 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <memory>
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<VkInstance> instance;
};
}

View file

@ -0,0 +1,55 @@
#pragma once
#include "core/commandbuffer.hpp"
#include "core/shadermodule.hpp"
#include "core/device.hpp"
#include <vulkan/vulkan_core.h>
#include <memory>
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<VkPipeline> pipeline;
std::shared_ptr<VkPipelineLayout> layout;
};
}

View file

@ -0,0 +1,48 @@
#pragma once
#include "core/device.hpp"
#include <vulkan/vulkan_core.h>
#include <memory>
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<VkSampler> sampler;
};
}

View file

@ -0,0 +1,80 @@
#pragma once
#include "core/device.hpp"
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <optional>
#include <memory>
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<uint32_t> 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<VkSemaphore> semaphore;
bool isTimeline{};
};
}

View file

@ -0,0 +1,52 @@
#pragma once
#include "core/device.hpp"
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <cstddef>
#include <utility>
#include <vector>
#include <memory>
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<uint8_t>& code,
const std::vector<std::pair<size_t, VkDescriptorType>>& 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<VkShaderModule> shaderModule;
std::shared_ptr<VkDescriptorSetLayout> descriptorSetLayout;
};
}

View file

@ -0,0 +1,69 @@
#pragma once
#include "core/device.hpp"
#include "core/buffer.hpp"
#include "core/sampler.hpp"
#include "vulkan/vulkan_core.h"
#include <cstdint>
#include <unordered_map>
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<uint64_t, Core::Buffer> buffers;
std::unordered_map<uint64_t, Core::Sampler> samplers;
bool isHdr{};
float flowScale{};
};
}

View file

@ -0,0 +1,65 @@
#pragma once
#include "core/device.hpp"
#include "core/pipeline.hpp"
#include "core/shadermodule.hpp"
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <cstddef>
#include <functional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
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<std::vector<uint8_t>(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<std::pair<size_t, VkDescriptorType>>& 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<std::vector<uint8_t>(const std::string&)> source;
std::unordered_map<std::string, Core::ShaderModule> shaders;
std::unordered_map<std::string, Core::Pipeline> pipelines;
};
}

View file

@ -0,0 +1,66 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <functional>
#include <cstdint>
#include <string>
#include <vector>
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<std::vector<uint8_t>(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<int>& 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<int>& 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();
}

View file

@ -0,0 +1,66 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <functional>
#include <cstdint>
#include <string>
#include <vector>
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<std::vector<uint8_t>(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<int>& 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<int>& 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();
}

View file

@ -0,0 +1,24 @@
#include "common/exception.hpp"
#include <vulkan/vulkan_core.h>
#include <exception>
#include <stdexcept>
#include <cstdint>
#include <format>
#include <string>
using namespace LSFG;
vulkan_error::vulkan_error(VkResult result, const std::string& message)
: std::runtime_error(std::format("{} (error {})", message, static_cast<int32_t>(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;

View file

@ -0,0 +1,191 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#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 <cstdint>
#include <cerrno>
#include <cstdlib>
#include <fstream>
#include <string>
#include <ios>
#include <system_error>
#include <vector>
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<uint32_t>(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<char> code(static_cast<size_t>(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<uint32_t>(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, &region
);
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.");
}

View file

@ -0,0 +1,86 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/buffer.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
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<uint32_t> 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<void**>(&buf));
if (res != VK_SUCCESS || buf == nullptr)
throw LSFG::vulkan_error(res, "Failed to map memory for Vulkan buffer");
std::copy_n(reinterpret_cast<const uint8_t*>(data), this->size, buf);
vkUnmapMemory(device.handle(), memoryHandle);
// store buffer and memory in shared ptr
this->buffer = std::shared_ptr<VkBuffer>(
new VkBuffer(bufferHandle),
[dev = device.handle()](VkBuffer* img) {
vkDestroyBuffer(dev, *img, nullptr);
}
);
this->memory = std::shared_ptr<VkDeviceMemory>(
new VkDeviceMemory(memoryHandle),
[dev = device.handle()](VkDeviceMemory* mem) {
vkFreeMemory(dev, *mem, nullptr);
}
);
}

View file

@ -0,0 +1,125 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#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 <memory>
#include <stdexcept>
#include <cstdint>
#include <optional>
#include <vector>
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>(CommandBufferState::Empty);
this->commandBuffer = std::shared_ptr<VkCommandBuffer>(
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> fence,
const std::vector<Semaphore>& waitSemaphores,
std::optional<std::vector<uint64_t>> waitSemaphoreValues,
const std::vector<Semaphore>& signalSemaphores,
std::optional<std::vector<uint64_t>> signalSemaphoreValues) {
if (*this->state != CommandBufferState::Full)
throw std::logic_error("Command buffer is not in Full state");
const std::vector<VkPipelineStageFlags> 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<uint32_t>(waitSemaphoreValues->size());
timelineInfo.pWaitSemaphoreValues = waitSemaphoreValues->data();
}
if (signalSemaphoreValues.has_value()) {
timelineInfo.signalSemaphoreValueCount =
static_cast<uint32_t>(signalSemaphoreValues->size());
timelineInfo.pSignalSemaphoreValues = signalSemaphoreValues->data();
}
std::vector<VkSemaphore> waitSemaphoresHandles;
waitSemaphoresHandles.reserve(waitSemaphores.size());
for (const auto& semaphore : waitSemaphores)
waitSemaphoresHandles.push_back(semaphore.handle());
std::vector<VkSemaphore> 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<uint32_t>(waitSemaphores.size()),
.pWaitSemaphores = waitSemaphoresHandles.data(),
.pWaitDstStageMask = waitStages.data(),
.commandBufferCount = 1,
.pCommandBuffers = &(*this->commandBuffer),
.signalSemaphoreCount = static_cast<uint32_t>(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;
}

View file

@ -0,0 +1,30 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/commandpool.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"
#include <memory>
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<VkCommandPool>(
new VkCommandPool(commandPoolHandle),
[dev = device.handle()](VkCommandPool* commandPoolHandle) {
vkDestroyCommandPool(dev, *commandPoolHandle, nullptr);
}
);
}

View file

@ -0,0 +1,41 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/descriptorpool.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"
#include <array>
#include <cstdint>
#include <memory>
using namespace LSFG::Core;
DescriptorPool::DescriptorPool(const Core::Device& device) {
// create descriptor pool
const std::array<VkDescriptorPoolSize, 4> 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<uint32_t>(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<VkDescriptorPool>(
new VkDescriptorPool(poolHandle),
[dev = device.handle()](VkDescriptorPool* poolHandle) {
vkDestroyDescriptorPool(dev, *poolHandle, nullptr);
}
);
}

View file

@ -0,0 +1,129 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#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 <memory>
#include <cstdint>
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<VkDescriptorSet>(
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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(this->entries.size()),
.descriptorCount = 1,
.descriptorType = type,
.pImageInfo = new VkDescriptorImageInfo {
},
.pBufferInfo = nullptr
});
return *this;
}
void DescriptorSetUpdateBuilder::build() {
vkUpdateDescriptorSets(this->device->handle(),
static_cast<uint32_t>(this->entries.size()),
this->entries.data(), 0, nullptr);
// NOLINTBEGIN
for (const auto& entry : this->entries) {
delete entry.pImageInfo;
delete entry.pBufferInfo;
}
// NOLINTEND
}

View file

@ -0,0 +1,117 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/device.hpp"
#include "core/instance.hpp"
#include "common/exception.hpp"
#include <cstdint>
#include <memory>
#include <optional>
#include <vector>
using namespace LSFG::Core;
const std::vector<const char*> 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<VkPhysicalDevice> 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<VkPhysicalDevice> physicalDevice;
for (const auto& device : devices) {
VkPhysicalDeviceProperties properties;
vkGetPhysicalDeviceProperties(device, &properties);
const uint64_t uuid =
static_cast<uint64_t>(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<VkQueueFamilyProperties> queueFamilies(familyCount);
vkGetPhysicalDeviceQueueFamilyProperties(*physicalDevice, &familyCount, queueFamilies.data());
std::optional<uint32_t> 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<uint32_t>(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<VkDevice>(
new VkDevice(deviceHandle),
[](VkDevice* device) {
vkDestroyDevice(*device, nullptr);
}
);
}

View file

@ -0,0 +1,46 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/fence.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"
#include <memory>
#include <cstdint>
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<VkFence>(
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;
}

242
framegen/src/core/image.cpp Normal file
View file

@ -0,0 +1,242 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/image.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"
#include <cstdint>
#include <memory>
#include <optional>
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<uint32_t> 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<VkImageLayout>(VK_IMAGE_LAYOUT_UNDEFINED);
this->image = std::shared_ptr<VkImage>(
new VkImage(imageHandle),
[dev = device.handle()](VkImage* img) {
vkDestroyImage(dev, *img, nullptr);
}
);
this->memory = std::shared_ptr<VkDeviceMemory>(
new VkDeviceMemory(memoryHandle),
[dev = device.handle()](VkDeviceMemory* mem) {
vkFreeMemory(dev, *mem, nullptr);
}
);
this->view = std::shared_ptr<VkImageView>(
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<uint32_t> 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<VkImageLayout>(VK_IMAGE_LAYOUT_UNDEFINED);
this->image = std::shared_ptr<VkImage>(
new VkImage(imageHandle),
[dev = device.handle()](VkImage* img) {
vkDestroyImage(dev, *img, nullptr);
}
);
this->memory = std::shared_ptr<VkDeviceMemory>(
new VkDeviceMemory(memoryHandle),
[dev = device.handle()](VkDeviceMemory* mem) {
vkFreeMemory(dev, *mem, nullptr);
}
);
this->view = std::shared_ptr<VkImageView>(
new VkImageView(viewHandle),
[dev = device.handle()](VkImageView* imgView) {
vkDestroyImageView(dev, *imgView, nullptr);
}
);
}

View file

@ -0,0 +1,49 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/instance.hpp"
#include "common/exception.hpp"
#include <cstdint>
#include <memory>
#include <vector>
using namespace LSFG::Core;
const std::vector<const char*> 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<uint32_t>(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<VkInstance>(
new VkInstance(instanceHandle),
[](VkInstance* instance) {
vkDestroyInstance(*instance, nullptr);
}
);
}

View file

@ -0,0 +1,62 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/pipeline.hpp"
#include "core/device.hpp"
#include "core/shadermodule.hpp"
#include "core/commandbuffer.hpp"
#include "common/exception.hpp"
#include <memory>
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<VkPipelineLayout>(
new VkPipelineLayout(layoutHandle),
[dev = device.handle()](VkPipelineLayout* layout) {
vkDestroyPipelineLayout(dev, *layout, nullptr);
}
);
this->pipeline = std::shared_ptr<VkPipeline>(
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);
}

View file

@ -0,0 +1,43 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/sampler.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"
#include <memory>
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<VkSampler>(
new VkSampler(samplerHandle),
[dev = device.handle()](VkSampler* samplerHandle) {
vkDestroySampler(dev, *samplerHandle, nullptr);
}
);
}

View file

@ -0,0 +1,110 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/semaphore.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"
#include <optional>
#include <cstdint>
#include <memory>
#include <stdexcept>
using namespace LSFG::Core;
Semaphore::Semaphore(const Core::Device& device, std::optional<uint32_t> 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<VkSemaphore>(
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<PFN_vkImportSemaphoreFdKHR>(
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<VkSemaphore>(
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;
}

View file

@ -0,0 +1,65 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "core/shadermodule.hpp"
#include "core/device.hpp"
#include "common/exception.hpp"
#include <vector>
#include <cstdint>
#include <utility>
#include <cstddef>
#include <memory>
using namespace LSFG::Core;
ShaderModule::ShaderModule(const Core::Device& device, const std::vector<uint8_t>& code,
const std::vector<std::pair<size_t, VkDescriptorType>>& 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<const uint32_t*>(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<VkDescriptorSetLayoutBinding> 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<uint32_t>(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<uint32_t>(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<VkShaderModule>(
new VkShaderModule(shaderModuleHandle),
[dev = device.handle()](VkShaderModule* shaderModuleHandle) {
vkDestroyShaderModule(dev, *shaderModuleHandle, nullptr);
}
);
this->descriptorSetLayout = std::shared_ptr<VkDescriptorSetLayout>(
new VkDescriptorSetLayout(descriptorSetLayout),
[dev = device.handle()](VkDescriptorSetLayout* layout) {
vkDestroyDescriptorSetLayout(dev, *layout, nullptr);
}
);
}

View file

@ -0,0 +1,72 @@
#include "pool/resourcepool.hpp"
#include "core/buffer.hpp"
#include "core/device.hpp"
#include "core/sampler.hpp"
#include <vulkan/vulkan_core.h>
#include <array>
#include <cstdint>
using namespace LSFG;
using namespace LSFG::Pool;
struct ConstantBuffer {
std::array<uint32_t, 2> inputOffset;
uint32_t firstIter;
uint32_t firstIterS;
uint32_t advancedColorKind;
uint32_t hdrSupport;
float resolutionInvScale;
float timestamp;
float uiThreshold;
std::array<uint32_t, 3> 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<uint64_t>(firstIter) << 32;
hash |= static_cast<uint64_t>(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<uint64_t>(type) << 0;
hash |= static_cast<uint64_t>(compare) << 8;
hash |= static_cast<uint64_t>(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;
}

View file

@ -0,0 +1,48 @@
#include "pool/shaderpool.hpp"
#include "core/shadermodule.hpp"
#include "core/device.hpp"
#include "core/pipeline.hpp"
#include <vulkan/vulkan_core.h>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <vector>
#include <utility>
using namespace LSFG;
using namespace LSFG::Pool;
Core::ShaderModule ShaderPool::getShader(
const Core::Device& device, const std::string& name,
const std::vector<std::pair<size_t, VkDescriptorType>>& 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;
}

View file

@ -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 <vulkan/vulkan_core.h>
#include <vector>
#include <cstdint>
#include <array>
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<int>& 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<int>& 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<Core::Semaphore> internalSemaphores; // signaled when first step is done
std::vector<Core::Semaphore> outSemaphores; // signaled when each pass is done
std::vector<Core::Fence> completionFences; // fence for completion of each pass
Core::CommandBuffer cmdBuffer1;
std::vector<Core::CommandBuffer> cmdBuffers2; // command buffers for second step
bool shouldWait{false};
};
std::array<RenderData, 8> data;
Shaders::Mipmaps mipmaps;
std::array<Shaders::Alpha, 7> alpha;
Shaders::Beta beta;
std::array<Shaders::Gamma, 7> gamma;
std::array<Shaders::Delta, 3> delta;
Shaders::Generate generate;
};
}

View file

@ -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 <array>
#include <cstdint>
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<Core::ShaderModule, 4> shaderModules;
std::array<Core::Pipeline, 4> pipelines;
Core::Sampler sampler;
std::array<Core::DescriptorSet, 3> descriptorSets;
std::array<Core::DescriptorSet, 3> lastDescriptorSet;
Core::Image inImg;
std::array<Core::Image, 2> tempImgs1;
std::array<Core::Image, 2> tempImgs2;
std::array<Core::Image, 4> tempImgs3;
std::array<std::array<Core::Image, 4>, 3> outImgs;
};
}

View file

@ -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 <array>
#include <cstdint>
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<std::array<Core::Image, 4>, 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<Core::ShaderModule, 5> shaderModules;
std::array<Core::Pipeline, 5> pipelines;
std::array<Core::Sampler, 2> samplers;
Core::Buffer buffer;
std::array<Core::DescriptorSet, 3> firstDescriptorSet;
std::array<Core::DescriptorSet, 4> descriptorSets;
std::array<std::array<Core::Image, 4>, 3> inImgs;
std::array<Core::Image, 2> tempImgs1;
std::array<Core::Image, 2> tempImgs2;
std::array<Core::Image, 6> outImgs;
};
}

View file

@ -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 <array>
#include <cstdint>
#include <optional>
#include <vector>
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<std::array<Core::Image, 4>, 3> inImgs1,
Core::Image inImg2,
std::optional<Core::Image> optImg1,
std::optional<Core::Image> optImg2,
std::optional<Core::Image> 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<Core::ShaderModule, 10> shaderModules;
std::array<Core::Pipeline, 10> pipelines;
std::array<Core::Sampler, 3> samplers;
struct DeltaPass {
Core::Buffer buffer;
std::array<Core::DescriptorSet, 3> firstDescriptorSet;
std::array<Core::DescriptorSet, 8> descriptorSets;
std::array<Core::DescriptorSet, 3> sixthDescriptorSet;
};
std::vector<DeltaPass> passes;
std::array<std::array<Core::Image, 4>, 3> inImgs1;
Core::Image inImg2;
std::optional<Core::Image> optImg1, optImg2, optImg3;
std::array<Core::Image, 4> tempImgs1;
std::array<Core::Image, 4> tempImgs2;
Core::Image outImg1, outImg2;
};
}

View file

@ -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 <array>
#include <cstdint>
#include <optional>
#include <vector>
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<std::array<Core::Image, 4>, 3> inImgs1,
Core::Image inImg2, std::optional<Core::Image> 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<Core::ShaderModule, 5> shaderModules;
std::array<Core::Pipeline, 5> pipelines;
std::array<Core::Sampler, 3> samplers;
struct GammaPass {
Core::Buffer buffer;
std::array<Core::DescriptorSet, 3> firstDescriptorSet;
std::array<Core::DescriptorSet, 4> descriptorSets;
};
std::vector<GammaPass> passes;
std::array<std::array<Core::Image, 4>, 3> inImgs1;
Core::Image inImg2;
std::optional<Core::Image> optImg;
std::array<Core::Image, 4> tempImgs1;
std::array<Core::Image, 4> tempImgs2;
Core::Image outImg;
};
}

View file

@ -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 <vulkan/vulkan_core.h>
#include <array>
#include <vector>
#include <cstdint>
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<int>& 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<Core::Sampler, 2> samplers;
struct GeneratePass {
Core::Buffer buffer;
std::array<Core::DescriptorSet, 2> descriptorSet;
};
std::vector<GeneratePass> passes;
Core::Image inImg1, inImg2;
Core::Image inImg3, inImg4, inImg5;
std::vector<Core::Image> outImgs;
};
}

View file

@ -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 <array>
#include <cstdint>
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<Core::DescriptorSet, 2> descriptorSets;
Core::Image inImg_0, inImg_1;
std::array<Core::Image, 7> outImgs;
};
}

View file

@ -0,0 +1,122 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1/context.hpp"
#include "common/utils.hpp"
#include "common/exception.hpp"
#include <vector>
#include <cstddef>
#include <algorithm>
#include <optional>
#include <cstdint>
using namespace LSFG_3_1;
Context::Context(Vulkan& vk,
int in0, int in1, const std::vector<int>& 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<size_t>(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<int>& 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<Core::Semaphore> 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<Core::Semaphore> signals = { outSemaphore };
if (inSem < 0) signals.clear();
buf2.submit(vk.device.getComputeQueue(), completionFence,
{ internalSemaphore }, std::nullopt,
signals, std::nullopt);
}
this->frameIdx++;
}

View file

@ -0,0 +1,97 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#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 <cstdint>
#include <optional>
#include <cstdlib>
#include <ctime>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
using namespace LSFG;
using namespace LSFG_3_1;
namespace {
std::optional<Core::Instance> instance;
std::optional<Vulkan> device;
std::unordered_map<int32_t, Context> contexts;
}
void LSFG_3_1::initialize(uint64_t deviceUUID,
bool isHdr, float flowScale, uint64_t generationCount,
const std::function<std::vector<uint8_t>(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<int32_t, Context>();
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<uint32_t>(std::time(nullptr)));
}
int32_t LSFG_3_1::createContext(
int in0, int in1, const std::vector<int>& 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<int>& 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();
}

View file

@ -0,0 +1,140 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1/shaders/alpha.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"
#include <utility>
#include <cstddef>
#include <cstdint>
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);
}

View file

@ -0,0 +1,162 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1/shaders/beta.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"
#include <array>
#include <utility>
#include <cstddef>
#include <cstdint>
using namespace LSFG_3_1::Shaders;
Beta::Beta(Vulkan& vk, std::array<std::array<Core::Image, 4>, 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);
}

View file

@ -0,0 +1,341 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1/shaders/delta.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"
#include <array>
#include <optional>
#include <utility>
#include <cstddef>
#include <cstdint>
using namespace LSFG_3_1::Shaders;
Delta::Delta(Vulkan& vk, std::array<std::array<Core::Image, 4>, 3> inImgs1,
Core::Image inImg2,
std::optional<Core::Image> optImg1,
std::optional<Core::Image> optImg2,
std::optional<Core::Image> 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<float>(pass_idx + 1) / static_cast<float>(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);
}

View file

@ -0,0 +1,193 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1/shaders/gamma.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"
#include <array>
#include <optional>
#include <utility>
#include <cstddef>
#include <cstdint>
using namespace LSFG_3_1::Shaders;
Gamma::Gamma(Vulkan& vk, std::array<std::array<Core::Image, 4>, 3> inImgs1,
Core::Image inImg2,
std::optional<Core::Image> 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<float>(pass_idx + 1) / static_cast<float>(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);
}

View file

@ -0,0 +1,83 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1/shaders/generate.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"
#include <vector>
#include <utility>
#include <cstddef>
#include <cstdint>
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<int>& 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<float>(i + 1) / static_cast<float>(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);
}

View file

@ -0,0 +1,66 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1/shaders/mipmaps.hpp"
#include "common/utils.hpp"
#include "core/image.hpp"
#include "core/commandbuffer.hpp"
#include <utility>
#include <cstddef>
#include <cstdint>
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<uint32_t>(
static_cast<float>(this->inImg_0.getExtent().width) / vk.flowScale),
.height = static_cast<uint32_t>(
static_cast<float>(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);
}

View file

@ -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 <vulkan/vulkan_core.h>
#include <vector>
#include <cstdint>
#include <array>
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<int>& 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<int>& 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<Core::Semaphore> internalSemaphores; // signaled when first step is done
std::vector<Core::Semaphore> outSemaphores; // signaled when each pass is done
std::vector<Core::Fence> completionFences; // fence for completion of each pass
Core::CommandBuffer cmdBuffer1;
std::vector<Core::CommandBuffer> cmdBuffers2; // command buffers for second step
bool shouldWait{false};
};
std::array<RenderData, 8> data;
Shaders::Mipmaps mipmaps;
std::array<Shaders::Alpha, 7> alpha;
Shaders::Beta beta;
std::array<Shaders::Gamma, 7> gamma;
std::array<Shaders::Delta, 3> delta;
Shaders::Generate generate;
};
}

View file

@ -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 <array>
#include <cstdint>
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<Core::ShaderModule, 4> shaderModules;
std::array<Core::Pipeline, 4> pipelines;
Core::Sampler sampler;
std::array<Core::DescriptorSet, 3> descriptorSets;
std::array<Core::DescriptorSet, 3> lastDescriptorSet;
Core::Image inImg;
Core::Image tempImg1;
Core::Image tempImg2;
std::array<Core::Image, 2> tempImgs3;
std::array<std::array<Core::Image, 2>, 3> outImgs;
};
}

View file

@ -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 <array>
#include <cstdint>
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<std::array<Core::Image, 2>, 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<Core::ShaderModule, 5> shaderModules;
std::array<Core::Pipeline, 5> pipelines;
std::array<Core::Sampler, 2> samplers;
Core::Buffer buffer;
std::array<Core::DescriptorSet, 3> firstDescriptorSet;
std::array<Core::DescriptorSet, 4> descriptorSets;
std::array<std::array<Core::Image, 2>, 3> inImgs;
std::array<Core::Image, 2> tempImgs1;
std::array<Core::Image, 2> tempImgs2;
std::array<Core::Image, 6> outImgs;
};
}

View file

@ -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 <array>
#include <cstdint>
#include <optional>
#include <vector>
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<std::array<Core::Image, 2>, 3> inImgs1,
Core::Image inImg2,
std::optional<Core::Image> optImg1,
std::optional<Core::Image> 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<Core::ShaderModule, 10> shaderModules;
std::array<Core::Pipeline, 10> pipelines;
std::array<Core::Sampler, 3> samplers;
struct DeltaPass {
Core::Buffer buffer;
std::array<Core::DescriptorSet, 3> firstDescriptorSet;
std::array<Core::DescriptorSet, 8> descriptorSets;
std::array<Core::DescriptorSet, 3> sixthDescriptorSet;
};
std::vector<DeltaPass> passes;
std::array<std::array<Core::Image, 2>, 3> inImgs1;
Core::Image inImg2;
std::optional<Core::Image> optImg1, optImg2;
std::array<Core::Image, 3> tempImgs1;
std::array<Core::Image, 2> tempImgs2;
Core::Image outImg1, outImg2;
};
}

View file

@ -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 <array>
#include <cstdint>
#include <optional>
#include <vector>
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<std::array<Core::Image, 2>, 3> inImgs1,
Core::Image inImg2, std::optional<Core::Image> 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<Core::ShaderModule, 5> shaderModules;
std::array<Core::Pipeline, 5> pipelines;
std::array<Core::Sampler, 3> samplers;
struct GammaPass {
Core::Buffer buffer;
std::array<Core::DescriptorSet, 3> firstDescriptorSet;
std::array<Core::DescriptorSet, 4> descriptorSets;
};
std::vector<GammaPass> passes;
std::array<std::array<Core::Image, 2>, 3> inImgs1;
Core::Image inImg2;
std::optional<Core::Image> optImg;
std::array<Core::Image, 3> tempImgs1;
std::array<Core::Image, 2> tempImgs2;
Core::Image outImg;
};
}

View file

@ -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 <vulkan/vulkan_core.h>
#include <array>
#include <vector>
#include <cstdint>
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<int>& 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<Core::Sampler, 2> samplers;
struct GeneratePass {
Core::Buffer buffer;
std::array<Core::DescriptorSet, 2> descriptorSet;
};
std::vector<GeneratePass> passes;
Core::Image inImg1, inImg2;
Core::Image inImg3, inImg4, inImg5;
std::vector<Core::Image> outImgs;
};
}

View file

@ -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 <array>
#include <cstdint>
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<Core::DescriptorSet, 2> descriptorSets;
Core::Image inImg_0, inImg_1;
std::array<Core::Image, 7> outImgs;
};
}

View file

@ -0,0 +1,122 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1p/context.hpp"
#include "common/utils.hpp"
#include "common/exception.hpp"
#include <vector>
#include <cstddef>
#include <algorithm>
#include <optional>
#include <cstdint>
using namespace LSFG;
using namespace LSFG_3_1P;
Context::Context(Vulkan& vk,
int in0, int in1, const std::vector<int>& 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<size_t>(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<int>& 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<Core::Semaphore> 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<Core::Semaphore> signals = { outSemaphore };
if (inSem < 0) signals.clear();
buf2.submit(vk.device.getComputeQueue(), completionFence,
{ internalSemaphore }, std::nullopt,
signals, std::nullopt);
}
this->frameIdx++;
}

View file

@ -0,0 +1,97 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#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 <cstdint>
#include <optional>
#include <cstdlib>
#include <ctime>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
using namespace LSFG;
using namespace LSFG_3_1P;
namespace {
std::optional<Core::Instance> instance;
std::optional<Vulkan> device;
std::unordered_map<int32_t, Context> contexts;
}
void LSFG_3_1P::initialize(uint64_t deviceUUID,
bool isHdr, float flowScale, uint64_t generationCount,
const std::function<std::vector<uint8_t>(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<int32_t, Context>();
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<uint32_t>(std::time(nullptr)));
}
int32_t LSFG_3_1P::createContext(
int in0, int in1, const std::vector<int>& 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<int>& 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();
}

View file

@ -0,0 +1,138 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1p/shaders/alpha.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"
#include <utility>
#include <cstddef>
#include <cstdint>
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);
}

View file

@ -0,0 +1,162 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1p/shaders/beta.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"
#include <array>
#include <utility>
#include <cstddef>
#include <cstdint>
using namespace LSFG_3_1P::Shaders;
Beta::Beta(Vulkan& vk, std::array<std::array<Core::Image, 2>, 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);
}

View file

@ -0,0 +1,323 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1p/shaders/delta.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"
#include <array>
#include <optional>
#include <utility>
#include <cstddef>
#include <cstdint>
using namespace LSFG_3_1P::Shaders;
Delta::Delta(Vulkan& vk, std::array<std::array<Core::Image, 2>, 3> inImgs1,
Core::Image inImg2,
std::optional<Core::Image> optImg1,
std::optional<Core::Image> 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<float>(pass_idx + 1) / static_cast<float>(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);
}

View file

@ -0,0 +1,189 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1p/shaders/gamma.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"
#include <array>
#include <optional>
#include <utility>
#include <cstddef>
#include <cstdint>
using namespace LSFG_3_1P::Shaders;
Gamma::Gamma(Vulkan& vk, std::array<std::array<Core::Image, 2>, 3> inImgs1,
Core::Image inImg2,
std::optional<Core::Image> 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<float>(pass_idx + 1) / static_cast<float>(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);
}

View file

@ -0,0 +1,83 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1p/shaders/generate.hpp"
#include "common/utils.hpp"
#include "core/commandbuffer.hpp"
#include "core/image.hpp"
#include <vector>
#include <utility>
#include <cstddef>
#include <cstdint>
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<int>& 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<float>(i + 1) / static_cast<float>(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);
}

View file

@ -0,0 +1,66 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "v3_1p/shaders/mipmaps.hpp"
#include "common/utils.hpp"
#include "core/image.hpp"
#include "core/commandbuffer.hpp"
#include <utility>
#include <cstddef>
#include <cstdint>
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<uint32_t>(
static_cast<float>(this->inImg_0.getExtent().width) / vk.flowScale),
.height = static_cast<uint32_t>(
static_cast<float>(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);
}

60
include/config/config.hpp Normal file
View file

@ -0,0 +1,60 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <filesystem>
#include <chrono>
#include <cstddef>
#include <string>
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<std::chrono::file_clock> 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<std::string, std::string>& name);
}

View file

@ -0,0 +1,36 @@
#pragma once
#include <string>
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
)";

80
include/context.hpp Normal file
View file

@ -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 <vulkan/vulkan_core.h>
#include <array>
#include <cstdint>
#include <memory>
#include <vector>
///
/// 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<VkImage>& 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<VkSemaphore>& 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<VkImage> swapchainImages;
VkExtent2D extent;
std::shared_ptr<int32_t> lsfgCtxId; // lsfg context id
Mini::Image frame_0, frame_1; // frames shared with lsfg. write to frame_0 when fc % 2 == 0
std::vector<Mini::Image> 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<Mini::Semaphore, 2> preCopySemaphores; // signal when preCopyBuf is done
std::vector<Mini::Semaphore> renderSemaphores; // signal when lsfg is done with frame n
std::vector<Mini::Semaphore> acquireSemaphores; // signal for swapchain image n
std::vector<Mini::CommandBuffer> postCopyBufs; // copy from out_n to swapchain image
std::vector<Mini::Semaphore> postCopySemaphores; // signal when postCopyBuf is done
std::vector<Mini::Semaphore> prevPostCopySemaphores; // signal for previous postCopyBuf
}; // data for a single render pass
std::array<RenderPassInfo, 8> passInfos; // allocate 8 because why not
};

View file

@ -0,0 +1,26 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
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<uint8_t> getShader(const std::string& name);
}

16
include/extract/trans.hpp Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
#include <vector>
namespace Extract {
///
/// Translate DXBC bytecode to SPIR-V bytecode.
///
/// @param bytecode The DXBC bytecode to translate.
/// @return The translated SPIR-V bytecode.
///
std::vector<uint8_t> translateShader(std::vector<uint8_t> bytecode);
}

22
include/hooks.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <unordered_map>
#include <utility>
#include <string>
namespace Hooks {
/// Vulkan device information structure.
struct DeviceInfo {
VkDevice device;
VkPhysicalDevice physicalDevice;
std::pair<uint32_t, VkQueue> queue; // graphics family
};
/// Map of hooked Vulkan functions.
extern std::unordered_map<std::string, PFN_vkVoidFunction> hooks;
}

224
include/layer.hpp Normal file
View file

@ -0,0 +1,224 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <cstdint>
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);

View file

@ -0,0 +1,91 @@
#pragma once
#include "mini/commandpool.hpp"
#include <vulkan/vulkan_core.h>
#include <vector>
#include <memory>
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<VkSemaphore>& waitSemaphores = {},
const std::vector<VkSemaphore>& 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<CommandBufferState> state;
std::shared_ptr<VkCommandBuffer> commandBuffer;
};
}

View file

@ -0,0 +1,42 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <memory>
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<VkCommandPool> commandPool;
};
}

60
include/mini/image.hpp Normal file
View file

@ -0,0 +1,60 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <memory>
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<VkImage> image;
std::shared_ptr<VkDeviceMemory> memory;
VkExtent2D extent{};
VkFormat format{};
VkImageAspectFlags aspectFlags{};
};
}

View file

@ -0,0 +1,50 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <memory>
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<VkSemaphore> semaphore;
};
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
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);
}

102
include/utils/utils.hpp Normal file
View file

@ -0,0 +1,102 @@
#pragma once
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <cstddef>
#include <utility>
#include <string>
#include <vector>
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<uint32_t, VkQueue> 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<const char*> addExtensions(const char* const* extensions, size_t count,
const std::vector<const char*>& 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<std::string, std::string> getProcessName();
///
/// Get the configuration file path.
///
/// @return The path to the configuration file.
///
std::string getConfigFile();
}

Some files were not shown because too many files have changed in this diff Show more