From fe5a8520e55b90678bce1d33b5427a6653fe2891 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 05:05:29 +0200 Subject: [PATCH 001/253] base vulkan classes --- include/device.hpp | 60 +++++++++++++++++++++++++++++++ include/instance.hpp | 44 +++++++++++++++++++++++ src/device.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++++ src/instance.cpp | 46 ++++++++++++++++++++++++ src/main.cpp | 12 +++++++ 5 files changed, 247 insertions(+) create mode 100644 include/device.hpp create mode 100644 include/instance.hpp create mode 100644 src/device.cpp create mode 100644 src/instance.cpp create mode 100644 src/main.cpp diff --git a/include/device.hpp b/include/device.hpp new file mode 100644 index 0000000..3e70c65 --- /dev/null +++ b/include/device.hpp @@ -0,0 +1,60 @@ +#ifndef DEVICE_HPP +#define DEVICE_HPP + +#include "instance.hpp" +#include + +#include +#include + +namespace Vulkan { + + /// + /// 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 + /// + /// @throws std::invalid_argument if the instance is invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + Device(const Vulkan::Instance& instance); + + /// 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; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->device); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + // Trivially copyable, moveable and destructible + Device(const Device&) noexcept = default; + Device& operator=(const Device&) noexcept = default; + Device(Device&&) noexcept = default; + Device& operator=(Device&&) noexcept = default; + ~Device() = default; + private: + std::shared_ptr device; + VkPhysicalDevice physicalDevice{}; + + uint32_t computeFamilyIdx{0}; + + VkQueue computeQueue{}; + }; + +} + +#endif // DEVICE_HPP diff --git a/include/instance.hpp b/include/instance.hpp new file mode 100644 index 0000000..affd0c6 --- /dev/null +++ b/include/instance.hpp @@ -0,0 +1,44 @@ +#ifndef INSTANCE_HPP +#define INSTANCE_HPP + +#include + +#include + +namespace Vulkan { + + /// + /// C++ wrapper class for a Vulkan instance. + /// + /// This class manages the lifetime of a Vulkan instance. + /// + class Instance { + public: + /// + /// Create the instance. + /// + /// @throws ls::vulkan_error if object creation fails. + /// + Instance(); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return this->instance ? *this->instance : VK_NULL_HANDLE; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return this->handle() != VK_NULL_HANDLE; } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + Instance(const Instance&) noexcept = default; + Instance& operator=(const Instance&) noexcept = default; + Instance(Instance&&) noexcept = default; + Instance& operator=(Instance&&) noexcept = default; + ~Instance() = default; + private: + std::shared_ptr instance; + }; + +} + +#endif // INSTANCE_HPP diff --git a/src/device.cpp b/src/device.cpp new file mode 100644 index 0000000..e854a93 --- /dev/null +++ b/src/device.cpp @@ -0,0 +1,85 @@ +#include "device.hpp" +#include "utils/exceptions.hpp" + +#include +#include + +using namespace Vulkan; + +Device::Device(const Instance& instance) { + if (!instance) + throw std::invalid_argument("Invalid Vulkan instance"); + + // get all physical devices + uint32_t deviceCount{}; + auto res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, nullptr); + if (res != VK_SUCCESS || deviceCount == 0) + throw ls::vulkan_error(res, "Failed to enumerate physical devices"); + + std::vector devices(deviceCount); + res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, devices.data()); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Failed to get physical devices"); + + // find first discrete GPU + std::optional physicalDevice; + for (const auto& device : devices) { + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(device, &properties); + + if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + physicalDevice = device; + break; + } + } + if (!physicalDevice) + throw ls::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No discrete GPU found"); + + // find queue family indices + uint32_t familyCount{}; + vkGetPhysicalDeviceQueueFamilyProperties(*physicalDevice, &familyCount, nullptr); + + std::vector queueFamilies(familyCount); + vkGetPhysicalDeviceQueueFamilyProperties(*physicalDevice, &familyCount, queueFamilies.data()); + + std::optional computeFamilyIdx; + for (uint32_t i = 0; i < familyCount; ++i) { + if (queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT) + computeFamilyIdx = i; + } + if (!computeFamilyIdx) + throw ls::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No compute queue family found"); + + // create logical device + const float queuePriority{1.0F}; // highest priority + 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, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &computeQueueDesc + }; + VkDevice deviceHandle{}; + res = vkCreateDevice(*physicalDevice, &deviceCreateInfo, nullptr, &deviceHandle); + if (res != VK_SUCCESS | deviceHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Failed to create logical device"); + + // get compute queue + VkQueue queueHandle{}; + vkGetDeviceQueue(deviceHandle, *computeFamilyIdx, 0, &queueHandle); + + // store in shared ptr + this->computeQueue = queueHandle; + this->computeFamilyIdx = *computeFamilyIdx; + this->physicalDevice = *physicalDevice; + this->device = std::shared_ptr( + new VkDevice(deviceHandle), + [](VkDevice* device) { + vkDestroyDevice(*device, nullptr); + } + ); +} diff --git a/src/instance.cpp b/src/instance.cpp new file mode 100644 index 0000000..96a6d68 --- /dev/null +++ b/src/instance.cpp @@ -0,0 +1,46 @@ +#include "instance.hpp" +#include "utils/exceptions.hpp" + +#include + +using namespace Vulkan; + +const std::vector requiredExtensions = { + +}; + +const std::vector requiredLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +Instance::Instance() { + // 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_4 + }; + const VkInstanceCreateInfo createInfo{ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data() + }; + VkInstance instanceHandle{}; + auto res = vkCreateInstance(&createInfo, nullptr, &instanceHandle); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Failed to create Vulkan instance"); + + // store in shared ptr + this->instance = std::shared_ptr( + new VkInstance(instanceHandle), + [](VkInstance* instance) { + vkDestroyInstance(*instance, nullptr); + } + ); +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..48c0f86 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,12 @@ +#include "device.hpp" +#include "instance.hpp" + +#include + +int main() { + const Vulkan::Instance instance; + const Vulkan::Device device(instance); + + std::cerr << "Application finished" << '\n'; + return 0; +} From 40d7d032a3f4062d5982675a3615ad513e69af54 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 05:05:47 +0200 Subject: [PATCH 002/253] initial set of core vulkan objects --- include/core/commandbuffer.hpp | 105 ++++++++++++++++++++++++++++++ include/core/commandpool.hpp | 56 ++++++++++++++++ include/core/fence.hpp | 69 ++++++++++++++++++++ include/core/semaphore.hpp | 75 +++++++++++++++++++++ include/core/shadermodule.hpp | 52 +++++++++++++++ include/utils/exceptions.hpp | 37 +++++++++++ src/core/commandbuffer.cpp | 115 +++++++++++++++++++++++++++++++++ src/core/commandpool.cpp | 34 ++++++++++ src/core/fence.cpp | 50 ++++++++++++++ src/core/semaphore.cpp | 66 +++++++++++++++++++ src/core/shadermodule.cpp | 46 +++++++++++++ src/utils/exceptions.cpp | 10 +++ 12 files changed, 715 insertions(+) create mode 100644 include/core/commandbuffer.hpp create mode 100644 include/core/commandpool.hpp create mode 100644 include/core/fence.hpp create mode 100644 include/core/semaphore.hpp create mode 100644 include/core/shadermodule.hpp create mode 100644 include/utils/exceptions.hpp create mode 100644 src/core/commandbuffer.cpp create mode 100644 src/core/commandpool.cpp create mode 100644 src/core/fence.cpp create mode 100644 src/core/semaphore.cpp create mode 100644 src/core/shadermodule.cpp create mode 100644 src/utils/exceptions.cpp diff --git a/include/core/commandbuffer.hpp b/include/core/commandbuffer.hpp new file mode 100644 index 0000000..b60c437 --- /dev/null +++ b/include/core/commandbuffer.hpp @@ -0,0 +1,105 @@ +#ifndef COMMANDBUFFER_HPP +#define COMMANDBUFFER_HPP + +#include "core/commandpool.hpp" +#include "core/semaphore.hpp" +#include "device.hpp" +#include + +#include +#include +#include + +namespace Vulkan::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: + /// + /// Create the command buffer. + /// + /// @param device Vulkan device + /// @param pool Vulkan command pool + /// + /// @throws std::invalid_argument if the device or pool are invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + CommandBuffer(const 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 ls::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 ls::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 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::invalid_argument if the queue is null. + /// @throws std::logic_error if the command buffer is not in Full state. + /// @throws ls::vulkan_error if submission fails. + /// + void submit(VkQueue queue, // TODO: fence + const std::vector& waitSemaphores = {}, + std::optional> waitSemaphoreValues = std::nullopt, + const std::vector& signalSemaphores = {}, + std::optional> signalSemaphoreValues = std::nullopt); + + /// Get the state of the command buffer. + [[nodiscard]] CommandBufferState getState() const { return *this->state; } + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { *this->commandBuffer; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->commandBuffer); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + CommandBuffer(const CommandBuffer&) noexcept = default; + CommandBuffer& operator=(const CommandBuffer&) noexcept = default; + CommandBuffer(CommandBuffer&&) noexcept = default; + CommandBuffer& operator=(CommandBuffer&&) noexcept = default; + ~CommandBuffer() = default; + private: + std::shared_ptr state; + std::shared_ptr commandBuffer; + }; + +} + +#endif // COMMANDBUFFER_HPP diff --git a/include/core/commandpool.hpp b/include/core/commandpool.hpp new file mode 100644 index 0000000..b2d0909 --- /dev/null +++ b/include/core/commandpool.hpp @@ -0,0 +1,56 @@ +#ifndef COMMANDPOOL_HPP +#define COMMANDPOOL_HPP + +#include "device.hpp" + +#include + +#include + +namespace Vulkan::Core { + + /// Enumeration for different types of command pools. + enum class CommandPoolType { + /// Used for compute-type command buffers. + Compute + }; + + /// + /// C++ wrapper class for a Vulkan command pool. + /// + /// This class manages the lifetime of a Vulkan command pool. + /// + class CommandPool { + public: + /// + /// Create the command pool. + /// + /// @param device Vulkan device + /// @param type Type of command pool to create. + /// + /// @throws std::invalid_argument if the device is invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + CommandPool(const Device& device, CommandPoolType type); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->commandPool; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->commandPool); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + CommandPool(const CommandPool&) noexcept = default; + CommandPool& operator=(const CommandPool&) noexcept = default; + CommandPool(CommandPool&&) noexcept = default; + CommandPool& operator=(CommandPool&&) noexcept = default; + ~CommandPool() = default; + private: + std::shared_ptr commandPool; + }; + +} + +#endif // COMMANDPOOL_HPP diff --git a/include/core/fence.hpp b/include/core/fence.hpp new file mode 100644 index 0000000..6b9135d --- /dev/null +++ b/include/core/fence.hpp @@ -0,0 +1,69 @@ +#ifndef FENCE_HPP +#define FENCE_HPP + +#include "device.hpp" + +#include + +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan fence. + /// + /// This class manages the lifetime of a Vulkan fence. + /// + class Fence { + public: + /// + /// Create the fence. + /// + /// @param device Vulkan device + /// + /// @throws std::invalid_argument if the device is null. + /// @throws ls::vulkan_error if object creation fails. + /// + Fence(const Device& device); + + /// + /// Reset the fence to an unsignaled state. + /// + /// @throws std::logic_error if the fence is not valid. + /// @throws ls::vulkan_error if resetting fails. + /// + void reset() const; + + /// + /// Wait for the fence + /// + /// @param timeout The timeout in nanoseconds, or UINT64_MAX for no timeout. + /// @returns true if the fence signaled, false if it timed out. + /// + /// @throws std::logic_error if the fence is not valid. + /// @throws ls::vulkan_error if waiting fails. + /// + bool wait(uint64_t timeout = UINT64_MAX); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->fence; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->fence); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + // Trivially copyable, moveable and destructible + Fence(const Fence&) noexcept = default; + Fence& operator=(const Fence&) noexcept = default; + Fence(Fence&&) noexcept = default; + Fence& operator=(Fence&&) noexcept = default; + ~Fence() = default; + private: + std::shared_ptr fence; + VkDevice device{}; + }; + +} + +#endif // FENCE_HPP diff --git a/include/core/semaphore.hpp b/include/core/semaphore.hpp new file mode 100644 index 0000000..e20c8c6 --- /dev/null +++ b/include/core/semaphore.hpp @@ -0,0 +1,75 @@ +#ifndef SEMAPHORE_HPP +#define SEMAPHORE_HPP + +#include "device.hpp" + +#include + +#include +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan semaphore. + /// + /// This class manages the lifetime of a Vulkan semaphore. + /// + class Semaphore { + public: + /// + /// Create the semaphore. + /// + /// @param device Vulkan device + /// @param initial Optional initial value for creating a timeline semaphore. + /// + /// @throws std::invalid_argument if the device is null. + /// @throws ls::vulkan_error if object creation fails. + /// + Semaphore(const Device& device, std::optional initial = std::nullopt); + + /// + /// Signal the semaphore to a specific value. + /// + /// @param value The value to signal the semaphore to. + /// + /// @throws std::logic_error if the semaphore is not a timeline semaphore. + /// @throws ls::vulkan_error if signaling fails. + /// + void signal(uint64_t value) const; + + /// + /// Wait for the semaphore to reach a specific value. + /// + /// @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 ls::vulkan_error if waiting fails. + /// + bool wait(uint64_t value, uint64_t timeout = UINT64_MAX); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->semaphore; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->semaphore); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + // Trivially copyable, moveable and destructible + Semaphore(const Semaphore&) noexcept = default; + Semaphore& operator=(const Semaphore&) noexcept = default; + Semaphore(Semaphore&&) noexcept = default; + Semaphore& operator=(Semaphore&&) noexcept = default; + ~Semaphore() = default; + private: + std::shared_ptr semaphore; + VkDevice device{}; + bool isTimeline{}; + }; + +} + +#endif // SEMAPHORE_HPP diff --git a/include/core/shadermodule.hpp b/include/core/shadermodule.hpp new file mode 100644 index 0000000..18b40dc --- /dev/null +++ b/include/core/shadermodule.hpp @@ -0,0 +1,52 @@ +#ifndef SHADERMODULE_HPP +#define SHADERMODULE_HPP + +#include "device.hpp" + +#include +#include + +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan shader module. + /// + /// This class manages the lifetime of a Vulkan shader module. + /// + class ShaderModule { + public: + /// + /// Create the shader module. + /// + /// @param device Vulkan device + /// @param path Path to the shader file. + /// + /// @throws std::invalid_argument if the device is invalid. + /// @throws std::system_error if the shader file cannot be opened or read. + /// @throws ls::vulkan_error if object creation fails. + /// + ShaderModule(const Device& device, const std::string& path); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->shaderModule; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->shaderModule); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + ShaderModule(const ShaderModule&) noexcept = default; + ShaderModule& operator=(const ShaderModule&) noexcept = default; + ShaderModule(ShaderModule&&) noexcept = default; + ShaderModule& operator=(ShaderModule&&) noexcept = default; + ~ShaderModule() = default; + private: + std::shared_ptr shaderModule; + }; + +} + +#endif // SHADERMODULE_HPP diff --git a/include/utils/exceptions.hpp b/include/utils/exceptions.hpp new file mode 100644 index 0000000..9b47718 --- /dev/null +++ b/include/utils/exceptions.hpp @@ -0,0 +1,37 @@ +#ifndef EXCEPTIONS_HPP +#define EXCEPTIONS_HPP + +#include + +#include +#include + +namespace ls { + + /// 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; + }; + +} + +#endif // EXCEPTIONS_HPP diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp new file mode 100644 index 0000000..15b4b8d --- /dev/null +++ b/src/core/commandbuffer.cpp @@ -0,0 +1,115 @@ +#include "core/commandbuffer.hpp" +#include "core/semaphore.hpp" +#include "utils/exceptions.hpp" + +using namespace Vulkan::Core; + +CommandBuffer::CommandBuffer(const Device& device, const CommandPool& pool) { + if (!device || !pool) + throw std::invalid_argument("Invalid Vulkan device or command 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 || commandBuffer == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Unable to allocate command buffer"); + + // store command buffer in shared ptr + this->state = std::make_shared(CommandBufferState::Empty); + this->commandBuffer = std::shared_ptr( + new VkCommandBuffer(commandBufferHandle), + [dev = device.handle(), pool = pool.handle()](VkCommandBuffer* cmdBuffer) { + vkFreeCommandBuffers(dev, pool, 1, cmdBuffer); + } + ); +} + +void CommandBuffer::begin() { + if (*this->state != CommandBufferState::Empty) + throw std::logic_error("Command buffer is not in Empty state"); + + const VkCommandBufferBeginInfo beginInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; + auto res = vkBeginCommandBuffer(*this->commandBuffer, &beginInfo); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Unable to begin command buffer"); + + *this->state = CommandBufferState::Recording; +} + +void CommandBuffer::end() { + if (*this->state != CommandBufferState::Recording) + throw std::logic_error("Command buffer is not in Recording state"); + + auto res = vkEndCommandBuffer(*this->commandBuffer); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Unable to end command buffer"); + + *this->state = CommandBufferState::Full; +} + +void CommandBuffer::submit(VkQueue queue, + const std::vector& waitSemaphores, + std::optional> waitSemaphoreValues, + const std::vector& signalSemaphores, + std::optional> signalSemaphoreValues) { + if (!queue) + throw std::invalid_argument("Invalid Vulkan queue"); + if (*this->state != CommandBufferState::Full) + throw std::logic_error("Command buffer is not in Full state"); + + const std::vector waitStages(waitSemaphores.size(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + VkTimelineSemaphoreSubmitInfo timelineInfo = { + .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, + }; + if (waitSemaphoreValues.has_value()) { + timelineInfo.waitSemaphoreValueCount = + static_cast(waitSemaphoreValues->size()); + timelineInfo.pWaitSemaphoreValues = waitSemaphoreValues->data(); + } + if (signalSemaphoreValues.has_value()) { + timelineInfo.signalSemaphoreValueCount = + static_cast(signalSemaphoreValues->size()); + timelineInfo.pSignalSemaphoreValues = signalSemaphoreValues->data(); + } + + std::vector waitSemaphoresHandles; + for (const auto& semaphore : waitSemaphores) { + if (!semaphore) + throw std::invalid_argument("Invalid Vulkan semaphore in waitSemaphores"); + waitSemaphoresHandles.push_back(semaphore.handle()); + } + std::vector signalSemaphoresHandles; + for (const auto& semaphore : signalSemaphores) { + if (!semaphore) + throw std::invalid_argument("Invalid Vulkan semaphore in signalSemaphores"); + signalSemaphoresHandles.push_back(semaphore.handle()); + } + + const VkSubmitInfo submitInfo = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = (waitSemaphoreValues.has_value() || signalSemaphoreValues.has_value()) + ? &timelineInfo : nullptr, + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphoresHandles.data(), + .pWaitDstStageMask = waitStages.data(), + .commandBufferCount = 1, + .pCommandBuffers = &(*this->commandBuffer), + .signalSemaphoreCount = static_cast(signalSemaphores.size()), + .pSignalSemaphores = signalSemaphoresHandles.data() + }; + auto res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Unable to submit command buffer"); + + *this->state = CommandBufferState::Submitted; +} diff --git a/src/core/commandpool.cpp b/src/core/commandpool.cpp new file mode 100644 index 0000000..65b7ddf --- /dev/null +++ b/src/core/commandpool.cpp @@ -0,0 +1,34 @@ +#include "core/commandpool.hpp" +#include "utils/exceptions.hpp" + +using namespace Vulkan::Core; + +CommandPool::CommandPool(const Device& device, CommandPoolType type) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + uint32_t familyIdx{}; + switch (type) { + case CommandPoolType::Compute: + familyIdx = device.getComputeFamilyIdx(); + break; + } + + // create command pool + const VkCommandPoolCreateInfo desc = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .queueFamilyIndex = familyIdx + }; + VkCommandPool commandPoolHandle{}; + auto res = vkCreateCommandPool(device.handle(), &desc, nullptr, &commandPoolHandle); + if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Unable to create command pool"); + + // store the command pool in a shared pointer + this->commandPool = std::shared_ptr( + new VkCommandPool(commandPoolHandle), + [dev = device.handle()](VkCommandPool* commandPoolHandle) { + vkDestroyCommandPool(dev, *commandPoolHandle, nullptr); + } + ); +} diff --git a/src/core/fence.cpp b/src/core/fence.cpp new file mode 100644 index 0000000..b4d6610 --- /dev/null +++ b/src/core/fence.cpp @@ -0,0 +1,50 @@ +#include "core/fence.hpp" +#include "utils/exceptions.hpp" +#include + +using namespace Vulkan::Core; + +Fence::Fence(const Device& device) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // create fence + const VkFenceCreateInfo desc = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO + }; + VkFence fenceHandle{}; + auto res = vkCreateFence(device.handle(), &desc, nullptr, &fenceHandle); + if (res != VK_SUCCESS || fenceHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Unable to create fence"); + + // store fence in shared ptr + this->device = device.handle(); + this->fence = std::shared_ptr( + new VkFence(fenceHandle), + [dev = device.handle()](VkFence* fenceHandle) { + vkDestroyFence(dev, *fenceHandle, nullptr); + } + ); +} + +void Fence::reset() const { + if (!this->isValid()) + throw std::runtime_error("Invalid fence"); + + VkFence fenceHandle = this->handle(); + auto res = vkResetFences(this->device, 1, &fenceHandle); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Unable to reset fence"); +} + +bool Fence::wait(uint64_t timeout) { + if (!this->isValid()) + throw std::runtime_error("Invalid fence"); + + VkFence fenceHandle = this->handle(); + auto res = vkWaitForFences(this->device, 1, &fenceHandle, VK_TRUE, timeout); + if (res != VK_SUCCESS && res != VK_TIMEOUT) + throw ls::vulkan_error(res, "Unable to wait for fence"); + + return res == VK_SUCCESS; +} diff --git a/src/core/semaphore.cpp b/src/core/semaphore.cpp new file mode 100644 index 0000000..cc484a9 --- /dev/null +++ b/src/core/semaphore.cpp @@ -0,0 +1,66 @@ +#include "core/semaphore.hpp" +#include "utils/exceptions.hpp" + +using namespace Vulkan::Core; + +Semaphore::Semaphore(const Device& device, std::optional initial) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // 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 ls::vulkan_error(res, "Unable to create semaphore"); + + // store semaphore in shared ptr + this->isTimeline = initial.has_value(); + this->device = device.handle(); + this->semaphore = std::shared_ptr( + new VkSemaphore(semaphoreHandle), + [dev = device.handle()](VkSemaphore* semaphoreHandle) { + vkDestroySemaphore(dev, *semaphoreHandle, nullptr); + } + ); +} + +void Semaphore::signal(uint64_t value) const { + if (!this->isValid() || !this->isTimeline) + throw std::runtime_error("Invalid timeline semaphore"); + + const VkSemaphoreSignalInfo signalInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO, + .semaphore = this->handle(), + .value = value + }; + auto res = vkSignalSemaphore(this->device, &signalInfo); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Unable to signal semaphore"); +} + +bool Semaphore::wait(uint64_t value, uint64_t timeout) { + if (!this->isValid() || !this->isTimeline) + throw std::runtime_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(this->device, &waitInfo, timeout); + if (res != VK_SUCCESS && res != VK_TIMEOUT) + throw ls::vulkan_error(res, "Unable to wait for semaphore"); + + return res == VK_SUCCESS; +} diff --git a/src/core/shadermodule.cpp b/src/core/shadermodule.cpp new file mode 100644 index 0000000..f3edcd8 --- /dev/null +++ b/src/core/shadermodule.cpp @@ -0,0 +1,46 @@ +#include "core/shadermodule.hpp" +#include "utils/exceptions.hpp" + +#include +#include + +using namespace Vulkan::Core; + +ShaderModule::ShaderModule(const Device& device, const std::string& path) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // read shader bytecode + std::ifstream file(path, std::ios::ate | std::ios::binary); + if (!file) + throw std::system_error(errno, std::generic_category(), "Failed to open shader file: " + path); + + const std::streamsize size = file.tellg(); + std::vector code(static_cast(size)); + + file.seekg(0, std::ios::beg); + if (!file.read(reinterpret_cast(code.data()), size)) + throw std::system_error(errno, std::generic_category(), "Failed to read shader file: " + path); + + file.close(); + + // create shader module + const uint8_t* data_ptr = code.data(); + const VkShaderModuleCreateInfo createInfo{ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = code.size() * sizeof(uint32_t), + .pCode = reinterpret_cast(data_ptr) + }; + VkShaderModule shaderModuleHandle{}; + auto res = vkCreateShaderModule(device.handle(), &createInfo, nullptr, &shaderModuleHandle); + if (res != VK_SUCCESS || !shaderModuleHandle) + throw ls::vulkan_error(res, "Failed to create shader module"); + + // store shader module in shared ptr + this->shaderModule = std::shared_ptr( + new VkShaderModule(shaderModuleHandle), + [dev = device.handle()](VkShaderModule* shaderModuleHandle) { + vkDestroyShaderModule(dev, *shaderModuleHandle, nullptr); + } + ); +} diff --git a/src/utils/exceptions.cpp b/src/utils/exceptions.cpp new file mode 100644 index 0000000..3c41429 --- /dev/null +++ b/src/utils/exceptions.cpp @@ -0,0 +1,10 @@ +#include "utils/exceptions.hpp" + +#include + +using namespace ls; + +vulkan_error::vulkan_error(VkResult result, const std::string& message) + : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} + +vulkan_error::~vulkan_error() noexcept = default; From 8bbeb183adc338f917e34b13b8b0efb988b77c55 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 15:34:47 +0200 Subject: [PATCH 003/253] add descriptor set layouts to shadermoduel --- include/core/shadermodule.hpp | 8 +++++++- src/core/shadermodule.cpp | 31 ++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/include/core/shadermodule.hpp b/include/core/shadermodule.hpp index 18b40dc..cdcf3f6 100644 --- a/include/core/shadermodule.hpp +++ b/include/core/shadermodule.hpp @@ -4,6 +4,7 @@ #include "device.hpp" #include +#include #include #include @@ -22,15 +23,19 @@ namespace Vulkan::Core { /// /// @param device Vulkan device /// @param path Path to the shader file. + /// @param descriptorTypes Descriptor types used in the shader. /// /// @throws std::invalid_argument if the device is invalid. /// @throws std::system_error if the shader file cannot be opened or read. /// @throws ls::vulkan_error if object creation fails. /// - ShaderModule(const Device& device, const std::string& path); + ShaderModule(const Device& device, const std::string& path, + std::vector descriptorTypes); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->shaderModule; } + /// Get the descriptor set layout. + [[nodiscard]] auto getDescriptorSetLayout() const { return *this->descriptorSetLayout; } /// Check whether the object is valid. [[nodiscard]] bool isValid() const { return static_cast(this->shaderModule); } @@ -45,6 +50,7 @@ namespace Vulkan::Core { ~ShaderModule() = default; private: std::shared_ptr shaderModule; + std::shared_ptr descriptorSetLayout; }; } diff --git a/src/core/shadermodule.cpp b/src/core/shadermodule.cpp index f3edcd8..eb7ecb8 100644 --- a/src/core/shadermodule.cpp +++ b/src/core/shadermodule.cpp @@ -6,7 +6,8 @@ using namespace Vulkan::Core; -ShaderModule::ShaderModule(const Device& device, const std::string& path) { +ShaderModule::ShaderModule(const Device& device, const std::string& path, + std::vector descriptorTypes) { if (!device) throw std::invalid_argument("Invalid Vulkan device"); @@ -43,4 +44,32 @@ ShaderModule::ShaderModule(const Device& device, const std::string& path) { vkDestroyShaderModule(dev, *shaderModuleHandle, nullptr); } ); + + // create descriptor set layout + std::vector layoutBindings(descriptorTypes.size()); + for (size_t i = 0; i < descriptorTypes.size(); ++i) + layoutBindings.at(i) = { + .binding = static_cast(i), + .descriptorType = descriptorTypes.at(i), + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT + }; + + const VkDescriptorSetLayoutCreateInfo layoutDesc{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = static_cast(layoutBindings.size()), + .pBindings = layoutBindings.data() + }; + VkDescriptorSetLayout descriptorSetLayout{}; + res = vkCreateDescriptorSetLayout(device.handle(), &layoutDesc, nullptr, &descriptorSetLayout); + if (res != VK_SUCCESS || !descriptorSetLayout) + throw ls::vulkan_error(res, "Failed to create descriptor set layout"); + + // store layout in shared ptr + this->descriptorSetLayout = std::shared_ptr( + new VkDescriptorSetLayout(descriptorSetLayout), + [dev = device.handle()](VkDescriptorSetLayout* layout) { + vkDestroyDescriptorSetLayout(dev, *layout, nullptr); + } + ); } From e5eb4cb740f604cce7a8e82217b54dd04b7ed3dd Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 15:34:58 +0200 Subject: [PATCH 004/253] core image object --- include/core/image.hpp | 65 +++++++++++++++++++++++ src/core/image.cpp | 117 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 include/core/image.hpp create mode 100644 src/core/image.cpp diff --git a/include/core/image.hpp b/include/core/image.hpp new file mode 100644 index 0000000..9fe3d52 --- /dev/null +++ b/include/core/image.hpp @@ -0,0 +1,65 @@ +#ifndef IMAGE_HPP +#define IMAGE_HPP + +#include "device.hpp" + +#include + +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan image. + /// + /// This class manages the lifetime of a Vulkan image. + /// + class Image { + public: + /// + /// 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 std::invalid_argument if the device is invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + Image(const Device& device, VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->image; } + /// 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; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->image); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + Image(const Image&) noexcept = default; + Image& operator=(const Image&) noexcept = default; + Image(Image&&) noexcept = default; + Image& operator=(Image&&) noexcept = default; + ~Image() = default; + private: + std::shared_ptr image; + std::shared_ptr memory; + std::shared_ptr view; + + VkExtent2D extent; + VkFormat format; + }; + +} + +#endif // IMAGE_HPP diff --git a/src/core/image.cpp b/src/core/image.cpp new file mode 100644 index 0000000..5c8eb38 --- /dev/null +++ b/src/core/image.cpp @@ -0,0 +1,117 @@ +#include "core/image.hpp" +#include "utils/exceptions.hpp" + +#include + +using namespace Vulkan::Core; + +Image::Image(const Device& device, VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags) + : extent(extent), format(format) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // 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 ls::vulkan_error(res, "Failed to create Vulkan image"); + + // find memory type + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps); + + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device.handle(), imageHandle, &memReqs); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + std::optional memType{}; + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN + (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + memType.emplace(i); + break; + } // NOLINTEND + } + if (!memType.has_value()) + throw ls::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 ls::vulkan_error(res, "Failed to allocate memory for Vulkan image"); + + res = vkBindImageMemory(device.handle(), imageHandle, memoryHandle, 0); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Failed to bind memory to Vulkan image"); + + // store image and memory in shared ptr + this->image = std::shared_ptr( + new VkImage(imageHandle), + [dev = device.handle()](VkImage* img) { + vkDestroyImage(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device.handle()](VkDeviceMemory* mem) { + vkFreeMemory(dev, *mem, nullptr); + } + ); + + // create image view + const VkImageViewCreateInfo viewDesc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = *this->image, + .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 ls::vulkan_error(res, "Failed to create image view"); + + // store image view in shared ptr + this->view = std::shared_ptr( + new VkImageView(viewHandle), + [dev = device.handle()](VkImageView* imgView) { + vkDestroyImageView(dev, *imgView, nullptr); + } + ); + +} From ce6542a0397a7c919203e9142477f2d3d22f3724 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 16:25:03 +0200 Subject: [PATCH 005/253] fixes --- .gitignore | 3 +++ include/core/commandbuffer.hpp | 1 + include/core/shadermodule.hpp | 4 ++-- src/core/commandbuffer.cpp | 1 - src/core/fence.cpp | 5 ++--- src/core/semaphore.cpp | 4 ++-- src/core/shadermodule.cpp | 2 +- src/device.cpp | 6 ++++++ src/instance.cpp | 2 +- src/main.cpp | 17 +++++++++++++++++ 10 files changed, 35 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index eb55056..0b4f101 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ /.clangd /.cache /.ccls + +# private shaders +/shaders diff --git a/include/core/commandbuffer.hpp b/include/core/commandbuffer.hpp index b60c437..20a8105 100644 --- a/include/core/commandbuffer.hpp +++ b/include/core/commandbuffer.hpp @@ -4,6 +4,7 @@ #include "core/commandpool.hpp" #include "core/semaphore.hpp" #include "device.hpp" + #include #include diff --git a/include/core/shadermodule.hpp b/include/core/shadermodule.hpp index cdcf3f6..4ef8390 100644 --- a/include/core/shadermodule.hpp +++ b/include/core/shadermodule.hpp @@ -3,10 +3,10 @@ #include "device.hpp" -#include -#include #include +#include +#include #include namespace Vulkan::Core { diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp index 15b4b8d..173b15a 100644 --- a/src/core/commandbuffer.cpp +++ b/src/core/commandbuffer.cpp @@ -1,5 +1,4 @@ #include "core/commandbuffer.hpp" -#include "core/semaphore.hpp" #include "utils/exceptions.hpp" using namespace Vulkan::Core; diff --git a/src/core/fence.cpp b/src/core/fence.cpp index b4d6610..25a515d 100644 --- a/src/core/fence.cpp +++ b/src/core/fence.cpp @@ -1,6 +1,5 @@ #include "core/fence.hpp" #include "utils/exceptions.hpp" -#include using namespace Vulkan::Core; @@ -29,7 +28,7 @@ Fence::Fence(const Device& device) { void Fence::reset() const { if (!this->isValid()) - throw std::runtime_error("Invalid fence"); + throw std::logic_error("Invalid fence"); VkFence fenceHandle = this->handle(); auto res = vkResetFences(this->device, 1, &fenceHandle); @@ -39,7 +38,7 @@ void Fence::reset() const { bool Fence::wait(uint64_t timeout) { if (!this->isValid()) - throw std::runtime_error("Invalid fence"); + throw std::logic_error("Invalid fence"); VkFence fenceHandle = this->handle(); auto res = vkWaitForFences(this->device, 1, &fenceHandle, VK_TRUE, timeout); diff --git a/src/core/semaphore.cpp b/src/core/semaphore.cpp index cc484a9..db4142b 100644 --- a/src/core/semaphore.cpp +++ b/src/core/semaphore.cpp @@ -35,7 +35,7 @@ Semaphore::Semaphore(const Device& device, std::optional initial) { void Semaphore::signal(uint64_t value) const { if (!this->isValid() || !this->isTimeline) - throw std::runtime_error("Invalid timeline semaphore"); + throw std::logic_error("Invalid timeline semaphore"); const VkSemaphoreSignalInfo signalInfo{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO, @@ -49,7 +49,7 @@ void Semaphore::signal(uint64_t value) const { bool Semaphore::wait(uint64_t value, uint64_t timeout) { if (!this->isValid() || !this->isTimeline) - throw std::runtime_error("Invalid timeline semaphore"); + throw std::logic_error("Invalid timeline semaphore"); VkSemaphore semaphore = this->handle(); const VkSemaphoreWaitInfo waitInfo{ diff --git a/src/core/shadermodule.cpp b/src/core/shadermodule.cpp index eb7ecb8..f7258d6 100644 --- a/src/core/shadermodule.cpp +++ b/src/core/shadermodule.cpp @@ -29,7 +29,7 @@ ShaderModule::ShaderModule(const Device& device, const std::string& path, const uint8_t* data_ptr = code.data(); const VkShaderModuleCreateInfo createInfo{ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - .codeSize = code.size() * sizeof(uint32_t), + .codeSize = code.size(), .pCode = reinterpret_cast(data_ptr) }; VkShaderModule shaderModuleHandle{}; diff --git a/src/device.cpp b/src/device.cpp index e854a93..d8e1bcf 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -52,6 +52,11 @@ Device::Device(const Instance& instance) { // create logical device const float queuePriority{1.0F}; // highest priority + const VkPhysicalDeviceVulkan12Features features{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, + .timelineSemaphore = VK_TRUE, + .vulkanMemoryModel = VK_TRUE, + }; const VkDeviceQueueCreateInfo computeQueueDesc{ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = *computeFamilyIdx, @@ -60,6 +65,7 @@ Device::Device(const Instance& instance) { }; const VkDeviceCreateInfo deviceCreateInfo{ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &features, .queueCreateInfoCount = 1, .pQueueCreateInfos = &computeQueueDesc }; diff --git a/src/instance.cpp b/src/instance.cpp index 96a6d68..3b829c9 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -21,7 +21,7 @@ Instance::Instance() { .applicationVersion = VK_MAKE_VERSION(0, 0, 1), .pEngineName = "lsfg-vk-base", .engineVersion = VK_MAKE_VERSION(0, 0, 1), - .apiVersion = VK_API_VERSION_1_4 + .apiVersion = VK_API_VERSION_1_3 }; const VkInstanceCreateInfo createInfo{ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, diff --git a/src/main.cpp b/src/main.cpp index 48c0f86..ca594f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,29 @@ +#include "core/shadermodule.hpp" #include "device.hpp" #include "instance.hpp" #include +#include int main() { const Vulkan::Instance instance; const Vulkan::Device device(instance); + const Vulkan::Core::ShaderModule computeShader(device, "shaders/downsample.spv", + { + VK_DESCRIPTOR_TYPE_SAMPLER, + VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER + } + ); + std::cerr << "Application finished" << '\n'; return 0; } From 2717f02f0ffdaa370c20cc34aa8c3d48170826ea Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 16:45:35 +0200 Subject: [PATCH 006/253] core pipeline object --- include/core/pipeline.hpp | 54 ++++++++++++++++++++++++++++++++++++++ src/core/pipeline.cpp | 55 +++++++++++++++++++++++++++++++++++++++ src/main.cpp | 2 ++ 3 files changed, 111 insertions(+) create mode 100644 include/core/pipeline.hpp create mode 100644 src/core/pipeline.cpp diff --git a/include/core/pipeline.hpp b/include/core/pipeline.hpp new file mode 100644 index 0000000..d951359 --- /dev/null +++ b/include/core/pipeline.hpp @@ -0,0 +1,54 @@ +#ifndef PIPELINE_HPP +#define PIPELINE_HPP + +#include "core/shadermodule.hpp" +#include "device.hpp" + +#include + +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan pipeline. + /// + /// This class manages the lifetime of a Vulkan pipeline. + /// + class Pipeline { + public: + /// + /// Create a compute pipeline. + /// + /// @param device Vulkan device + /// @param shader Shader module to use for the pipeline. + /// + /// @throws std::invalid_argument if the device is invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + Pipeline(const Device& device, const ShaderModule& shader); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->pipeline; } + /// Get the pipeline layout. + [[nodiscard]] auto getLayout() const { return *this->layout; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->pipeline); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + Pipeline(const Pipeline&) noexcept = default; + Pipeline& operator=(const Pipeline&) noexcept = default; + Pipeline(Pipeline&&) noexcept = default; + Pipeline& operator=(Pipeline&&) noexcept = default; + ~Pipeline() = default; + private: + std::shared_ptr pipeline; + std::shared_ptr layout; + }; + +} + +#endif // PIPELINE_HPP diff --git a/src/core/pipeline.cpp b/src/core/pipeline.cpp new file mode 100644 index 0000000..b60fb7b --- /dev/null +++ b/src/core/pipeline.cpp @@ -0,0 +1,55 @@ +#include "core/pipeline.hpp" +#include "utils/exceptions.hpp" + +using namespace Vulkan::Core; + +Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // create pipeline layout + VkDescriptorSetLayout shaderLayout = shader.getDescriptorSetLayout(); + 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 ls::vulkan_error(res, "Failed to create pipeline layout"); + + // store layout in shared ptr + this->layout = std::shared_ptr( + new VkPipelineLayout(layoutHandle), + [dev = device.handle()](VkPipelineLayout* layout) { + vkDestroyPipelineLayout(dev, *layout, nullptr); + } + ); + + // 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 ls::vulkan_error(res, "Failed to create compute pipeline"); + + // store pipeline in shared ptr + this->pipeline = std::shared_ptr( + new VkPipeline(pipelineHandle), + [dev = device.handle()](VkPipeline* pipeline) { + vkDestroyPipeline(dev, *pipeline, nullptr); + } + ); +} diff --git a/src/main.cpp b/src/main.cpp index ca594f8..b4df0a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include "core/pipeline.hpp" #include "core/shadermodule.hpp" #include "device.hpp" #include "instance.hpp" @@ -23,6 +24,7 @@ int main() { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } ); + const Vulkan::Core::Pipeline computePipeline(device, computeShader); std::cerr << "Application finished" << '\n'; return 0; From d3f5d87d613a7a2190c3bc86234c49c274e13b3d Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 16:52:01 +0200 Subject: [PATCH 007/253] more comfortable way to initialize shader --- include/core/shadermodule.hpp | 3 ++- src/core/shadermodule.cpp | 20 +++++++++++--------- src/main.cpp | 17 ++++------------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/include/core/shadermodule.hpp b/include/core/shadermodule.hpp index 4ef8390..9971f56 100644 --- a/include/core/shadermodule.hpp +++ b/include/core/shadermodule.hpp @@ -3,6 +3,7 @@ #include "device.hpp" +#include #include #include @@ -30,7 +31,7 @@ namespace Vulkan::Core { /// @throws ls::vulkan_error if object creation fails. /// ShaderModule(const Device& device, const std::string& path, - std::vector descriptorTypes); + const std::vector>& descriptorTypes); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->shaderModule; } diff --git a/src/core/shadermodule.cpp b/src/core/shadermodule.cpp index f7258d6..703b609 100644 --- a/src/core/shadermodule.cpp +++ b/src/core/shadermodule.cpp @@ -7,7 +7,7 @@ using namespace Vulkan::Core; ShaderModule::ShaderModule(const Device& device, const std::string& path, - std::vector descriptorTypes) { + const std::vector>& descriptorTypes) { if (!device) throw std::invalid_argument("Invalid Vulkan device"); @@ -46,14 +46,16 @@ ShaderModule::ShaderModule(const Device& device, const std::string& path, ); // create descriptor set layout - std::vector layoutBindings(descriptorTypes.size()); - for (size_t i = 0; i < descriptorTypes.size(); ++i) - layoutBindings.at(i) = { - .binding = static_cast(i), - .descriptorType = descriptorTypes.at(i), - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT - }; + std::vector layoutBindings; + size_t bindIdx = 0; + for (const auto &[count, type] : descriptorTypes) + for (size_t i = 0; i < count; i++, bindIdx++) + layoutBindings.emplace_back(VkDescriptorSetLayoutBinding { + .binding = static_cast(bindIdx), + .descriptorType = type, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT + }); const VkDescriptorSetLayoutCreateInfo layoutDesc{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, diff --git a/src/main.cpp b/src/main.cpp index b4df0a9..d55ef02 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,19 +11,10 @@ int main() { const Vulkan::Device device(instance); const Vulkan::Core::ShaderModule computeShader(device, "shaders/downsample.spv", - { - VK_DESCRIPTOR_TYPE_SAMPLER, - VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER - } - ); + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER}, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER} }); const Vulkan::Core::Pipeline computePipeline(device, computeShader); std::cerr << "Application finished" << '\n'; From 20fcab9a882287510795a5095cc31530297bbe6a Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 17:20:38 +0200 Subject: [PATCH 008/253] fence support in commandbuffer --- include/core/commandbuffer.hpp | 4 +++- src/core/commandbuffer.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/core/commandbuffer.hpp b/include/core/commandbuffer.hpp index 20a8105..4c73ff6 100644 --- a/include/core/commandbuffer.hpp +++ b/include/core/commandbuffer.hpp @@ -2,6 +2,7 @@ #define COMMANDBUFFER_HPP #include "core/commandpool.hpp" +#include "core/fence.hpp" #include "core/semaphore.hpp" #include "device.hpp" @@ -65,6 +66,7 @@ namespace Vulkan::Core { /// 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 @@ -74,7 +76,7 @@ namespace Vulkan::Core { /// @throws std::logic_error if the command buffer is not in Full state. /// @throws ls::vulkan_error if submission fails. /// - void submit(VkQueue queue, // TODO: fence + void submit(VkQueue queue, std::optional fence, const std::vector& waitSemaphores = {}, std::optional> waitSemaphoreValues = std::nullopt, const std::vector& signalSemaphores = {}, diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp index 173b15a..6a03d99 100644 --- a/src/core/commandbuffer.cpp +++ b/src/core/commandbuffer.cpp @@ -55,7 +55,7 @@ void CommandBuffer::end() { *this->state = CommandBufferState::Full; } -void CommandBuffer::submit(VkQueue queue, +void CommandBuffer::submit(VkQueue queue, std::optional fence, const std::vector& waitSemaphores, std::optional> waitSemaphoreValues, const std::vector& signalSemaphores, @@ -106,7 +106,7 @@ void CommandBuffer::submit(VkQueue queue, .signalSemaphoreCount = static_cast(signalSemaphores.size()), .pSignalSemaphores = signalSemaphoresHandles.data() }; - auto res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + auto res = vkQueueSubmit(queue, 1, &submitInfo, fence ? fence->handle() : VK_NULL_HANDLE); if (res != VK_SUCCESS) throw ls::vulkan_error(res, "Unable to submit command buffer"); From 93ecfbd79e7d3b1c60bca95707f4f290ffb9a817 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 17:21:04 +0200 Subject: [PATCH 009/253] const correctness --- include/core/fence.hpp | 2 +- include/core/semaphore.hpp | 2 +- src/core/fence.cpp | 2 +- src/core/semaphore.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/core/fence.hpp b/include/core/fence.hpp index 6b9135d..bb921bd 100644 --- a/include/core/fence.hpp +++ b/include/core/fence.hpp @@ -43,7 +43,7 @@ namespace Vulkan::Core { /// @throws std::logic_error if the fence is not valid. /// @throws ls::vulkan_error if waiting fails. /// - bool wait(uint64_t timeout = UINT64_MAX); + [[nodiscard]] bool wait(uint64_t timeout = UINT64_MAX) const; /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->fence; } diff --git a/include/core/semaphore.hpp b/include/core/semaphore.hpp index e20c8c6..46c3683 100644 --- a/include/core/semaphore.hpp +++ b/include/core/semaphore.hpp @@ -48,7 +48,7 @@ namespace Vulkan::Core { /// @throws std::logic_error if the semaphore is not a timeline semaphore. /// @throws ls::vulkan_error if waiting fails. /// - bool wait(uint64_t value, uint64_t timeout = UINT64_MAX); + [[nodiscard]] bool wait(uint64_t value, uint64_t timeout = UINT64_MAX) const; /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->semaphore; } diff --git a/src/core/fence.cpp b/src/core/fence.cpp index 25a515d..7fd9088 100644 --- a/src/core/fence.cpp +++ b/src/core/fence.cpp @@ -36,7 +36,7 @@ void Fence::reset() const { throw ls::vulkan_error(res, "Unable to reset fence"); } -bool Fence::wait(uint64_t timeout) { +bool Fence::wait(uint64_t timeout) const { if (!this->isValid()) throw std::logic_error("Invalid fence"); diff --git a/src/core/semaphore.cpp b/src/core/semaphore.cpp index db4142b..0da15cb 100644 --- a/src/core/semaphore.cpp +++ b/src/core/semaphore.cpp @@ -47,7 +47,7 @@ void Semaphore::signal(uint64_t value) const { throw ls::vulkan_error(res, "Unable to signal semaphore"); } -bool Semaphore::wait(uint64_t value, uint64_t timeout) { +bool Semaphore::wait(uint64_t value, uint64_t timeout) const { if (!this->isValid() || !this->isTimeline) throw std::logic_error("Invalid timeline semaphore"); From 2832020d10c12a253c819328fa0ed4335856b128 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 17:21:21 +0200 Subject: [PATCH 010/253] fixing bugs and continuing main --- include/core/shadermodule.hpp | 2 +- src/core/commandbuffer.cpp | 2 +- src/core/fence.cpp | 2 +- src/main.cpp | 32 +++++++++++++++++++++++++++----- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/include/core/shadermodule.hpp b/include/core/shadermodule.hpp index 9971f56..0cce6df 100644 --- a/include/core/shadermodule.hpp +++ b/include/core/shadermodule.hpp @@ -3,9 +3,9 @@ #include "device.hpp" -#include #include +#include #include #include #include diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp index 6a03d99..3b66840 100644 --- a/src/core/commandbuffer.cpp +++ b/src/core/commandbuffer.cpp @@ -16,7 +16,7 @@ CommandBuffer::CommandBuffer(const Device& device, const CommandPool& pool) { }; VkCommandBuffer commandBufferHandle{}; auto res = vkAllocateCommandBuffers(device.handle(), &desc, &commandBufferHandle); - if (res != VK_SUCCESS || commandBuffer == VK_NULL_HANDLE) + if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE) throw ls::vulkan_error(res, "Unable to allocate command buffer"); // store command buffer in shared ptr diff --git a/src/core/fence.cpp b/src/core/fence.cpp index 7fd9088..a0ddd75 100644 --- a/src/core/fence.cpp +++ b/src/core/fence.cpp @@ -9,7 +9,7 @@ Fence::Fence(const Device& device) { // create fence const VkFenceCreateInfo desc = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; VkFence fenceHandle{}; auto res = vkCreateFence(device.handle(), &desc, nullptr, &fenceHandle); diff --git a/src/main.cpp b/src/main.cpp index d55ef02..5613bb5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,21 +1,43 @@ +#include "core/commandbuffer.hpp" +#include "core/commandpool.hpp" +#include "core/fence.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" #include "device.hpp" #include "instance.hpp" +#include #include -#include + +using namespace Vulkan; int main() { - const Vulkan::Instance instance; - const Vulkan::Device device(instance); + // initialize Vulkan + const Instance instance; + const Device device(instance); - const Vulkan::Core::ShaderModule computeShader(device, "shaders/downsample.spv", + // prepare render pass + const Core::CommandPool commandPool(device, Core::CommandPoolType::Compute); + + // prepare shader + const Core::ShaderModule computeShader(device, "shaders/downsample.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER}, { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER} }); - const Vulkan::Core::Pipeline computePipeline(device, computeShader); + const Core::Pipeline computePipeline(device, computeShader); + + // start pass + Core::Fence fence(device); + + Core::CommandBuffer commandBuffer(device, commandPool); + commandBuffer.begin(); + + // end pass + commandBuffer.end(); + + commandBuffer.submit(device.getComputeQueue(), fence); + assert(fence.wait() && "Synchronization fence timed out"); std::cerr << "Application finished" << '\n'; return 0; From 013950596c6f630247652095ce2752612bfb4621 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 18:08:04 +0200 Subject: [PATCH 011/253] more compute mandatory functions --- include/core/commandbuffer.hpp | 13 ++++++++++++- include/core/commandpool.hpp | 9 +-------- include/core/pipeline.hpp | 10 ++++++++++ src/core/commandbuffer.cpp | 7 +++++++ src/core/commandpool.cpp | 11 ++--------- src/core/pipeline.cpp | 7 +++++++ src/main.cpp | 2 +- 7 files changed, 40 insertions(+), 19 deletions(-) diff --git a/include/core/commandbuffer.hpp b/include/core/commandbuffer.hpp index 4c73ff6..b01cf16 100644 --- a/include/core/commandbuffer.hpp +++ b/include/core/commandbuffer.hpp @@ -54,6 +54,17 @@ namespace Vulkan::Core { /// 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); + /// /// End recording commands in the command buffer. /// @@ -85,7 +96,7 @@ namespace Vulkan::Core { /// Get the state of the command buffer. [[nodiscard]] CommandBufferState getState() const { return *this->state; } /// Get the Vulkan handle. - [[nodiscard]] auto handle() const { *this->commandBuffer; } + [[nodiscard]] auto handle() const { return *this->commandBuffer; } /// Check whether the object is valid. [[nodiscard]] bool isValid() const { return static_cast(this->commandBuffer); } diff --git a/include/core/commandpool.hpp b/include/core/commandpool.hpp index b2d0909..3936983 100644 --- a/include/core/commandpool.hpp +++ b/include/core/commandpool.hpp @@ -9,12 +9,6 @@ namespace Vulkan::Core { - /// Enumeration for different types of command pools. - enum class CommandPoolType { - /// Used for compute-type command buffers. - Compute - }; - /// /// C++ wrapper class for a Vulkan command pool. /// @@ -26,12 +20,11 @@ namespace Vulkan::Core { /// Create the command pool. /// /// @param device Vulkan device - /// @param type Type of command pool to create. /// /// @throws std::invalid_argument if the device is invalid. /// @throws ls::vulkan_error if object creation fails. /// - CommandPool(const Device& device, CommandPoolType type); + CommandPool(const Device& device); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->commandPool; } diff --git a/include/core/pipeline.hpp b/include/core/pipeline.hpp index d951359..36c7a65 100644 --- a/include/core/pipeline.hpp +++ b/include/core/pipeline.hpp @@ -1,6 +1,7 @@ #ifndef PIPELINE_HPP #define PIPELINE_HPP +#include "core/commandbuffer.hpp" #include "core/shadermodule.hpp" #include "device.hpp" @@ -28,6 +29,15 @@ namespace Vulkan::Core { /// Pipeline(const Device& device, const ShaderModule& shader); + /// + /// Bind the pipeline to a command buffer. + /// + /// @param commandBuffer Command buffer to bind the pipeline to. + /// + /// @throws std::invalid_argument if the command buffer is invalid. + /// + void bind(const CommandBuffer& commandBuffer) const; + /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->pipeline; } /// Get the pipeline layout. diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp index 3b66840..fa50bab 100644 --- a/src/core/commandbuffer.cpp +++ b/src/core/commandbuffer.cpp @@ -44,6 +44,13 @@ void CommandBuffer::begin() { *this->state = CommandBufferState::Recording; } +void CommandBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z) { + 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"); diff --git a/src/core/commandpool.cpp b/src/core/commandpool.cpp index 65b7ddf..6061e37 100644 --- a/src/core/commandpool.cpp +++ b/src/core/commandpool.cpp @@ -3,21 +3,14 @@ using namespace Vulkan::Core; -CommandPool::CommandPool(const Device& device, CommandPoolType type) { +CommandPool::CommandPool(const Device& device) { if (!device) throw std::invalid_argument("Invalid Vulkan device"); - uint32_t familyIdx{}; - switch (type) { - case CommandPoolType::Compute: - familyIdx = device.getComputeFamilyIdx(); - break; - } - // create command pool const VkCommandPoolCreateInfo desc = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .queueFamilyIndex = familyIdx + .queueFamilyIndex = device.getComputeFamilyIdx() }; VkCommandPool commandPoolHandle{}; auto res = vkCreateCommandPool(device.handle(), &desc, nullptr, &commandPoolHandle); diff --git a/src/core/pipeline.cpp b/src/core/pipeline.cpp index b60fb7b..120c6f1 100644 --- a/src/core/pipeline.cpp +++ b/src/core/pipeline.cpp @@ -53,3 +53,10 @@ Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { } ); } + +void Pipeline::bind(const CommandBuffer& commandBuffer) const { + if (!commandBuffer) + throw std::invalid_argument("Invalid command buffer"); + + vkCmdBindPipeline(commandBuffer.handle(), VK_PIPELINE_BIND_POINT_COMPUTE, *this->pipeline); +} diff --git a/src/main.cpp b/src/main.cpp index 5613bb5..69ea2cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,7 +17,7 @@ int main() { const Device device(instance); // prepare render pass - const Core::CommandPool commandPool(device, Core::CommandPoolType::Compute); + const Core::CommandPool commandPool(device); // prepare shader const Core::ShaderModule computeShader(device, "shaders/downsample.spv", From d3a903524a8c760b2dd3c1d8ec30ab356f2e09e1 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 18:08:17 +0200 Subject: [PATCH 012/253] core descriptor pool/set objects --- include/core/descriptorpool.hpp | 49 ++++++++++++++++++++++++ include/core/descriptorset.hpp | 66 +++++++++++++++++++++++++++++++++ src/core/descriptorpool.cpp | 37 ++++++++++++++++++ src/core/descriptorset.cpp | 43 +++++++++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 include/core/descriptorpool.hpp create mode 100644 include/core/descriptorset.hpp create mode 100644 src/core/descriptorpool.cpp create mode 100644 src/core/descriptorset.cpp diff --git a/include/core/descriptorpool.hpp b/include/core/descriptorpool.hpp new file mode 100644 index 0000000..9b4d470 --- /dev/null +++ b/include/core/descriptorpool.hpp @@ -0,0 +1,49 @@ +#ifndef DESCRIPTORPOOL_HPP +#define DESCRIPTORPOOL_HPP + +#include "device.hpp" + +#include + +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan descriptor pool. + /// + /// This class manages the lifetime of a Vulkan descriptor pool. + /// + class DescriptorPool { + public: + /// + /// Create the descriptor pool. + /// + /// @param device Vulkan device + /// + /// @throws std::invalid_argument if the device is invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + DescriptorPool(const Device& device); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->descriptorPool; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->descriptorPool); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + DescriptorPool(const DescriptorPool&) noexcept = default; + DescriptorPool& operator=(const DescriptorPool&) noexcept = default; + DescriptorPool(DescriptorPool&&) noexcept = default; + DescriptorPool& operator=(DescriptorPool&&) noexcept = default; + ~DescriptorPool() = default; + private: + std::shared_ptr descriptorPool; + }; + +} + +#endif // DESCRIPTORPOOL_HPP diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp new file mode 100644 index 0000000..bfd110f --- /dev/null +++ b/include/core/descriptorset.hpp @@ -0,0 +1,66 @@ +#ifndef DESCRIPTORSET_HPP +#define DESCRIPTORSET_HPP + +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" + +#include + +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan descriptor set. + /// + /// This class manages the lifetime of a Vulkan descriptor set. + /// + class DescriptorSet { + public: + /// + /// 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 std::invalid_argument if the device or pool is invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + DescriptorSet(const Device& device, + DescriptorPool pool, const ShaderModule& shaderModule); + + /// + /// 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. + /// + /// @throws std::invalid_argument if the command buffer is invalid. + /// + void bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const; + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->descriptorSet; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->descriptorSet); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + DescriptorSet(const DescriptorSet&) noexcept = default; + DescriptorSet& operator=(const DescriptorSet&) noexcept = default; + DescriptorSet(DescriptorSet&&) noexcept = default; + DescriptorSet& operator=(DescriptorSet&&) noexcept = default; + ~DescriptorSet() = default; + private: + std::shared_ptr descriptorSet; + }; + +} + +#endif // DESCRIPTORSET_HPP diff --git a/src/core/descriptorpool.cpp b/src/core/descriptorpool.cpp new file mode 100644 index 0000000..340092e --- /dev/null +++ b/src/core/descriptorpool.cpp @@ -0,0 +1,37 @@ +#include "core/descriptorpool.hpp" +#include "utils/exceptions.hpp" + +#include + +using namespace Vulkan::Core; + +DescriptorPool::DescriptorPool(const Device& device) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // create descriptor pool + const std::array pools{{ // arbitrary limits + { .type = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = 4096 }, + { .type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, .descriptorCount = 4096 }, + { .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 4096 }, + { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 4096 } + }}; + const VkDescriptorPoolCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .maxSets = 16384, // arbitrary limit + .poolSizeCount = static_cast(pools.size()), + .pPoolSizes = pools.data() + }; + VkDescriptorPool poolHandle{}; + auto res = vkCreateDescriptorPool(device.handle(), &desc, nullptr, &poolHandle); + if (res != VK_SUCCESS || poolHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Unable to create descriptor pool"); + + /// store pool in shared ptr + this->descriptorPool = std::shared_ptr( + new VkDescriptorPool(poolHandle), + [dev = device.handle()](VkDescriptorPool* poolHandle) { + vkDestroyDescriptorPool(dev, *poolHandle, nullptr); + } + ); +} diff --git a/src/core/descriptorset.cpp b/src/core/descriptorset.cpp new file mode 100644 index 0000000..73a0a60 --- /dev/null +++ b/src/core/descriptorset.cpp @@ -0,0 +1,43 @@ +#include "core/descriptorset.hpp" +#include "utils/exceptions.hpp" + +using namespace Vulkan::Core; + +DescriptorSet::DescriptorSet(const Device& device, + DescriptorPool pool, const ShaderModule& shaderModule) { + if (!device || !pool) + throw std::invalid_argument("Invalid Vulkan device"); + + // create descriptor set + VkDescriptorSetLayout layout = shaderModule.getDescriptorSetLayout(); + 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 ls::vulkan_error(res, "Unable to allocate descriptor set"); + + /// store descriptor set in shared ptr + this->descriptorSet = std::shared_ptr( + new VkDescriptorSet(descriptorSetHandle), + [dev = device.handle(), pool = std::move(pool)](VkDescriptorSet* setHandle) { + vkFreeDescriptorSets(dev, pool.handle(), 1, setHandle); + } + ); +} + +void DescriptorSet::bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const { + if (!commandBuffer) + throw std::invalid_argument("Invalid command buffer"); + + // bind descriptor set + VkDescriptorSet descriptorSetHandle = this->handle(); + vkCmdBindDescriptorSets(commandBuffer.handle(), + VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.getLayout(), + 0, 1, &descriptorSetHandle, + 0, nullptr); +} From d0bd00d412b6b345879c520c47cac1879c02a9f5 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 19:04:45 +0200 Subject: [PATCH 013/253] core sampler and buffer objects --- include/core/buffer.hpp | 59 +++++++++++++++++++++++++++ include/core/sampler.hpp | 50 +++++++++++++++++++++++ src/core/buffer.cpp | 86 ++++++++++++++++++++++++++++++++++++++++ src/core/sampler.cpp | 33 +++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 include/core/buffer.hpp create mode 100644 include/core/sampler.hpp create mode 100644 src/core/buffer.cpp create mode 100644 src/core/sampler.cpp diff --git a/include/core/buffer.hpp b/include/core/buffer.hpp new file mode 100644 index 0000000..fecf3e1 --- /dev/null +++ b/include/core/buffer.hpp @@ -0,0 +1,59 @@ +#ifndef BUFFER_HPP +#define BUFFER_HPP + +#include "device.hpp" + +#include + +#include +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan buffer. + /// + /// This class manages the lifetime of a Vulkan buffer. + /// + class Buffer { + public: + /// + /// Create the buffer. + /// + /// @param device Vulkan device + /// @param size Size of the buffer in bytes. + /// @param data Initial data for the buffer. + /// @param usage Usage flags for the buffer + /// + /// @throws std::invalid_argument if the device or buffer size is invalid + /// @throws ls::vulkan_error if object creation fails. + /// + Buffer(const Device& device, uint32_t size, std::vector data, + VkBufferUsageFlags usage); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->buffer; } + /// Get the size of the buffer. + [[nodiscard]] uint32_t getSize() const { return this->size; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->buffer); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// 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: + std::shared_ptr buffer; + std::shared_ptr memory; + + uint32_t size; + }; + +} + +#endif // BUFFER_HPP diff --git a/include/core/sampler.hpp b/include/core/sampler.hpp new file mode 100644 index 0000000..84ce43a --- /dev/null +++ b/include/core/sampler.hpp @@ -0,0 +1,50 @@ +#ifndef SAMPLER_HPP +#define SAMPLER_HPP + +#include "device.hpp" + +#include + +#include + +namespace Vulkan::Core { + + /// + /// C++ wrapper class for a Vulkan sampler. + /// + /// This class manages the lifetime of a Vulkan sampler. + /// + class Sampler { + public: + /// + /// Create the sampler. + /// + /// @param device Vulkan device + /// @param mode Address mode for the sampler. + /// + /// @throws std::invalid_argument if the device is invalid. + /// @throws ls::vulkan_error if object creation fails. + /// + Sampler(const Device& device, VkSamplerAddressMode mode); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->sampler; } + + /// Check whether the object is valid. + [[nodiscard]] bool isValid() const { return static_cast(this->sampler); } + /// if (obj) operator. Checks if the object is valid. + explicit operator bool() const { return this->isValid(); } + + /// Trivially copyable, moveable and destructible + Sampler(const Sampler&) noexcept = default; + Sampler& operator=(const Sampler&) noexcept = default; + Sampler(Sampler&&) noexcept = default; + Sampler& operator=(Sampler&&) noexcept = default; + ~Sampler() = default; + private: + std::shared_ptr sampler; + }; + +} + +#endif // SAMPLER_HPP diff --git a/src/core/buffer.cpp b/src/core/buffer.cpp new file mode 100644 index 0000000..32e0e60 --- /dev/null +++ b/src/core/buffer.cpp @@ -0,0 +1,86 @@ +#include "core/buffer.hpp" +#include "utils/exceptions.hpp" + +#include +#include + +using namespace Vulkan::Core; + +Buffer::Buffer(const Device& device, uint32_t size, std::vector data, + VkBufferUsageFlags usage) : size(size) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + if (size < data.size()) + throw std::invalid_argument("Invalid buffer size"); + + // create buffer + const VkBufferCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .size = 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 ls::vulkan_error(res, "Failed to create Vulkan buffer"); + + // find memory type + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps); + + VkMemoryRequirements memReqs; + vkGetBufferMemoryRequirements(device.handle(), bufferHandle, &memReqs); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + std::optional memType{}; + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN + (memProps.memoryTypes[i].propertyFlags & + (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))) { + memType.emplace(i); + break; + } // NOLINTEND + } + if (!memType.has_value()) + throw ls::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 ls::vulkan_error(res, "Failed to allocate memory for Vulkan buffer"); + + res = vkBindBufferMemory(device.handle(), bufferHandle, memoryHandle, 0); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Failed to bind memory to Vulkan buffer"); + + // store buffer and memory in shared ptr + this->buffer = std::shared_ptr( + new VkBuffer(bufferHandle), + [dev = device.handle()](VkBuffer* img) { + vkDestroyBuffer(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device.handle()](VkDeviceMemory* mem) { + vkFreeMemory(dev, *mem, nullptr); + } + ); + + // upload data to buffer + uint8_t* buf{}; + res = vkMapMemory(device.handle(), memoryHandle, 0, size, 0, reinterpret_cast(&buf)); + if (res != VK_SUCCESS || buf == nullptr) + throw ls::vulkan_error(res, "Failed to map memory for Vulkan buffer"); + std::copy_n(data.data(), size, buf); + vkUnmapMemory(device.handle(), memoryHandle); +} diff --git a/src/core/sampler.cpp b/src/core/sampler.cpp new file mode 100644 index 0000000..e817e54 --- /dev/null +++ b/src/core/sampler.cpp @@ -0,0 +1,33 @@ +#include "core/sampler.hpp" +#include "utils/exceptions.hpp" + +using namespace Vulkan::Core; + +Sampler::Sampler(const Device& device, VkSamplerAddressMode mode) { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + // 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, + .maxLod = VK_LOD_CLAMP_NONE + }; + VkSampler samplerHandle{}; + auto res = vkCreateSampler(device.handle(), &desc, nullptr, &samplerHandle); + if (res != VK_SUCCESS || samplerHandle == VK_NULL_HANDLE) + throw ls::vulkan_error(res, "Unable to create sampler"); + + // store the sampler in a shared pointer + this->sampler = std::shared_ptr( + new VkSampler(samplerHandle), + [dev = device.handle()](VkSampler* samplerHandle) { + vkDestroySampler(dev, *samplerHandle, nullptr); + } + ); +} From 736fa9b74fd94cd6f83dd200f00774f575b14adf Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 19:04:56 +0200 Subject: [PATCH 014/253] descriptor set updates --- include/core/descriptorset.hpp | 19 ++++++++++- src/core/descriptorset.cpp | 60 ++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp index bfd110f..b2c2bf7 100644 --- a/include/core/descriptorset.hpp +++ b/include/core/descriptorset.hpp @@ -1,12 +1,16 @@ #ifndef DESCRIPTORSET_HPP #define DESCRIPTORSET_HPP +#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 "device.hpp" +#include #include #include @@ -33,13 +37,26 @@ namespace Vulkan::Core { DescriptorSet(const Device& device, DescriptorPool pool, const ShaderModule& shaderModule); + using ResourcePair = std::pair>; + + /// + /// Update the descriptor set with resources. + /// + /// @param device Vulkan device + /// @param resources Resources to update the descriptor set with + /// + /// @throws std::invalid_argument if the device or resources are invalid. + /// + void update(const Device& device, + const std::vector>& resources) 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. /// - /// @throws std::invalid_argument if the command buffer is invalid. + /// @throws std::invalid_argument if the command buffer or pipeline is invalid. /// void bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const; diff --git a/src/core/descriptorset.cpp b/src/core/descriptorset.cpp index 73a0a60..a483b36 100644 --- a/src/core/descriptorset.cpp +++ b/src/core/descriptorset.cpp @@ -30,6 +30,66 @@ DescriptorSet::DescriptorSet(const Device& device, ); } +void DescriptorSet::update(const Device& device, + const std::vector>& resources) const { + if (!device) + throw std::invalid_argument("Invalid Vulkan device"); + + std::vector writeDescriptorSets; + uint32_t bindingIndex = 0; + for (const auto& list : resources) { + for (const auto& [type, resource] : list) { + VkWriteDescriptorSet writeDesc{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = this->handle(), + .dstBinding = bindingIndex++, + .descriptorCount = 1, + .descriptorType = type, + }; + + if (std::holds_alternative(resource)) { + const auto& image = std::get(resource); + if (!image) + throw std::invalid_argument("Invalid image resource"); + + const VkDescriptorImageInfo imageInfo{ + .imageView = image.getView(), + .imageLayout = VK_IMAGE_LAYOUT_GENERAL + }; + writeDesc.pImageInfo = &imageInfo; + } else if (std::holds_alternative(resource)) { + const auto& sampler = std::get(resource); + if (!sampler) + throw std::invalid_argument("Invalid sampler resource"); + + const VkDescriptorImageInfo imageInfo{ + .sampler = sampler.handle() + }; + writeDesc.pImageInfo = &imageInfo; + } else if (std::holds_alternative(resource)) { + const auto& buffer = std::get(resource); + if (!buffer) + throw std::invalid_argument("Invalid buffer resource"); + + const VkDescriptorBufferInfo bufferInfo{ + .buffer = buffer.handle(), + .range = buffer.getSize() + }; + writeDesc.pBufferInfo = &bufferInfo; + } else { + throw std::invalid_argument("Unsupported resource type"); + } + + writeDescriptorSets.push_back(writeDesc); + } + } + + vkUpdateDescriptorSets(device.handle(), + static_cast(writeDescriptorSets.size()), + writeDescriptorSets.data(), + 0, nullptr); +} + void DescriptorSet::bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const { if (!commandBuffer) throw std::invalid_argument("Invalid command buffer"); From 6261a6ed6e9e87b890e0d83c047c995b76edc735 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 19:35:29 +0200 Subject: [PATCH 015/253] initial round of fixes --- include/core/buffer.hpp | 6 +- include/core/descriptorset.hpp | 4 +- include/core/fence.hpp | 4 +- include/core/pipeline.hpp | 2 +- src/core/buffer.cpp | 2 +- src/core/fence.cpp | 6 -- src/core/pipeline.cpp | 4 +- src/core/semaphore.cpp | 4 +- src/core/shadermodule.cpp | 1 - src/main.cpp | 116 +++++++++++++++++++++++++++++++-- 10 files changed, 124 insertions(+), 25 deletions(-) diff --git a/include/core/buffer.hpp b/include/core/buffer.hpp index fecf3e1..5f9ac61 100644 --- a/include/core/buffer.hpp +++ b/include/core/buffer.hpp @@ -28,13 +28,13 @@ namespace Vulkan::Core { /// @throws std::invalid_argument if the device or buffer size is invalid /// @throws ls::vulkan_error if object creation fails. /// - Buffer(const Device& device, uint32_t size, std::vector data, + Buffer(const Device& device, size_t size, std::vector data, VkBufferUsageFlags usage); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->buffer; } /// Get the size of the buffer. - [[nodiscard]] uint32_t getSize() const { return this->size; } + [[nodiscard]] size_t getSize() const { return this->size; } /// Check whether the object is valid. [[nodiscard]] bool isValid() const { return static_cast(this->buffer); } @@ -51,7 +51,7 @@ namespace Vulkan::Core { std::shared_ptr buffer; std::shared_ptr memory; - uint32_t size; + size_t size; }; } diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp index b2c2bf7..2bbb922 100644 --- a/include/core/descriptorset.hpp +++ b/include/core/descriptorset.hpp @@ -10,9 +10,9 @@ #include "core/shadermodule.hpp" #include "device.hpp" -#include #include +#include #include namespace Vulkan::Core { @@ -31,7 +31,7 @@ namespace Vulkan::Core { /// @param pool Descriptor pool to allocate from /// @param shaderModule Shader module to use for the descriptor set /// - /// @throws std::invalid_argument if the device or pool is invalid. + /// @throws std::invalid_argument if the device, pool or shader module is invalid. /// @throws ls::vulkan_error if object creation fails. /// DescriptorSet(const Device& device, diff --git a/include/core/fence.hpp b/include/core/fence.hpp index bb921bd..8900840 100644 --- a/include/core/fence.hpp +++ b/include/core/fence.hpp @@ -21,7 +21,7 @@ namespace Vulkan::Core { /// /// @param device Vulkan device /// - /// @throws std::invalid_argument if the device is null. + /// @throws std::invalid_argument if the device is invalid. /// @throws ls::vulkan_error if object creation fails. /// Fence(const Device& device); @@ -29,7 +29,6 @@ namespace Vulkan::Core { /// /// Reset the fence to an unsignaled state. /// - /// @throws std::logic_error if the fence is not valid. /// @throws ls::vulkan_error if resetting fails. /// void reset() const; @@ -40,7 +39,6 @@ namespace Vulkan::Core { /// @param timeout The timeout in nanoseconds, or UINT64_MAX for no timeout. /// @returns true if the fence signaled, false if it timed out. /// - /// @throws std::logic_error if the fence is not valid. /// @throws ls::vulkan_error if waiting fails. /// [[nodiscard]] bool wait(uint64_t timeout = UINT64_MAX) const; diff --git a/include/core/pipeline.hpp b/include/core/pipeline.hpp index 36c7a65..b51ec14 100644 --- a/include/core/pipeline.hpp +++ b/include/core/pipeline.hpp @@ -24,7 +24,7 @@ namespace Vulkan::Core { /// @param device Vulkan device /// @param shader Shader module to use for the pipeline. /// - /// @throws std::invalid_argument if the device is invalid. + /// @throws std::invalid_argument if the device or shader module is invalid. /// @throws ls::vulkan_error if object creation fails. /// Pipeline(const Device& device, const ShaderModule& shader); diff --git a/src/core/buffer.cpp b/src/core/buffer.cpp index 32e0e60..9ef9a78 100644 --- a/src/core/buffer.cpp +++ b/src/core/buffer.cpp @@ -6,7 +6,7 @@ using namespace Vulkan::Core; -Buffer::Buffer(const Device& device, uint32_t size, std::vector data, +Buffer::Buffer(const Device& device, size_t size, std::vector data, VkBufferUsageFlags usage) : size(size) { if (!device) throw std::invalid_argument("Invalid Vulkan device"); diff --git a/src/core/fence.cpp b/src/core/fence.cpp index a0ddd75..e864340 100644 --- a/src/core/fence.cpp +++ b/src/core/fence.cpp @@ -27,9 +27,6 @@ Fence::Fence(const Device& device) { } void Fence::reset() const { - if (!this->isValid()) - throw std::logic_error("Invalid fence"); - VkFence fenceHandle = this->handle(); auto res = vkResetFences(this->device, 1, &fenceHandle); if (res != VK_SUCCESS) @@ -37,9 +34,6 @@ void Fence::reset() const { } bool Fence::wait(uint64_t timeout) const { - if (!this->isValid()) - throw std::logic_error("Invalid fence"); - VkFence fenceHandle = this->handle(); auto res = vkWaitForFences(this->device, 1, &fenceHandle, VK_TRUE, timeout); if (res != VK_SUCCESS && res != VK_TIMEOUT) diff --git a/src/core/pipeline.cpp b/src/core/pipeline.cpp index 120c6f1..ac0cc31 100644 --- a/src/core/pipeline.cpp +++ b/src/core/pipeline.cpp @@ -4,8 +4,8 @@ using namespace Vulkan::Core; Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { - if (!device) - throw std::invalid_argument("Invalid Vulkan device"); + if (!device || !shader) + throw std::invalid_argument("Invalid Vulkan device or shader module"); // create pipeline layout VkDescriptorSetLayout shaderLayout = shader.getDescriptorSetLayout(); diff --git a/src/core/semaphore.cpp b/src/core/semaphore.cpp index 0da15cb..7008764 100644 --- a/src/core/semaphore.cpp +++ b/src/core/semaphore.cpp @@ -34,7 +34,7 @@ Semaphore::Semaphore(const Device& device, std::optional initial) { } void Semaphore::signal(uint64_t value) const { - if (!this->isValid() || !this->isTimeline) + if (!this->isTimeline) throw std::logic_error("Invalid timeline semaphore"); const VkSemaphoreSignalInfo signalInfo{ @@ -48,7 +48,7 @@ void Semaphore::signal(uint64_t value) const { } bool Semaphore::wait(uint64_t value, uint64_t timeout) const { - if (!this->isValid() || !this->isTimeline) + if (!this->isTimeline) throw std::logic_error("Invalid timeline semaphore"); VkSemaphore semaphore = this->handle(); diff --git a/src/core/shadermodule.cpp b/src/core/shadermodule.cpp index 703b609..4d777b3 100644 --- a/src/core/shadermodule.cpp +++ b/src/core/shadermodule.cpp @@ -2,7 +2,6 @@ #include "utils/exceptions.hpp" #include -#include using namespace Vulkan::Core; diff --git a/src/main.cpp b/src/main.cpp index 69ea2cc..6094deb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,31 +1,134 @@ +#include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/commandpool.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" #include "core/fence.hpp" +#include "core/image.hpp" #include "core/pipeline.hpp" +#include "core/sampler.hpp" #include "core/shadermodule.hpp" #include "device.hpp" #include "instance.hpp" +#include +#include #include #include +#include +#include using namespace Vulkan; +struct DataBuffer { + std::array inputOffset; + uint32_t firstIter; + uint32_t firstIterS; + uint32_t advancedColorKind; + uint32_t hdrSupport; + float resolutionInvScale; + float timestamp; + float uiThreshold; + std::array pad; +}; + +const static DataBuffer data{ + .inputOffset = { 0, 29 }, + .resolutionInvScale = 1.0F, + .timestamp = 0.5, + .uiThreshold = 0.1F +}; + int main() { // initialize Vulkan const Instance instance; const Device device(instance); - // prepare render pass - const Core::CommandPool commandPool(device); - - // prepare shader + // load shader const Core::ShaderModule computeShader(device, "shaders/downsample.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER}, { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER} }); + + // prepare render pass + const Core::CommandPool commandPool(device); + const Core::DescriptorPool descriptorPool(device); const Core::Pipeline computePipeline(device, computeShader); + const Core::DescriptorSet descriptorSet(device, descriptorPool, computeShader); + + // create shader inputs + const Core::Sampler sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); + + const std::vector images(1, Core::Image( + device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + )); + + const auto* dataBuffer = reinterpret_cast(&data); + std::vector dataVec(sizeof(DataBuffer)); + std::copy_n(dataBuffer, sizeof(DataBuffer), dataVec.data()); + const Core::Buffer buffer(device, dataVec.size(), dataVec, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + + // create shader outputs + const std::vector outputImages = { + Core::Image( + device, { 1280, 705 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ), + Core::Image( + device, { 640, 352 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ), + Core::Image( + device, { 320, 176 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ), + Core::Image( + device, { 160, 88 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ), + Core::Image( + device, { 80, 44 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ), + Core::Image( + device, { 40, 22 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ), + Core::Image( + device, { 20, 11 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ) + }; + + // load descriptor set + descriptorSet.update( + device, + { + {{ VK_DESCRIPTOR_TYPE_SAMPLER, sampler }}, + {{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, images[0] }}, + { + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[0] }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[1] }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[2] }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[3] }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[4] }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[5] }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[6] } + }, + {{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, buffer }} + } + ); // start pass Core::Fence fence(device); @@ -33,6 +136,11 @@ int main() { Core::CommandBuffer commandBuffer(device, commandPool); commandBuffer.begin(); + // render + computePipeline.bind(commandBuffer); + descriptorSet.bind(commandBuffer, computePipeline); + commandBuffer.dispatch(40, 23, 1); + // end pass commandBuffer.end(); From dc11a64c0a45d1afe463b89587fbe6a74fa034a7 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 20:20:02 +0200 Subject: [PATCH 016/253] general cleanup --- include/core/buffer.hpp | 6 ------ include/core/commandbuffer.hpp | 7 ------- include/core/commandpool.hpp | 6 ------ include/core/descriptorpool.hpp | 6 ------ include/core/descriptorset.hpp | 10 ---------- include/core/fence.hpp | 14 +++++-------- include/core/image.hpp | 6 ------ include/core/pipeline.hpp | 8 -------- include/core/sampler.hpp | 6 ------ include/core/semaphore.hpp | 13 ++++-------- include/core/shadermodule.hpp | 6 ------ include/device.hpp | 6 ------ include/instance.hpp | 5 ----- src/core/buffer.cpp | 21 ++++++++------------ src/core/commandbuffer.cpp | 25 +++++++---------------- src/core/commandpool.cpp | 7 ++----- src/core/descriptorpool.cpp | 7 ++----- src/core/descriptorset.cpp | 35 +++++---------------------------- src/core/fence.cpp | 16 ++++++--------- src/core/image.cpp | 33 +++++++++++++------------------ src/core/pipeline.cpp | 24 ++++++++-------------- src/core/sampler.cpp | 5 +---- src/core/semaphore.cpp | 14 +++++-------- src/core/shadermodule.cpp | 19 +++++++----------- src/device.cpp | 3 --- src/main.cpp | 2 +- 26 files changed, 75 insertions(+), 235 deletions(-) diff --git a/include/core/buffer.hpp b/include/core/buffer.hpp index 5f9ac61..073bb3f 100644 --- a/include/core/buffer.hpp +++ b/include/core/buffer.hpp @@ -25,7 +25,6 @@ namespace Vulkan::Core { /// @param data Initial data for the buffer. /// @param usage Usage flags for the buffer /// - /// @throws std::invalid_argument if the device or buffer size is invalid /// @throws ls::vulkan_error if object creation fails. /// Buffer(const Device& device, size_t size, std::vector data, @@ -36,11 +35,6 @@ namespace Vulkan::Core { /// Get the size of the buffer. [[nodiscard]] size_t getSize() const { return this->size; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->buffer); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - /// Trivially copyable, moveable and destructible Buffer(const Buffer&) noexcept = default; Buffer& operator=(const Buffer&) noexcept = default; diff --git a/include/core/commandbuffer.hpp b/include/core/commandbuffer.hpp index b01cf16..67f1c4e 100644 --- a/include/core/commandbuffer.hpp +++ b/include/core/commandbuffer.hpp @@ -41,7 +41,6 @@ namespace Vulkan::Core { /// @param device Vulkan device /// @param pool Vulkan command pool /// - /// @throws std::invalid_argument if the device or pool are invalid. /// @throws ls::vulkan_error if object creation fails. /// CommandBuffer(const Device& device, const CommandPool& pool); @@ -83,7 +82,6 @@ namespace Vulkan::Core { /// @param signalSemaphores Semaphores to signal after executing the command buffer /// @param signalSemaphoreValues Values for the semaphores to signal /// - /// @throws std::invalid_argument if the queue is null. /// @throws std::logic_error if the command buffer is not in Full state. /// @throws ls::vulkan_error if submission fails. /// @@ -98,11 +96,6 @@ namespace Vulkan::Core { /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->commandBuffer; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->commandBuffer); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - /// Trivially copyable, moveable and destructible CommandBuffer(const CommandBuffer&) noexcept = default; CommandBuffer& operator=(const CommandBuffer&) noexcept = default; diff --git a/include/core/commandpool.hpp b/include/core/commandpool.hpp index 3936983..7470d86 100644 --- a/include/core/commandpool.hpp +++ b/include/core/commandpool.hpp @@ -21,7 +21,6 @@ namespace Vulkan::Core { /// /// @param device Vulkan device /// - /// @throws std::invalid_argument if the device is invalid. /// @throws ls::vulkan_error if object creation fails. /// CommandPool(const Device& device); @@ -29,11 +28,6 @@ namespace Vulkan::Core { /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->commandPool; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->commandPool); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - /// Trivially copyable, moveable and destructible CommandPool(const CommandPool&) noexcept = default; CommandPool& operator=(const CommandPool&) noexcept = default; diff --git a/include/core/descriptorpool.hpp b/include/core/descriptorpool.hpp index 9b4d470..2227a66 100644 --- a/include/core/descriptorpool.hpp +++ b/include/core/descriptorpool.hpp @@ -21,7 +21,6 @@ namespace Vulkan::Core { /// /// @param device Vulkan device /// - /// @throws std::invalid_argument if the device is invalid. /// @throws ls::vulkan_error if object creation fails. /// DescriptorPool(const Device& device); @@ -29,11 +28,6 @@ namespace Vulkan::Core { /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->descriptorPool; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->descriptorPool); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - /// Trivially copyable, moveable and destructible DescriptorPool(const DescriptorPool&) noexcept = default; DescriptorPool& operator=(const DescriptorPool&) noexcept = default; diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp index 2bbb922..339ea54 100644 --- a/include/core/descriptorset.hpp +++ b/include/core/descriptorset.hpp @@ -31,7 +31,6 @@ namespace Vulkan::Core { /// @param pool Descriptor pool to allocate from /// @param shaderModule Shader module to use for the descriptor set /// - /// @throws std::invalid_argument if the device, pool or shader module is invalid. /// @throws ls::vulkan_error if object creation fails. /// DescriptorSet(const Device& device, @@ -45,8 +44,6 @@ namespace Vulkan::Core { /// @param device Vulkan device /// @param resources Resources to update the descriptor set with /// - /// @throws std::invalid_argument if the device or resources are invalid. - /// void update(const Device& device, const std::vector>& resources) const; @@ -56,18 +53,11 @@ namespace Vulkan::Core { /// @param commandBuffer Command buffer to bind the descriptor set to. /// @param pipeline Pipeline to bind the descriptor set to. /// - /// @throws std::invalid_argument if the command buffer or pipeline is invalid. - /// void bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const; /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->descriptorSet; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->descriptorSet); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - /// Trivially copyable, moveable and destructible DescriptorSet(const DescriptorSet&) noexcept = default; DescriptorSet& operator=(const DescriptorSet&) noexcept = default; diff --git a/include/core/fence.hpp b/include/core/fence.hpp index 8900840..e12fe06 100644 --- a/include/core/fence.hpp +++ b/include/core/fence.hpp @@ -21,7 +21,6 @@ namespace Vulkan::Core { /// /// @param device Vulkan device /// - /// @throws std::invalid_argument if the device is invalid. /// @throws ls::vulkan_error if object creation fails. /// Fence(const Device& device); @@ -29,28 +28,26 @@ namespace Vulkan::Core { /// /// Reset the fence to an unsignaled state. /// + /// @param device Vulkan device + /// /// @throws ls::vulkan_error if resetting fails. /// - void reset() const; + void reset(const 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 ls::vulkan_error if waiting fails. /// - [[nodiscard]] bool wait(uint64_t timeout = UINT64_MAX) const; + [[nodiscard]] bool wait(const Device& device, uint64_t timeout = UINT64_MAX) const; /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->fence; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->fence); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - // Trivially copyable, moveable and destructible Fence(const Fence&) noexcept = default; Fence& operator=(const Fence&) noexcept = default; @@ -59,7 +56,6 @@ namespace Vulkan::Core { ~Fence() = default; private: std::shared_ptr fence; - VkDevice device{}; }; } diff --git a/include/core/image.hpp b/include/core/image.hpp index 9fe3d52..254dd88 100644 --- a/include/core/image.hpp +++ b/include/core/image.hpp @@ -25,7 +25,6 @@ namespace Vulkan::Core { /// @param usage Usage flags for the image /// @param aspectFlags Aspect flags for the image view /// - /// @throws std::invalid_argument if the device is invalid. /// @throws ls::vulkan_error if object creation fails. /// Image(const Device& device, VkExtent2D extent, VkFormat format, @@ -40,11 +39,6 @@ namespace Vulkan::Core { /// Get the format of the image. [[nodiscard]] VkFormat getFormat() const { return this->format; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->image); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - /// Trivially copyable, moveable and destructible Image(const Image&) noexcept = default; Image& operator=(const Image&) noexcept = default; diff --git a/include/core/pipeline.hpp b/include/core/pipeline.hpp index b51ec14..b90fb94 100644 --- a/include/core/pipeline.hpp +++ b/include/core/pipeline.hpp @@ -24,7 +24,6 @@ namespace Vulkan::Core { /// @param device Vulkan device /// @param shader Shader module to use for the pipeline. /// - /// @throws std::invalid_argument if the device or shader module is invalid. /// @throws ls::vulkan_error if object creation fails. /// Pipeline(const Device& device, const ShaderModule& shader); @@ -34,8 +33,6 @@ namespace Vulkan::Core { /// /// @param commandBuffer Command buffer to bind the pipeline to. /// - /// @throws std::invalid_argument if the command buffer is invalid. - /// void bind(const CommandBuffer& commandBuffer) const; /// Get the Vulkan handle. @@ -43,11 +40,6 @@ namespace Vulkan::Core { /// Get the pipeline layout. [[nodiscard]] auto getLayout() const { return *this->layout; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->pipeline); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - /// Trivially copyable, moveable and destructible Pipeline(const Pipeline&) noexcept = default; Pipeline& operator=(const Pipeline&) noexcept = default; diff --git a/include/core/sampler.hpp b/include/core/sampler.hpp index 84ce43a..b1f9b55 100644 --- a/include/core/sampler.hpp +++ b/include/core/sampler.hpp @@ -22,7 +22,6 @@ namespace Vulkan::Core { /// @param device Vulkan device /// @param mode Address mode for the sampler. /// - /// @throws std::invalid_argument if the device is invalid. /// @throws ls::vulkan_error if object creation fails. /// Sampler(const Device& device, VkSamplerAddressMode mode); @@ -30,11 +29,6 @@ namespace Vulkan::Core { /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->sampler; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->sampler); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - /// Trivially copyable, moveable and destructible Sampler(const Sampler&) noexcept = default; Sampler& operator=(const Sampler&) noexcept = default; diff --git a/include/core/semaphore.hpp b/include/core/semaphore.hpp index 46c3683..30168b2 100644 --- a/include/core/semaphore.hpp +++ b/include/core/semaphore.hpp @@ -23,7 +23,6 @@ namespace Vulkan::Core { /// @param device Vulkan device /// @param initial Optional initial value for creating a timeline semaphore. /// - /// @throws std::invalid_argument if the device is null. /// @throws ls::vulkan_error if object creation fails. /// Semaphore(const Device& device, std::optional initial = std::nullopt); @@ -31,16 +30,18 @@ namespace Vulkan::Core { /// /// 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 ls::vulkan_error if signaling fails. /// - void signal(uint64_t value) const; + void signal(const 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. @@ -48,16 +49,11 @@ namespace Vulkan::Core { /// @throws std::logic_error if the semaphore is not a timeline semaphore. /// @throws ls::vulkan_error if waiting fails. /// - [[nodiscard]] bool wait(uint64_t value, uint64_t timeout = UINT64_MAX) const; + [[nodiscard]] bool wait(const Device& device, uint64_t value, uint64_t timeout = UINT64_MAX) const; /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->semaphore; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->semaphore); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - // Trivially copyable, moveable and destructible Semaphore(const Semaphore&) noexcept = default; Semaphore& operator=(const Semaphore&) noexcept = default; @@ -66,7 +62,6 @@ namespace Vulkan::Core { ~Semaphore() = default; private: std::shared_ptr semaphore; - VkDevice device{}; bool isTimeline{}; }; diff --git a/include/core/shadermodule.hpp b/include/core/shadermodule.hpp index 0cce6df..fcfcf5b 100644 --- a/include/core/shadermodule.hpp +++ b/include/core/shadermodule.hpp @@ -26,7 +26,6 @@ namespace Vulkan::Core { /// @param path Path to the shader file. /// @param descriptorTypes Descriptor types used in the shader. /// - /// @throws std::invalid_argument if the device is invalid. /// @throws std::system_error if the shader file cannot be opened or read. /// @throws ls::vulkan_error if object creation fails. /// @@ -38,11 +37,6 @@ namespace Vulkan::Core { /// Get the descriptor set layout. [[nodiscard]] auto getDescriptorSetLayout() const { return *this->descriptorSetLayout; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->shaderModule); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - /// Trivially copyable, moveable and destructible ShaderModule(const ShaderModule&) noexcept = default; ShaderModule& operator=(const ShaderModule&) noexcept = default; diff --git a/include/device.hpp b/include/device.hpp index 3e70c65..ef9ddcf 100644 --- a/include/device.hpp +++ b/include/device.hpp @@ -21,7 +21,6 @@ namespace Vulkan { /// /// @param instance Vulkan instance /// - /// @throws std::invalid_argument if the instance is invalid. /// @throws ls::vulkan_error if object creation fails. /// Device(const Vulkan::Instance& instance); @@ -35,11 +34,6 @@ namespace Vulkan { /// Get the compute queue. [[nodiscard]] VkQueue getComputeQueue() const { return this->computeQueue; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return static_cast(this->device); } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - // Trivially copyable, moveable and destructible Device(const Device&) noexcept = default; Device& operator=(const Device&) noexcept = default; diff --git a/include/instance.hpp b/include/instance.hpp index affd0c6..7b9d7ff 100644 --- a/include/instance.hpp +++ b/include/instance.hpp @@ -24,11 +24,6 @@ namespace Vulkan { /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return this->instance ? *this->instance : VK_NULL_HANDLE; } - /// Check whether the object is valid. - [[nodiscard]] bool isValid() const { return this->handle() != VK_NULL_HANDLE; } - /// if (obj) operator. Checks if the object is valid. - explicit operator bool() const { return this->isValid(); } - /// Trivially copyable, moveable and destructible Instance(const Instance&) noexcept = default; Instance& operator=(const Instance&) noexcept = default; diff --git a/src/core/buffer.cpp b/src/core/buffer.cpp index 9ef9a78..823a765 100644 --- a/src/core/buffer.cpp +++ b/src/core/buffer.cpp @@ -8,11 +8,6 @@ using namespace Vulkan::Core; Buffer::Buffer(const Device& device, size_t size, std::vector data, VkBufferUsageFlags usage) : size(size) { - if (!device) - throw std::invalid_argument("Invalid Vulkan device"); - if (size < data.size()) - throw std::invalid_argument("Invalid buffer size"); - // create buffer const VkBufferCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, @@ -62,6 +57,14 @@ Buffer::Buffer(const Device& device, size_t size, std::vector data, if (res != VK_SUCCESS) throw ls::vulkan_error(res, "Failed to bind memory to Vulkan buffer"); + // upload data to buffer + uint8_t* buf{}; + res = vkMapMemory(device.handle(), memoryHandle, 0, size, 0, reinterpret_cast(&buf)); + if (res != VK_SUCCESS || buf == nullptr) + throw ls::vulkan_error(res, "Failed to map memory for Vulkan buffer"); + std::copy_n(data.data(), std::min(size, data.size()), buf); + vkUnmapMemory(device.handle(), memoryHandle); + // store buffer and memory in shared ptr this->buffer = std::shared_ptr( new VkBuffer(bufferHandle), @@ -75,12 +78,4 @@ Buffer::Buffer(const Device& device, size_t size, std::vector data, vkFreeMemory(dev, *mem, nullptr); } ); - - // upload data to buffer - uint8_t* buf{}; - res = vkMapMemory(device.handle(), memoryHandle, 0, size, 0, reinterpret_cast(&buf)); - if (res != VK_SUCCESS || buf == nullptr) - throw ls::vulkan_error(res, "Failed to map memory for Vulkan buffer"); - std::copy_n(data.data(), size, buf); - vkUnmapMemory(device.handle(), memoryHandle); } diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp index fa50bab..5affc4e 100644 --- a/src/core/commandbuffer.cpp +++ b/src/core/commandbuffer.cpp @@ -4,11 +4,8 @@ using namespace Vulkan::Core; CommandBuffer::CommandBuffer(const Device& device, const CommandPool& pool) { - if (!device || !pool) - throw std::invalid_argument("Invalid Vulkan device or command pool"); - // create command buffer - const VkCommandBufferAllocateInfo desc = { + const VkCommandBufferAllocateInfo desc{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = pool.handle(), .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, @@ -67,14 +64,12 @@ void CommandBuffer::submit(VkQueue queue, std::optional fence, std::optional> waitSemaphoreValues, const std::vector& signalSemaphores, std::optional> signalSemaphoreValues) { - if (!queue) - throw std::invalid_argument("Invalid Vulkan queue"); if (*this->state != CommandBufferState::Full) throw std::logic_error("Command buffer is not in Full state"); const std::vector waitStages(waitSemaphores.size(), VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); - VkTimelineSemaphoreSubmitInfo timelineInfo = { + VkTimelineSemaphoreSubmitInfo timelineInfo{ .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO, }; if (waitSemaphoreValues.has_value()) { @@ -88,20 +83,14 @@ void CommandBuffer::submit(VkQueue queue, std::optional fence, timelineInfo.pSignalSemaphoreValues = signalSemaphoreValues->data(); } - std::vector waitSemaphoresHandles; - for (const auto& semaphore : waitSemaphores) { - if (!semaphore) - throw std::invalid_argument("Invalid Vulkan semaphore in waitSemaphores"); + std::vector waitSemaphoresHandles(waitSemaphores.size()); + for (const auto& semaphore : waitSemaphores) waitSemaphoresHandles.push_back(semaphore.handle()); - } - std::vector signalSemaphoresHandles; - for (const auto& semaphore : signalSemaphores) { - if (!semaphore) - throw std::invalid_argument("Invalid Vulkan semaphore in signalSemaphores"); + std::vector signalSemaphoresHandles(signalSemaphores.size()); + for (const auto& semaphore : signalSemaphores) signalSemaphoresHandles.push_back(semaphore.handle()); - } - const VkSubmitInfo submitInfo = { + const VkSubmitInfo submitInfo{ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .pNext = (waitSemaphoreValues.has_value() || signalSemaphoreValues.has_value()) ? &timelineInfo : nullptr, diff --git a/src/core/commandpool.cpp b/src/core/commandpool.cpp index 6061e37..e1fba1c 100644 --- a/src/core/commandpool.cpp +++ b/src/core/commandpool.cpp @@ -4,11 +4,8 @@ using namespace Vulkan::Core; CommandPool::CommandPool(const Device& device) { - if (!device) - throw std::invalid_argument("Invalid Vulkan device"); - // create command pool - const VkCommandPoolCreateInfo desc = { + const VkCommandPoolCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .queueFamilyIndex = device.getComputeFamilyIdx() }; @@ -17,7 +14,7 @@ CommandPool::CommandPool(const Device& device) { if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE) throw ls::vulkan_error(res, "Unable to create command pool"); - // store the command pool in a shared pointer + // store command pool in shared ptr this->commandPool = std::shared_ptr( new VkCommandPool(commandPoolHandle), [dev = device.handle()](VkCommandPool* commandPoolHandle) { diff --git a/src/core/descriptorpool.cpp b/src/core/descriptorpool.cpp index 340092e..fb49ecf 100644 --- a/src/core/descriptorpool.cpp +++ b/src/core/descriptorpool.cpp @@ -6,9 +6,6 @@ using namespace Vulkan::Core; DescriptorPool::DescriptorPool(const Device& device) { - if (!device) - throw std::invalid_argument("Invalid Vulkan device"); - // create descriptor pool const std::array pools{{ // arbitrary limits { .type = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = 4096 }, @@ -18,7 +15,7 @@ DescriptorPool::DescriptorPool(const Device& device) { }}; const VkDescriptorPoolCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .maxSets = 16384, // arbitrary limit + .maxSets = 16384, .poolSizeCount = static_cast(pools.size()), .pPoolSizes = pools.data() }; @@ -27,7 +24,7 @@ DescriptorPool::DescriptorPool(const Device& device) { if (res != VK_SUCCESS || poolHandle == VK_NULL_HANDLE) throw ls::vulkan_error(res, "Unable to create descriptor pool"); - /// store pool in shared ptr + // store pool in shared ptr this->descriptorPool = std::shared_ptr( new VkDescriptorPool(poolHandle), [dev = device.handle()](VkDescriptorPool* poolHandle) { diff --git a/src/core/descriptorset.cpp b/src/core/descriptorset.cpp index a483b36..36b6ae6 100644 --- a/src/core/descriptorset.cpp +++ b/src/core/descriptorset.cpp @@ -5,9 +5,6 @@ using namespace Vulkan::Core; DescriptorSet::DescriptorSet(const Device& device, DescriptorPool pool, const ShaderModule& shaderModule) { - if (!device || !pool) - throw std::invalid_argument("Invalid Vulkan device"); - // create descriptor set VkDescriptorSetLayout layout = shaderModule.getDescriptorSetLayout(); const VkDescriptorSetAllocateInfo desc{ @@ -21,7 +18,7 @@ DescriptorSet::DescriptorSet(const Device& device, if (res != VK_SUCCESS || descriptorSetHandle == VK_NULL_HANDLE) throw ls::vulkan_error(res, "Unable to allocate descriptor set"); - /// store descriptor set in shared ptr + /// store set in shared ptr this->descriptorSet = std::shared_ptr( new VkDescriptorSet(descriptorSetHandle), [dev = device.handle(), pool = std::move(pool)](VkDescriptorSet* setHandle) { @@ -32,9 +29,6 @@ DescriptorSet::DescriptorSet(const Device& device, void DescriptorSet::update(const Device& device, const std::vector>& resources) const { - if (!device) - throw std::invalid_argument("Invalid Vulkan device"); - std::vector writeDescriptorSets; uint32_t bindingIndex = 0; for (const auto& list : resources) { @@ -48,36 +42,23 @@ void DescriptorSet::update(const Device& device, }; if (std::holds_alternative(resource)) { - const auto& image = std::get(resource); - if (!image) - throw std::invalid_argument("Invalid image resource"); - const VkDescriptorImageInfo imageInfo{ - .imageView = image.getView(), + .imageView = std::get(resource).getView(), .imageLayout = VK_IMAGE_LAYOUT_GENERAL }; writeDesc.pImageInfo = &imageInfo; } else if (std::holds_alternative(resource)) { - const auto& sampler = std::get(resource); - if (!sampler) - throw std::invalid_argument("Invalid sampler resource"); - const VkDescriptorImageInfo imageInfo{ - .sampler = sampler.handle() + .sampler = std::get(resource).handle() }; writeDesc.pImageInfo = &imageInfo; } else if (std::holds_alternative(resource)) { const auto& buffer = std::get(resource); - if (!buffer) - throw std::invalid_argument("Invalid buffer resource"); - const VkDescriptorBufferInfo bufferInfo{ .buffer = buffer.handle(), .range = buffer.getSize() }; writeDesc.pBufferInfo = &bufferInfo; - } else { - throw std::invalid_argument("Unsupported resource type"); } writeDescriptorSets.push_back(writeDesc); @@ -86,18 +67,12 @@ void DescriptorSet::update(const Device& device, vkUpdateDescriptorSets(device.handle(), static_cast(writeDescriptorSets.size()), - writeDescriptorSets.data(), - 0, nullptr); + writeDescriptorSets.data(), 0, nullptr); } void DescriptorSet::bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const { - if (!commandBuffer) - throw std::invalid_argument("Invalid command buffer"); - - // bind descriptor set VkDescriptorSet descriptorSetHandle = this->handle(); vkCmdBindDescriptorSets(commandBuffer.handle(), VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.getLayout(), - 0, 1, &descriptorSetHandle, - 0, nullptr); + 0, 1, &descriptorSetHandle, 0, nullptr); } diff --git a/src/core/fence.cpp b/src/core/fence.cpp index e864340..3108ede 100644 --- a/src/core/fence.cpp +++ b/src/core/fence.cpp @@ -4,11 +4,8 @@ using namespace Vulkan::Core; Fence::Fence(const Device& device) { - if (!device) - throw std::invalid_argument("Invalid Vulkan device"); - - // create fence - const VkFenceCreateInfo desc = { + // create fence + const VkFenceCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; VkFence fenceHandle{}; @@ -17,7 +14,6 @@ Fence::Fence(const Device& device) { throw ls::vulkan_error(res, "Unable to create fence"); // store fence in shared ptr - this->device = device.handle(); this->fence = std::shared_ptr( new VkFence(fenceHandle), [dev = device.handle()](VkFence* fenceHandle) { @@ -26,16 +22,16 @@ Fence::Fence(const Device& device) { ); } -void Fence::reset() const { +void Fence::reset(const Device& device) const { VkFence fenceHandle = this->handle(); - auto res = vkResetFences(this->device, 1, &fenceHandle); + auto res = vkResetFences(device.handle(), 1, &fenceHandle); if (res != VK_SUCCESS) throw ls::vulkan_error(res, "Unable to reset fence"); } -bool Fence::wait(uint64_t timeout) const { +bool Fence::wait(const Device& device, uint64_t timeout) const { VkFence fenceHandle = this->handle(); - auto res = vkWaitForFences(this->device, 1, &fenceHandle, VK_TRUE, timeout); + auto res = vkWaitForFences(device.handle(), 1, &fenceHandle, VK_TRUE, timeout); if (res != VK_SUCCESS && res != VK_TIMEOUT) throw ls::vulkan_error(res, "Unable to wait for fence"); diff --git a/src/core/image.cpp b/src/core/image.cpp index 5c8eb38..a976f3b 100644 --- a/src/core/image.cpp +++ b/src/core/image.cpp @@ -8,9 +8,6 @@ using namespace Vulkan::Core; Image::Image(const Device& device, VkExtent2D extent, VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspectFlags) : extent(extent), format(format) { - if (!device) - throw std::invalid_argument("Invalid Vulkan device"); - // create image const VkImageCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, @@ -68,24 +65,10 @@ Image::Image(const Device& device, VkExtent2D extent, VkFormat format, if (res != VK_SUCCESS) throw ls::vulkan_error(res, "Failed to bind memory to Vulkan image"); - // store image and memory in shared ptr - this->image = std::shared_ptr( - new VkImage(imageHandle), - [dev = device.handle()](VkImage* img) { - vkDestroyImage(dev, *img, nullptr); - } - ); - this->memory = std::shared_ptr( - new VkDeviceMemory(memoryHandle), - [dev = device.handle()](VkDeviceMemory* mem) { - vkFreeMemory(dev, *mem, nullptr); - } - ); - // create image view const VkImageViewCreateInfo viewDesc{ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = *this->image, + .image = imageHandle, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = format, .components = { @@ -106,7 +89,19 @@ Image::Image(const Device& device, VkExtent2D extent, VkFormat format, if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE) throw ls::vulkan_error(res, "Failed to create image view"); - // store image view in shared ptr + // store objects in shared ptr + this->image = std::shared_ptr( + new VkImage(imageHandle), + [dev = device.handle()](VkImage* img) { + vkDestroyImage(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device.handle()](VkDeviceMemory* mem) { + vkFreeMemory(dev, *mem, nullptr); + } + ); this->view = std::shared_ptr( new VkImageView(viewHandle), [dev = device.handle()](VkImageView* imgView) { diff --git a/src/core/pipeline.cpp b/src/core/pipeline.cpp index ac0cc31..e20b040 100644 --- a/src/core/pipeline.cpp +++ b/src/core/pipeline.cpp @@ -4,9 +4,6 @@ using namespace Vulkan::Core; Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { - if (!device || !shader) - throw std::invalid_argument("Invalid Vulkan device or shader module"); - // create pipeline layout VkDescriptorSetLayout shaderLayout = shader.getDescriptorSetLayout(); const VkPipelineLayoutCreateInfo layoutDesc{ @@ -19,14 +16,6 @@ Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { if (res != VK_SUCCESS || !layoutHandle) throw ls::vulkan_error(res, "Failed to create pipeline layout"); - // store layout in shared ptr - this->layout = std::shared_ptr( - new VkPipelineLayout(layoutHandle), - [dev = device.handle()](VkPipelineLayout* layout) { - vkDestroyPipelineLayout(dev, *layout, nullptr); - } - ); - // create pipeline const VkPipelineShaderStageCreateInfo shaderStageInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, @@ -45,7 +34,13 @@ Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { if (res != VK_SUCCESS || !pipelineHandle) throw ls::vulkan_error(res, "Failed to create compute pipeline"); - // store pipeline in shared ptr + // store layout and pipeline in shared ptr + this->layout = std::shared_ptr( + new VkPipelineLayout(layoutHandle), + [dev = device.handle()](VkPipelineLayout* layout) { + vkDestroyPipelineLayout(dev, *layout, nullptr); + } + ); this->pipeline = std::shared_ptr( new VkPipeline(pipelineHandle), [dev = device.handle()](VkPipeline* pipeline) { @@ -55,8 +50,5 @@ Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { } void Pipeline::bind(const CommandBuffer& commandBuffer) const { - if (!commandBuffer) - throw std::invalid_argument("Invalid command buffer"); - - vkCmdBindPipeline(commandBuffer.handle(), VK_PIPELINE_BIND_POINT_COMPUTE, *this->pipeline); + vkCmdBindPipeline(commandBuffer.handle(), VK_PIPELINE_BIND_POINT_COMPUTE, *this->pipeline); } diff --git a/src/core/sampler.cpp b/src/core/sampler.cpp index e817e54..2f7d5a0 100644 --- a/src/core/sampler.cpp +++ b/src/core/sampler.cpp @@ -4,9 +4,6 @@ using namespace Vulkan::Core; Sampler::Sampler(const Device& device, VkSamplerAddressMode mode) { - if (!device) - throw std::invalid_argument("Invalid Vulkan device"); - // create sampler const VkSamplerCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, @@ -23,7 +20,7 @@ Sampler::Sampler(const Device& device, VkSamplerAddressMode mode) { if (res != VK_SUCCESS || samplerHandle == VK_NULL_HANDLE) throw ls::vulkan_error(res, "Unable to create sampler"); - // store the sampler in a shared pointer + // store sampler in shared ptr this->sampler = std::shared_ptr( new VkSampler(samplerHandle), [dev = device.handle()](VkSampler* samplerHandle) { diff --git a/src/core/semaphore.cpp b/src/core/semaphore.cpp index 7008764..510a1e9 100644 --- a/src/core/semaphore.cpp +++ b/src/core/semaphore.cpp @@ -4,16 +4,13 @@ using namespace Vulkan::Core; Semaphore::Semaphore(const Device& device, std::optional initial) { - if (!device) - throw std::invalid_argument("Invalid Vulkan device"); - // 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 = { + const VkSemaphoreCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = initial.has_value() ? &typeInfo : nullptr, }; @@ -24,7 +21,6 @@ Semaphore::Semaphore(const Device& device, std::optional initial) { // store semaphore in shared ptr this->isTimeline = initial.has_value(); - this->device = device.handle(); this->semaphore = std::shared_ptr( new VkSemaphore(semaphoreHandle), [dev = device.handle()](VkSemaphore* semaphoreHandle) { @@ -33,7 +29,7 @@ Semaphore::Semaphore(const Device& device, std::optional initial) { ); } -void Semaphore::signal(uint64_t value) const { +void Semaphore::signal(const Device& device, uint64_t value) const { if (!this->isTimeline) throw std::logic_error("Invalid timeline semaphore"); @@ -42,12 +38,12 @@ void Semaphore::signal(uint64_t value) const { .semaphore = this->handle(), .value = value }; - auto res = vkSignalSemaphore(this->device, &signalInfo); + auto res = vkSignalSemaphore(device.handle(), &signalInfo); if (res != VK_SUCCESS) throw ls::vulkan_error(res, "Unable to signal semaphore"); } -bool Semaphore::wait(uint64_t value, uint64_t timeout) const { +bool Semaphore::wait(const Device& device, uint64_t value, uint64_t timeout) const { if (!this->isTimeline) throw std::logic_error("Invalid timeline semaphore"); @@ -58,7 +54,7 @@ bool Semaphore::wait(uint64_t value, uint64_t timeout) const { .pSemaphores = &semaphore, .pValues = &value }; - auto res = vkWaitSemaphores(this->device, &waitInfo, timeout); + auto res = vkWaitSemaphores(device.handle(), &waitInfo, timeout); if (res != VK_SUCCESS && res != VK_TIMEOUT) throw ls::vulkan_error(res, "Unable to wait for semaphore"); diff --git a/src/core/shadermodule.cpp b/src/core/shadermodule.cpp index 4d777b3..006832d 100644 --- a/src/core/shadermodule.cpp +++ b/src/core/shadermodule.cpp @@ -7,9 +7,6 @@ using namespace Vulkan::Core; ShaderModule::ShaderModule(const Device& device, const std::string& path, const std::vector>& descriptorTypes) { - if (!device) - throw std::invalid_argument("Invalid Vulkan device"); - // read shader bytecode std::ifstream file(path, std::ios::ate | std::ios::binary); if (!file) @@ -36,14 +33,6 @@ ShaderModule::ShaderModule(const Device& device, const std::string& path, if (res != VK_SUCCESS || !shaderModuleHandle) throw ls::vulkan_error(res, "Failed to create shader module"); - // store shader module in shared ptr - this->shaderModule = std::shared_ptr( - new VkShaderModule(shaderModuleHandle), - [dev = device.handle()](VkShaderModule* shaderModuleHandle) { - vkDestroyShaderModule(dev, *shaderModuleHandle, nullptr); - } - ); - // create descriptor set layout std::vector layoutBindings; size_t bindIdx = 0; @@ -66,7 +55,13 @@ ShaderModule::ShaderModule(const Device& device, const std::string& path, if (res != VK_SUCCESS || !descriptorSetLayout) throw ls::vulkan_error(res, "Failed to create descriptor set layout"); - // store layout in shared ptr + // store module and layout in shared ptr + this->shaderModule = std::shared_ptr( + new VkShaderModule(shaderModuleHandle), + [dev = device.handle()](VkShaderModule* shaderModuleHandle) { + vkDestroyShaderModule(dev, *shaderModuleHandle, nullptr); + } + ); this->descriptorSetLayout = std::shared_ptr( new VkDescriptorSetLayout(descriptorSetLayout), [dev = device.handle()](VkDescriptorSetLayout* layout) { diff --git a/src/device.cpp b/src/device.cpp index d8e1bcf..894322a 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -7,9 +7,6 @@ using namespace Vulkan; Device::Device(const Instance& instance) { - if (!instance) - throw std::invalid_argument("Invalid Vulkan instance"); - // get all physical devices uint32_t deviceCount{}; auto res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, nullptr); diff --git a/src/main.cpp b/src/main.cpp index 6094deb..39ec734 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -145,7 +145,7 @@ int main() { commandBuffer.end(); commandBuffer.submit(device.getComputeQueue(), fence); - assert(fence.wait() && "Synchronization fence timed out"); + assert(fence.wait(device) && "Synchronization fence timed out"); std::cerr << "Application finished" << '\n'; return 0; From f724229ae4422b8db1568c85acf90ab70472bed6 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 22:34:36 +0200 Subject: [PATCH 017/253] bugfixes and pipeline barriers --- include/core/image.hpp | 3 ++ include/utils/memorybarriers.hpp | 40 ++++++++++++++++++ src/core/descriptorpool.cpp | 1 + src/core/image.cpp | 2 +- src/device.cpp | 12 ++++-- src/main.cpp | 68 ++++++++++-------------------- src/utils/memorybarriers.cpp | 71 ++++++++++++++++++++++++++++++++ 7 files changed, 146 insertions(+), 51 deletions(-) create mode 100644 include/utils/memorybarriers.hpp create mode 100644 src/utils/memorybarriers.cpp diff --git a/include/core/image.hpp b/include/core/image.hpp index 254dd88..775e492 100644 --- a/include/core/image.hpp +++ b/include/core/image.hpp @@ -38,6 +38,8 @@ namespace Vulkan::Core { [[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; @@ -52,6 +54,7 @@ namespace Vulkan::Core { VkExtent2D extent; VkFormat format; + VkImageAspectFlags aspectFlags; }; } diff --git a/include/utils/memorybarriers.hpp b/include/utils/memorybarriers.hpp new file mode 100644 index 0000000..412b169 --- /dev/null +++ b/include/utils/memorybarriers.hpp @@ -0,0 +1,40 @@ +#ifndef BARRIERS_HPP +#define BARRIERS_HPP + +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include + +namespace Barriers { + + /// + /// Insert memory barriers for images in a command buffer. + /// + /// @param buffer Command buffer to insert barriers into + /// @param r2wImages Images that are being read and will be written to + /// @param w2rImages Images that are being written to and will be read from + /// @param srcLayout Optional source layout for the images, defaults to VK_IMAGE_LAYOUT_GENERAL + /// + /// @throws std::logic_error if the command buffer is not in Recording state + /// + void insertBarrier( + const Vulkan::Core::CommandBuffer& buffer, + const std::vector& r2wImages, + const std::vector& w2rImages, + std::optional srcLayout = std::nullopt); + + /// + /// Insert a global memory barrier in a command buffer. + /// + /// @param buffer Command buffer to insert the barrier into + /// + /// @throws std::logic_error if the command buffer is not in Recording state + /// + void insertGlobalBarrier(const Vulkan::Core::CommandBuffer& buffer); + +}; + +#endif // BARRIERS_HPP diff --git a/src/core/descriptorpool.cpp b/src/core/descriptorpool.cpp index fb49ecf..1013bc9 100644 --- a/src/core/descriptorpool.cpp +++ b/src/core/descriptorpool.cpp @@ -15,6 +15,7 @@ DescriptorPool::DescriptorPool(const Device& device) { }}; const VkDescriptorPoolCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, .maxSets = 16384, .poolSizeCount = static_cast(pools.size()), .pPoolSizes = pools.data() diff --git a/src/core/image.cpp b/src/core/image.cpp index a976f3b..b6d4929 100644 --- a/src/core/image.cpp +++ b/src/core/image.cpp @@ -7,7 +7,7 @@ using namespace Vulkan::Core; Image::Image(const Device& device, VkExtent2D extent, VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspectFlags) - : extent(extent), format(format) { + : extent(extent), format(format), aspectFlags(aspectFlags) { // create image const VkImageCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, diff --git a/src/device.cpp b/src/device.cpp index 894322a..c84cb4c 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -3,6 +3,7 @@ #include #include +#include using namespace Vulkan; @@ -49,10 +50,15 @@ Device::Device(const Instance& instance) { // create logical device const float queuePriority{1.0F}; // highest priority - const VkPhysicalDeviceVulkan12Features features{ + VkPhysicalDeviceVulkan13Features features13{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, + .synchronization2 = VK_TRUE + }; + const VkPhysicalDeviceVulkan12Features features12{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, + .pNext = &features13, .timelineSemaphore = VK_TRUE, - .vulkanMemoryModel = VK_TRUE, + .vulkanMemoryModel = VK_TRUE }; const VkDeviceQueueCreateInfo computeQueueDesc{ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, @@ -62,7 +68,7 @@ Device::Device(const Instance& instance) { }; const VkDeviceCreateInfo deviceCreateInfo{ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .pNext = &features, + .pNext = &features12, .queueCreateInfoCount = 1, .pQueueCreateInfos = &computeQueueDesc }; diff --git a/src/main.cpp b/src/main.cpp index 39ec734..d84938a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "core/shadermodule.hpp" #include "device.hpp" #include "instance.hpp" +#include "utils/memorybarriers.hpp" #include #include @@ -40,27 +41,22 @@ const static DataBuffer data{ }; int main() { - // initialize Vulkan + // initialize application const Instance instance; const Device device(instance); + const Core::DescriptorPool descriptorPool(device); + const Core::CommandPool commandPool(device); - // load shader const Core::ShaderModule computeShader(device, "shaders/downsample.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER}, { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER} }); - - // prepare render pass - const Core::CommandPool commandPool(device); - const Core::DescriptorPool descriptorPool(device); const Core::Pipeline computePipeline(device, computeShader); - const Core::DescriptorSet descriptorSet(device, descriptorPool, computeShader); - // create shader inputs const Core::Sampler sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); - const std::vector images(1, Core::Image( + const std::vector inputImages(1, Core::Image( device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT, VK_IMAGE_ASPECT_COLOR_BIT @@ -72,51 +68,22 @@ int main() { const Core::Buffer buffer(device, dataVec.size(), dataVec, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); - // create shader outputs - const std::vector outputImages = { - Core::Image( - device, { 1280, 705 }, VK_FORMAT_R8G8B8A8_UNORM, + std::vector outputImages; + outputImages.reserve(7); + for (size_t i = 0; i < 7; ++i) + outputImages.emplace_back(device, + VkExtent2D { .width = 2560U >> i, .height = 1411U >> i }, + VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ), - Core::Image( - device, { 640, 352 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ), - Core::Image( - device, { 320, 176 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ), - Core::Image( - device, { 160, 88 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ), - Core::Image( - device, { 80, 44 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ), - Core::Image( - device, { 40, 22 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ), - Core::Image( - device, { 20, 11 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ) - }; + VK_IMAGE_ASPECT_COLOR_BIT); // load descriptor set + const Core::DescriptorSet descriptorSet(device, descriptorPool, computeShader); descriptorSet.update( device, { {{ VK_DESCRIPTOR_TYPE_SAMPLER, sampler }}, - {{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, images[0] }}, + {{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inputImages[0] }}, { { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[0] }, { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[1] }, @@ -137,6 +104,13 @@ int main() { commandBuffer.begin(); // render + Barriers::insertBarrier( + commandBuffer, + inputImages, + outputImages, + VK_IMAGE_LAYOUT_UNDEFINED + ); + computePipeline.bind(commandBuffer); descriptorSet.bind(commandBuffer, computePipeline); commandBuffer.dispatch(40, 23, 1); diff --git a/src/utils/memorybarriers.cpp b/src/utils/memorybarriers.cpp new file mode 100644 index 0000000..efc827c --- /dev/null +++ b/src/utils/memorybarriers.cpp @@ -0,0 +1,71 @@ +#include "utils/memorybarriers.hpp" + +#include + +using namespace Barriers; + +void Barriers::insertBarrier( + const Vulkan::Core::CommandBuffer& buffer, + const std::vector& r2wImages, + const std::vector& w2rImages, + std::optional srcLayout) { + std::vector barriers(r2wImages.size() + w2rImages.size()); + + size_t index = 0; + for (const auto& image : r2wImages) { + barriers[index++] = { + .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 = srcLayout.value_or(VK_IMAGE_LAYOUT_GENERAL), + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .image = image.handle(), + .subresourceRange = { + .aspectMask = image.getAspectFlags(), + .levelCount = 1, + .layerCount = 1 + } + }; + } + for (const auto& image : w2rImages) { + barriers[index++] = { + .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 = srcLayout.value_or(VK_IMAGE_LAYOUT_GENERAL), + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .image = image.handle(), + .subresourceRange = { + .aspectMask = image.getAspectFlags(), + .levelCount = 1, + .layerCount = 1 + } + }; + } + const VkDependencyInfo dependencyInfo = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .imageMemoryBarrierCount = static_cast(barriers.size()), + .pImageMemoryBarriers = barriers.data() + }; + vkCmdPipelineBarrier2(buffer.handle(), &dependencyInfo); +} + +void Barriers::insertGlobalBarrier(const Vulkan::Core::CommandBuffer& buffer) { + const VkMemoryBarrier2 barrier = { + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, + .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .srcAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT + }; + const VkDependencyInfo dependencyInfo = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .memoryBarrierCount = 1, + .pMemoryBarriers = &barrier + }; + vkCmdPipelineBarrier2(buffer.handle(), &dependencyInfo); +} From 80f27ad188532a8ec9ea2dc2bb78a9b9a765acf2 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 22:43:54 +0200 Subject: [PATCH 018/253] less copies for buffer init --- include/core/buffer.hpp | 13 ++++++++----- src/core/buffer.cpp | 9 ++++----- src/main.cpp | 6 +----- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/include/core/buffer.hpp b/include/core/buffer.hpp index 073bb3f..5012501 100644 --- a/include/core/buffer.hpp +++ b/include/core/buffer.hpp @@ -5,7 +5,6 @@ #include -#include #include namespace Vulkan::Core { @@ -21,14 +20,16 @@ namespace Vulkan::Core { /// Create the buffer. /// /// @param device Vulkan device - /// @param size Size of the buffer in bytes. - /// @param data Initial data for the buffer. + /// @param data Initial data for the buffer, also specifies the size of the buffer. /// @param usage Usage flags for the buffer /// /// @throws ls::vulkan_error if object creation fails. /// - Buffer(const Device& device, size_t size, std::vector data, - VkBufferUsageFlags usage); + template + Buffer(const Device& device, const T& data, VkBufferUsageFlags usage) + : size(sizeof(T)) { + construct(device, reinterpret_cast(&data), usage); + } /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->buffer; } @@ -42,6 +43,8 @@ namespace Vulkan::Core { Buffer& operator=(Buffer&&) noexcept = default; ~Buffer() = default; private: + void construct(const Device& device, const void* data, VkBufferUsageFlags usage); + std::shared_ptr buffer; std::shared_ptr memory; diff --git a/src/core/buffer.cpp b/src/core/buffer.cpp index 823a765..e8d5f7c 100644 --- a/src/core/buffer.cpp +++ b/src/core/buffer.cpp @@ -6,12 +6,11 @@ using namespace Vulkan::Core; -Buffer::Buffer(const Device& device, size_t size, std::vector data, - VkBufferUsageFlags usage) : size(size) { +void Buffer::construct(const Device& device, const void* data, VkBufferUsageFlags usage) { // create buffer const VkBufferCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .size = size, + .size = this->size, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE }; @@ -59,10 +58,10 @@ Buffer::Buffer(const Device& device, size_t size, std::vector data, // upload data to buffer uint8_t* buf{}; - res = vkMapMemory(device.handle(), memoryHandle, 0, size, 0, reinterpret_cast(&buf)); + res = vkMapMemory(device.handle(), memoryHandle, 0, this->size, 0, reinterpret_cast(&buf)); if (res != VK_SUCCESS || buf == nullptr) throw ls::vulkan_error(res, "Failed to map memory for Vulkan buffer"); - std::copy_n(data.data(), std::min(size, data.size()), buf); + std::copy_n(reinterpret_cast(data), this->size, buf); vkUnmapMemory(device.handle(), memoryHandle); // store buffer and memory in shared ptr diff --git a/src/main.cpp b/src/main.cpp index d84938a..c4cfa31 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -62,11 +62,7 @@ int main() { VK_IMAGE_ASPECT_COLOR_BIT )); - const auto* dataBuffer = reinterpret_cast(&data); - std::vector dataVec(sizeof(DataBuffer)); - std::copy_n(dataBuffer, sizeof(DataBuffer), dataVec.data()); - const Core::Buffer buffer(device, dataVec.size(), dataVec, - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + const Core::Buffer buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); std::vector outputImages; outputImages.reserve(7); From b463d71f69bec4ac21e82f2d29be2e31a3752e94 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 29 Jun 2025 23:31:34 +0200 Subject: [PATCH 019/253] builder for descriptor set updates --- include/core/descriptorset.hpp | 43 +++++++++++-- src/core/descriptorset.cpp | 107 ++++++++++++++++++++------------- src/main.cpp | 24 ++------ 3 files changed, 111 insertions(+), 63 deletions(-) diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp index 339ea54..89c33f7 100644 --- a/include/core/descriptorset.hpp +++ b/include/core/descriptorset.hpp @@ -17,6 +17,8 @@ namespace Vulkan::Core { + class DescriptorSetUpdateBuilder; + /// /// C++ wrapper class for a Vulkan descriptor set. /// @@ -36,16 +38,18 @@ namespace Vulkan::Core { DescriptorSet(const Device& device, DescriptorPool pool, const ShaderModule& shaderModule); - using ResourcePair = std::pair>; + using ResourceList = std::variant< + std::pair&>, + std::pair&>, + std::pair&> + >; /// /// Update the descriptor set with resources. /// /// @param device Vulkan device - /// @param resources Resources to update the descriptor set with /// - void update(const Device& device, - const std::vector>& resources) const; + [[nodiscard]] DescriptorSetUpdateBuilder update(const Device& device) const; /// /// Bind a descriptor set to a command buffer. @@ -68,6 +72,37 @@ namespace Vulkan::Core { std::shared_ptr descriptorSet; }; + /// + /// Builder class for updating a descriptor set. + /// + class DescriptorSetUpdateBuilder { + friend class DescriptorSet; + public: + /// Add a resource to the descriptor set update. + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Image& image); + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Sampler& sampler); + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Buffer& buffer); + + /// Add a list of resources to the descriptor set update. + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& images) { + for (const auto& image : images) this->add(type, image); return *this; } + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& samplers) { + for (const auto& sampler : samplers) this->add(type, sampler); return *this; } + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& buffers) { + for (const auto& buffer : buffers) this->add(type, buffer); return *this; } + + /// Finish building the descriptor set update. + void build() const; + private: + const DescriptorSet* descriptorSet; + const Device* device; + + DescriptorSetUpdateBuilder(const DescriptorSet& descriptorSet, const Device& device) + : descriptorSet(&descriptorSet), device(&device) {} + + std::vector entries; + }; + } #endif // DESCRIPTORSET_HPP diff --git a/src/core/descriptorset.cpp b/src/core/descriptorset.cpp index 36b6ae6..de752c0 100644 --- a/src/core/descriptorset.cpp +++ b/src/core/descriptorset.cpp @@ -27,47 +27,8 @@ DescriptorSet::DescriptorSet(const Device& device, ); } -void DescriptorSet::update(const Device& device, - const std::vector>& resources) const { - std::vector writeDescriptorSets; - uint32_t bindingIndex = 0; - for (const auto& list : resources) { - for (const auto& [type, resource] : list) { - VkWriteDescriptorSet writeDesc{ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = this->handle(), - .dstBinding = bindingIndex++, - .descriptorCount = 1, - .descriptorType = type, - }; - - if (std::holds_alternative(resource)) { - const VkDescriptorImageInfo imageInfo{ - .imageView = std::get(resource).getView(), - .imageLayout = VK_IMAGE_LAYOUT_GENERAL - }; - writeDesc.pImageInfo = &imageInfo; - } else if (std::holds_alternative(resource)) { - const VkDescriptorImageInfo imageInfo{ - .sampler = std::get(resource).handle() - }; - writeDesc.pImageInfo = &imageInfo; - } else if (std::holds_alternative(resource)) { - const auto& buffer = std::get(resource); - const VkDescriptorBufferInfo bufferInfo{ - .buffer = buffer.handle(), - .range = buffer.getSize() - }; - writeDesc.pBufferInfo = &bufferInfo; - } - - writeDescriptorSets.push_back(writeDesc); - } - } - - vkUpdateDescriptorSets(device.handle(), - static_cast(writeDescriptorSets.size()), - writeDescriptorSets.data(), 0, nullptr); +DescriptorSetUpdateBuilder DescriptorSet::update(const Device& device) const { + return { *this, device }; } void DescriptorSet::bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const { @@ -76,3 +37,67 @@ void DescriptorSet::bind(const CommandBuffer& commandBuffer, const Pipeline& pip VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.getLayout(), 0, 1, &descriptorSetHandle, 0, nullptr); } + +// updater class + +DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Image& image) { + this->entries.push_back({ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = this->descriptorSet->handle(), + .dstBinding = static_cast(this->entries.size()), + .descriptorCount = 1, + .descriptorType = type, + .pImageInfo = new VkDescriptorImageInfo { + .imageView = image.getView(), + .imageLayout = VK_IMAGE_LAYOUT_GENERAL + }, + .pBufferInfo = nullptr + }); + return *this; +} + +DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Sampler& sampler) { + this->entries.push_back({ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = this->descriptorSet->handle(), + .dstBinding = static_cast(this->entries.size()), + .descriptorCount = 1, + .descriptorType = type, + .pImageInfo = new VkDescriptorImageInfo { + .sampler = sampler.handle(), + }, + .pBufferInfo = nullptr + }); + return *this; +} + +DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Buffer& buffer) { + this->entries.push_back({ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = this->descriptorSet->handle(), + .dstBinding = static_cast(this->entries.size()), + .descriptorCount = 1, + .descriptorType = type, + .pImageInfo = nullptr, + .pBufferInfo = new VkDescriptorBufferInfo { + .buffer = buffer.handle(), + .range = buffer.getSize() + } + }); + return *this; +} + +void DescriptorSetUpdateBuilder::build() const { + if (this->entries.empty()) return; + + vkUpdateDescriptorSets(this->device->handle(), + static_cast(this->entries.size()), + this->entries.data(), 0, nullptr); + + // NOLINTBEGIN + for (const auto& entry : this->entries) { + delete entry.pImageInfo; + delete entry.pBufferInfo; + } + // NOLINTEND +} diff --git a/src/main.cpp b/src/main.cpp index c4cfa31..b3df478 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,7 +12,6 @@ #include "instance.hpp" #include "utils/memorybarriers.hpp" -#include #include #include #include @@ -75,23 +74,12 @@ int main() { // load descriptor set const Core::DescriptorSet descriptorSet(device, descriptorPool, computeShader); - descriptorSet.update( - device, - { - {{ VK_DESCRIPTOR_TYPE_SAMPLER, sampler }}, - {{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inputImages[0] }}, - { - { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[0] }, - { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[1] }, - { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[2] }, - { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[3] }, - { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[4] }, - { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[5] }, - { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages[6] } - }, - {{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, buffer }} - } - ); + descriptorSet.update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inputImages) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, buffer) + .build(); // start pass Core::Fence fence(device); From 15c2319ab6a6162d86738edb8279b4b5ed1c1f8e Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 00:33:02 +0200 Subject: [PATCH 020/253] more utility functions and cleanup --- include/core/buffer.hpp | 4 +++- include/core/commandbuffer.hpp | 4 +++- include/core/commandpool.hpp | 2 ++ include/core/descriptorpool.hpp | 2 ++ include/core/descriptorset.hpp | 22 ++++++++++++++-------- include/core/fence.hpp | 2 ++ include/core/image.hpp | 10 +++++++--- include/core/pipeline.hpp | 2 ++ include/core/sampler.hpp | 2 ++ include/core/semaphore.hpp | 2 ++ include/core/shadermodule.hpp | 4 +++- src/core/commandbuffer.cpp | 2 +- src/core/descriptorset.cpp | 6 +++--- src/core/pipeline.cpp | 2 +- 14 files changed, 47 insertions(+), 19 deletions(-) diff --git a/include/core/buffer.hpp b/include/core/buffer.hpp index 5012501..e0a1bc9 100644 --- a/include/core/buffer.hpp +++ b/include/core/buffer.hpp @@ -16,6 +16,8 @@ namespace Vulkan::Core { /// class Buffer { public: + Buffer() noexcept = default; + /// /// Create the buffer. /// @@ -48,7 +50,7 @@ namespace Vulkan::Core { std::shared_ptr buffer; std::shared_ptr memory; - size_t size; + size_t size{}; }; } diff --git a/include/core/commandbuffer.hpp b/include/core/commandbuffer.hpp index 67f1c4e..f028435 100644 --- a/include/core/commandbuffer.hpp +++ b/include/core/commandbuffer.hpp @@ -35,6 +35,8 @@ namespace Vulkan::Core { /// class CommandBuffer { public: + CommandBuffer() noexcept = default; + /// /// Create the command buffer. /// @@ -62,7 +64,7 @@ namespace Vulkan::Core { /// /// @throws std::logic_error if the command buffer is not in Recording state /// - void dispatch(uint32_t x, uint32_t y, uint32_t z); + void dispatch(uint32_t x, uint32_t y, uint32_t z) const; /// /// End recording commands in the command buffer. diff --git a/include/core/commandpool.hpp b/include/core/commandpool.hpp index 7470d86..247b717 100644 --- a/include/core/commandpool.hpp +++ b/include/core/commandpool.hpp @@ -16,6 +16,8 @@ namespace Vulkan::Core { /// class CommandPool { public: + CommandPool() noexcept = default; + /// /// Create the command pool. /// diff --git a/include/core/descriptorpool.hpp b/include/core/descriptorpool.hpp index 2227a66..0db8018 100644 --- a/include/core/descriptorpool.hpp +++ b/include/core/descriptorpool.hpp @@ -16,6 +16,8 @@ namespace Vulkan::Core { /// class DescriptorPool { public: + DescriptorPool() noexcept = default; + /// /// Create the descriptor pool. /// diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp index 89c33f7..6ca32f3 100644 --- a/include/core/descriptorset.hpp +++ b/include/core/descriptorset.hpp @@ -12,7 +12,6 @@ #include -#include #include namespace Vulkan::Core { @@ -26,6 +25,8 @@ namespace Vulkan::Core { /// class DescriptorSet { public: + DescriptorSet() noexcept = default; + /// /// Create the descriptor set. /// @@ -36,13 +37,7 @@ namespace Vulkan::Core { /// @throws ls::vulkan_error if object creation fails. /// DescriptorSet(const Device& device, - DescriptorPool pool, const ShaderModule& shaderModule); - - using ResourceList = std::variant< - std::pair&>, - std::pair&>, - std::pair&> - >; + const DescriptorPool& pool, const ShaderModule& shaderModule); /// /// Update the descriptor set with resources. @@ -91,6 +86,17 @@ namespace Vulkan::Core { DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& buffers) { for (const auto& buffer : buffers) this->add(type, buffer); return *this; } + /// Add an array of resources + template + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array& images) { + for (const auto& image : images) this->add(type, image); return *this; } + template + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array& samplers) { + for (const auto& sampler : samplers) this->add(type, sampler); return *this; } + template + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array& buffers) { + for (const auto& buffer : buffers) this->add(type, buffer); return *this; } + /// Finish building the descriptor set update. void build() const; private: diff --git a/include/core/fence.hpp b/include/core/fence.hpp index e12fe06..c7cf82a 100644 --- a/include/core/fence.hpp +++ b/include/core/fence.hpp @@ -16,6 +16,8 @@ namespace Vulkan::Core { /// class Fence { public: + Fence() noexcept = default; + /// /// Create the fence. /// diff --git a/include/core/image.hpp b/include/core/image.hpp index 775e492..7e7871f 100644 --- a/include/core/image.hpp +++ b/include/core/image.hpp @@ -16,6 +16,8 @@ namespace Vulkan::Core { /// class Image { public: + Image() noexcept = default; + /// /// Create the image. /// @@ -32,6 +34,8 @@ namespace Vulkan::Core { /// 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. @@ -52,9 +56,9 @@ namespace Vulkan::Core { std::shared_ptr memory; std::shared_ptr view; - VkExtent2D extent; - VkFormat format; - VkImageAspectFlags aspectFlags; + VkExtent2D extent{}; + VkFormat format{}; + VkImageAspectFlags aspectFlags{}; }; } diff --git a/include/core/pipeline.hpp b/include/core/pipeline.hpp index b90fb94..88a32f3 100644 --- a/include/core/pipeline.hpp +++ b/include/core/pipeline.hpp @@ -18,6 +18,8 @@ namespace Vulkan::Core { /// class Pipeline { public: + Pipeline() noexcept = default; + /// /// Create a compute pipeline. /// diff --git a/include/core/sampler.hpp b/include/core/sampler.hpp index b1f9b55..f08817b 100644 --- a/include/core/sampler.hpp +++ b/include/core/sampler.hpp @@ -16,6 +16,8 @@ namespace Vulkan::Core { /// class Sampler { public: + Sampler() noexcept = default; + /// /// Create the sampler. /// diff --git a/include/core/semaphore.hpp b/include/core/semaphore.hpp index 30168b2..41fa7e5 100644 --- a/include/core/semaphore.hpp +++ b/include/core/semaphore.hpp @@ -17,6 +17,8 @@ namespace Vulkan::Core { /// class Semaphore { public: + Semaphore() noexcept = default; + /// /// Create the semaphore. /// diff --git a/include/core/shadermodule.hpp b/include/core/shadermodule.hpp index fcfcf5b..de1f0da 100644 --- a/include/core/shadermodule.hpp +++ b/include/core/shadermodule.hpp @@ -19,6 +19,8 @@ namespace Vulkan::Core { /// class ShaderModule { public: + ShaderModule() noexcept = default; + /// /// Create the shader module. /// @@ -35,7 +37,7 @@ namespace Vulkan::Core { /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->shaderModule; } /// Get the descriptor set layout. - [[nodiscard]] auto getDescriptorSetLayout() const { return *this->descriptorSetLayout; } + [[nodiscard]] auto getLayout() const { return *this->descriptorSetLayout; } /// Trivially copyable, moveable and destructible ShaderModule(const ShaderModule&) noexcept = default; diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp index 5affc4e..6136538 100644 --- a/src/core/commandbuffer.cpp +++ b/src/core/commandbuffer.cpp @@ -41,7 +41,7 @@ void CommandBuffer::begin() { *this->state = CommandBufferState::Recording; } -void CommandBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z) { +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"); diff --git a/src/core/descriptorset.cpp b/src/core/descriptorset.cpp index de752c0..49e839f 100644 --- a/src/core/descriptorset.cpp +++ b/src/core/descriptorset.cpp @@ -4,9 +4,9 @@ using namespace Vulkan::Core; DescriptorSet::DescriptorSet(const Device& device, - DescriptorPool pool, const ShaderModule& shaderModule) { + const DescriptorPool& pool, const ShaderModule& shaderModule) { // create descriptor set - VkDescriptorSetLayout layout = shaderModule.getDescriptorSetLayout(); + VkDescriptorSetLayout layout = shaderModule.getLayout(); const VkDescriptorSetAllocateInfo desc{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = pool.handle(), @@ -21,7 +21,7 @@ DescriptorSet::DescriptorSet(const Device& device, /// store set in shared ptr this->descriptorSet = std::shared_ptr( new VkDescriptorSet(descriptorSetHandle), - [dev = device.handle(), pool = std::move(pool)](VkDescriptorSet* setHandle) { + [dev = device.handle(), pool = pool](VkDescriptorSet* setHandle) { vkFreeDescriptorSets(dev, pool.handle(), 1, setHandle); } ); diff --git a/src/core/pipeline.cpp b/src/core/pipeline.cpp index e20b040..00a8274 100644 --- a/src/core/pipeline.cpp +++ b/src/core/pipeline.cpp @@ -5,7 +5,7 @@ using namespace Vulkan::Core; Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { // create pipeline layout - VkDescriptorSetLayout shaderLayout = shader.getDescriptorSetLayout(); + VkDescriptorSetLayout shaderLayout = shader.getLayout(); const VkPipelineLayoutCreateInfo layoutDesc{ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, From 6b21e1f2983b1230c8e85fad8bfdb4e7ecd49ff6 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 00:33:28 +0200 Subject: [PATCH 021/253] initial shaderchain --- .gitignore | 4 +- CMakeLists.txt | 1 + include/core/image.hpp | 7 ++ include/shaderchains/downsample.hpp | 65 +++++++++++++ .../{memorybarriers.hpp => barriers.hpp} | 15 +-- include/utils/global.hpp | 41 ++++++++ src/core/image.cpp | 1 + src/main.cpp | 94 ++++--------------- src/shaderchains/downsample.cpp | 55 +++++++++++ .../{memorybarriers.cpp => barriers.cpp} | 35 ++----- src/utils/globals.cpp | 31 ++++++ 11 files changed, 234 insertions(+), 115 deletions(-) create mode 100644 include/shaderchains/downsample.hpp rename include/utils/{memorybarriers.hpp => barriers.hpp} (53%) create mode 100644 include/utils/global.hpp create mode 100644 src/shaderchains/downsample.cpp rename src/utils/{memorybarriers.cpp => barriers.cpp} (59%) create mode 100644 src/utils/globals.cpp diff --git a/.gitignore b/.gitignore index 0b4f101..483bf36 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,5 @@ /.cache /.ccls -# private shaders -/shaders +# private resources +/rsc diff --git a/CMakeLists.txt b/CMakeLists.txt index dd4393a..c80adf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) file(GLOB SOURCES "src/core/*.cpp" + "src/shaderchains/*.cpp" "src/utils/*.cpp" "src/*.cpp" ) diff --git a/include/core/image.hpp b/include/core/image.hpp index 7e7871f..8517473 100644 --- a/include/core/image.hpp +++ b/include/core/image.hpp @@ -45,6 +45,11 @@ namespace Vulkan::Core { /// 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; @@ -56,6 +61,8 @@ namespace Vulkan::Core { std::shared_ptr memory; std::shared_ptr view; + std::shared_ptr layout; + VkExtent2D extent{}; VkFormat format{}; VkImageAspectFlags aspectFlags{}; diff --git a/include/shaderchains/downsample.hpp b/include/shaderchains/downsample.hpp new file mode 100644 index 0000000..9584e48 --- /dev/null +++ b/include/shaderchains/downsample.hpp @@ -0,0 +1,65 @@ +#ifndef DOWNSAMPLE_HPP +#define DOWNSAMPLE_HPP + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" + +namespace Vulkan::Shaderchains { + + /// + /// Downsample shader. + /// + /// Takes an 8-bit RGBA texture and downsamples it into 7x 8-bit R textures. + /// + class Downsample { + public: + /// + /// Initialize the shaderchain. + /// + /// @param device The Vulkan device to create the resources on. + /// @param pool The descriptor pool to allocate in. + /// @param inImage The input image to downsample. + /// + /// @throws ls::vulkan_error if resource creation fails. + /// + Downsample(const Device& device, const Core::DescriptorPool& pool, + const Core::Image& inImage); + + /// + /// Dispatch the shaderchain. + /// + /// @param buf The command buffer to use for dispatching. + /// + /// @throws std::logic_error if the command buffer is not recording. + /// + void Dispatch(const Core::CommandBuffer& buf); + + /// Get the output images. + [[nodiscard]] const auto& getOutImages() const { return this->outImages; } + + /// Trivially copyable, moveable and destructible + Downsample(const Downsample&) noexcept = default; + Downsample& operator=(const Downsample&) noexcept = default; + Downsample(Downsample&&) noexcept = default; + Downsample& operator=(Downsample&&) noexcept = default; + ~Downsample() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + Core::DescriptorSet descriptorSet; + Core::Buffer buffer; + + Core::Image inImage; + + std::vector outImages; + }; + +} + +#endif // DOWNSAMPLE_HPP diff --git a/include/utils/memorybarriers.hpp b/include/utils/barriers.hpp similarity index 53% rename from include/utils/memorybarriers.hpp rename to include/utils/barriers.hpp index 412b169..c462d0e 100644 --- a/include/utils/memorybarriers.hpp +++ b/include/utils/barriers.hpp @@ -16,24 +16,13 @@ namespace Barriers { /// @param buffer Command buffer to insert barriers into /// @param r2wImages Images that are being read and will be written to /// @param w2rImages Images that are being written to and will be read from - /// @param srcLayout Optional source layout for the images, defaults to VK_IMAGE_LAYOUT_GENERAL /// /// @throws std::logic_error if the command buffer is not in Recording state /// void insertBarrier( const Vulkan::Core::CommandBuffer& buffer, - const std::vector& r2wImages, - const std::vector& w2rImages, - std::optional srcLayout = std::nullopt); - - /// - /// Insert a global memory barrier in a command buffer. - /// - /// @param buffer Command buffer to insert the barrier into - /// - /// @throws std::logic_error if the command buffer is not in Recording state - /// - void insertGlobalBarrier(const Vulkan::Core::CommandBuffer& buffer); + std::vector r2wImages, + std::vector w2rImages); }; diff --git a/include/utils/global.hpp b/include/utils/global.hpp new file mode 100644 index 0000000..b694ab1 --- /dev/null +++ b/include/utils/global.hpp @@ -0,0 +1,41 @@ +#ifndef GLOBAL_HPP +#define GLOBAL_HPP + +#include "core/sampler.hpp" + +#include + +namespace Vulkan::Globals { + + /// Global sampler with address mode set to clamp to border. + extern Core::Sampler samplerClampBorder; + /// Global sampler with address mode set to clamp to edge. + extern Core::Sampler samplerClampEdge; + + /// Commonly used constant buffer structure for shaders. + struct FgBuffer { + std::array inputOffset; + uint32_t firstIter; + uint32_t firstIterS; + uint32_t advancedColorKind; + uint32_t hdrSupport; + float resolutionInvScale; + float timestamp; + float uiThreshold; + std::array pad; + }; + + /// Default instance of the FgBuffer. + extern FgBuffer fgBuffer; + + static_assert(sizeof(FgBuffer) == 48, "FgBuffer must be 48 bytes in size."); + + /// Initialize global resources. + void initializeGlobals(const Device& device); + + /// Uninitialize global resources. + void uninitializeGlobals() noexcept; + +} + +#endif // GLOBAL_HPP diff --git a/src/core/image.cpp b/src/core/image.cpp index b6d4929..d58ed95 100644 --- a/src/core/image.cpp +++ b/src/core/image.cpp @@ -90,6 +90,7 @@ Image::Image(const Device& device, VkExtent2D extent, VkFormat format, throw ls::vulkan_error(res, "Failed to create image view"); // store objects in shared ptr + this->layout = std::make_shared(VK_IMAGE_LAYOUT_UNDEFINED); this->image = std::shared_ptr( new VkImage(imageHandle), [dev = device.handle()](VkImage* img) { diff --git a/src/main.cpp b/src/main.cpp index b3df478..cd78557 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,109 +1,55 @@ -#include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/commandpool.hpp" #include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" #include "core/fence.hpp" #include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/sampler.hpp" -#include "core/shadermodule.hpp" #include "device.hpp" #include "instance.hpp" -#include "utils/memorybarriers.hpp" +#include "shaderchains/downsample.hpp" +#include "utils/global.hpp" -#include -#include #include -#include -#include using namespace Vulkan; -struct DataBuffer { - std::array inputOffset; - uint32_t firstIter; - uint32_t firstIterS; - uint32_t advancedColorKind; - uint32_t hdrSupport; - float resolutionInvScale; - float timestamp; - float uiThreshold; - std::array pad; -}; - -const static DataBuffer data{ - .inputOffset = { 0, 29 }, - .resolutionInvScale = 1.0F, - .timestamp = 0.5, - .uiThreshold = 0.1F -}; - int main() { // initialize application const Instance instance; const Device device(instance); const Core::DescriptorPool descriptorPool(device); const Core::CommandPool commandPool(device); + const Core::Fence fence(device); - const Core::ShaderModule computeShader(device, "shaders/downsample.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER}, - { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, - { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER} }); - const Core::Pipeline computePipeline(device, computeShader); + Globals::initializeGlobals(device); - const Core::Sampler sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); - - const std::vector inputImages(1, Core::Image( + // create initialization resources + const Core::Image inputImage( device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT, VK_IMAGE_ASPECT_COLOR_BIT - )); + ); - const Core::Buffer buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - - std::vector outputImages; - outputImages.reserve(7); - for (size_t i = 0; i < 7; ++i) - outputImages.emplace_back(device, - VkExtent2D { .width = 2560U >> i, .height = 1411U >> i }, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - // load descriptor set - const Core::DescriptorSet descriptorSet(device, descriptorPool, computeShader); - descriptorSet.update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, sampler) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inputImages) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outputImages) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, buffer) - .build(); - - // start pass - Core::Fence fence(device); + // create the shaderchains + Shaderchains::Downsample downsample(device, descriptorPool, inputImage); + // start the rendering pipeline Core::CommandBuffer commandBuffer(device, commandPool); commandBuffer.begin(); - // render - Barriers::insertBarrier( - commandBuffer, - inputImages, - outputImages, - VK_IMAGE_LAYOUT_UNDEFINED - ); + downsample.Dispatch(commandBuffer); - computePipeline.bind(commandBuffer); - descriptorSet.bind(commandBuffer, computePipeline); - commandBuffer.dispatch(40, 23, 1); - - // end pass + // finish the rendering pipeline commandBuffer.end(); commandBuffer.submit(device.getComputeQueue(), fence); - assert(fence.wait(device) && "Synchronization fence timed out"); + if (!fence.wait(device)) { + Globals::uninitializeGlobals(); + + std::cerr << "Application failed due to timeout" << '\n'; + return 1; + } + + Globals::uninitializeGlobals(); std::cerr << "Application finished" << '\n'; return 0; diff --git a/src/shaderchains/downsample.cpp b/src/shaderchains/downsample.cpp new file mode 100644 index 0000000..347fdb9 --- /dev/null +++ b/src/shaderchains/downsample.cpp @@ -0,0 +1,55 @@ +#include "shaderchains/downsample.hpp" +#include "utils/global.hpp" +#include "utils/barriers.hpp" + +using namespace Vulkan::Shaderchains; + +Downsample::Downsample(const Device& device, const Core::DescriptorPool& pool, + const Core::Image& inImage) : inImage(inImage) { + // create internal resources + this->shaderModule = Core::ShaderModule(device, "rsc/shaders/downsample.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); + this->pipeline = Core::Pipeline(device, this->shaderModule); + this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); + + const Globals::FgBuffer data = Globals::fgBuffer; + this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + auto extent = inImage.getExtent(); + + // create output images + this->outImages.resize(7); + for (size_t i = 0; i < 7; i++) + this->outImages.at(i) = Core::Image(device, + { extent.width >> i, extent.height >> i }, + VK_FORMAT_R8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + + // update descriptor set + this->descriptorSet.update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inImage) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImages) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); +} + +void Downsample::Dispatch(const Core::CommandBuffer& buf) { + auto extent = inImage.getExtent(); + const uint32_t threadsX = (extent.width + 63) >> 6; + const uint32_t threadsY = (extent.height + 63) >> 6; + + Barriers::insertBarrier( + buf, + this->outImages, + { this->inImage } + ); + + this->pipeline.bind(buf); + this->descriptorSet.bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/src/utils/memorybarriers.cpp b/src/utils/barriers.cpp similarity index 59% rename from src/utils/memorybarriers.cpp rename to src/utils/barriers.cpp index efc827c..50d5aa7 100644 --- a/src/utils/memorybarriers.cpp +++ b/src/utils/barriers.cpp @@ -1,25 +1,22 @@ -#include "utils/memorybarriers.hpp" - -#include +#include "utils/barriers.hpp" using namespace Barriers; void Barriers::insertBarrier( const Vulkan::Core::CommandBuffer& buffer, - const std::vector& r2wImages, - const std::vector& w2rImages, - std::optional srcLayout) { + std::vector r2wImages, + std::vector w2rImages) { std::vector barriers(r2wImages.size() + w2rImages.size()); size_t index = 0; - for (const auto& image : r2wImages) { + for (auto& image : r2wImages) { barriers[index++] = { .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 = srcLayout.value_or(VK_IMAGE_LAYOUT_GENERAL), + .oldLayout = image.getLayout(), .newLayout = VK_IMAGE_LAYOUT_GENERAL, .image = image.handle(), .subresourceRange = { @@ -28,15 +25,16 @@ void Barriers::insertBarrier( .layerCount = 1 } }; + image.setLayout(VK_IMAGE_LAYOUT_GENERAL); } - for (const auto& image : w2rImages) { + for (auto& image : w2rImages) { barriers[index++] = { .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 = srcLayout.value_or(VK_IMAGE_LAYOUT_GENERAL), + .oldLayout = image.getLayout(), .newLayout = VK_IMAGE_LAYOUT_GENERAL, .image = image.handle(), .subresourceRange = { @@ -45,6 +43,7 @@ void Barriers::insertBarrier( .layerCount = 1 } }; + image.setLayout(VK_IMAGE_LAYOUT_GENERAL); } const VkDependencyInfo dependencyInfo = { .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, @@ -53,19 +52,3 @@ void Barriers::insertBarrier( }; vkCmdPipelineBarrier2(buffer.handle(), &dependencyInfo); } - -void Barriers::insertGlobalBarrier(const Vulkan::Core::CommandBuffer& buffer) { - const VkMemoryBarrier2 barrier = { - .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, - .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, - .srcAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT, - .dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, - .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT - }; - const VkDependencyInfo dependencyInfo = { - .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, - .memoryBarrierCount = 1, - .pMemoryBarriers = &barrier - }; - vkCmdPipelineBarrier2(buffer.handle(), &dependencyInfo); -} diff --git a/src/utils/globals.cpp b/src/utils/globals.cpp new file mode 100644 index 0000000..62609fc --- /dev/null +++ b/src/utils/globals.cpp @@ -0,0 +1,31 @@ +#include "utils/global.hpp" + +using namespace Vulkan; + +Core::Sampler Globals::samplerClampBorder; +Core::Sampler Globals::samplerClampEdge; +Globals::FgBuffer Globals::fgBuffer; + +void Globals::initializeGlobals(const Device& device) { + // initialize global samplers + samplerClampBorder = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); + samplerClampEdge = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); + + // initialize global constant buffer + fgBuffer = { + .inputOffset = { 0, 29 }, + .resolutionInvScale = 1.0F, + .timestamp = 0.5F, + .uiThreshold = 0.1F, + }; + +} + +void Globals::uninitializeGlobals() noexcept { + // uninitialize global samplers + samplerClampBorder = Core::Sampler(); + samplerClampEdge = Core::Sampler(); + + // uninitialize global constant buffer + fgBuffer = Globals::FgBuffer(); +} From bdeae9b8d0c4a9fc2c77bf5fcd8ab57fecf713f1 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 01:10:54 +0200 Subject: [PATCH 022/253] host image copy --- include/utils/upload.hpp | 16 +++++++++ src/device.cpp | 14 ++++++-- src/main.cpp | 7 ++-- src/utils/upload.cpp | 74 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 include/utils/upload.hpp create mode 100644 src/utils/upload.cpp diff --git a/include/utils/upload.hpp b/include/utils/upload.hpp new file mode 100644 index 0000000..c8a9af6 --- /dev/null +++ b/include/utils/upload.hpp @@ -0,0 +1,16 @@ +#ifndef UPLOAD_HPP +#define UPLOAD_HPP + +#include "core/image.hpp" +#include "device.hpp" + +#include + +namespace Upload { + + void upload(const Vulkan::Device& device, + Vulkan::Core::Image& image, const std::string& path); + +} + +#endif // UPLOAD_HPP diff --git a/src/device.cpp b/src/device.cpp index c84cb4c..0ca4622 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -3,10 +3,13 @@ #include #include -#include using namespace Vulkan; +const std::vector requiredExtensions = { + "VK_EXT_host_image_copy" +}; + Device::Device(const Instance& instance) { // get all physical devices uint32_t deviceCount{}; @@ -50,8 +53,13 @@ Device::Device(const Instance& instance) { // create logical device const float queuePriority{1.0F}; // highest priority + VkPhysicalDeviceHostImageCopyFeaturesEXT hostImageCopyFeatures{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_FEATURES_EXT, + .hostImageCopy = VK_TRUE + }; VkPhysicalDeviceVulkan13Features features13{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, + .pNext = &hostImageCopyFeatures, .synchronization2 = VK_TRUE }; const VkPhysicalDeviceVulkan12Features features12{ @@ -70,7 +78,9 @@ Device::Device(const Instance& instance) { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &features12, .queueCreateInfoCount = 1, - .pQueueCreateInfos = &computeQueueDesc + .pQueueCreateInfos = &computeQueueDesc, + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data() }; VkDevice deviceHandle{}; res = vkCreateDevice(*physicalDevice, &deviceCreateInfo, nullptr, &deviceHandle); diff --git a/src/main.cpp b/src/main.cpp index cd78557..cc66998 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include "instance.hpp" #include "shaderchains/downsample.hpp" #include "utils/global.hpp" +#include "utils/upload.hpp" #include @@ -23,11 +24,13 @@ int main() { Globals::initializeGlobals(device); // create initialization resources - const Core::Image inputImage( + Core::Image inputImage( device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT + | VK_IMAGE_USAGE_HOST_TRANSFER_BIT, // (remove in prod) VK_IMAGE_ASPECT_COLOR_BIT ); + Upload::upload(device, inputImage, "rsc/images/source.dds"); // create the shaderchains Shaderchains::Downsample downsample(device, descriptorPool, inputImage); diff --git a/src/utils/upload.cpp b/src/utils/upload.cpp new file mode 100644 index 0000000..2dae8dd --- /dev/null +++ b/src/utils/upload.cpp @@ -0,0 +1,74 @@ +#include "utils/upload.hpp" +#include "utils/exceptions.hpp" + +#include +#include +#include + +using namespace Upload; + +void Upload::upload(const Vulkan::Device& device, + Vulkan::Core::Image& image, const std::string& path) { + auto vkTransitionImageLayoutEXT = reinterpret_cast( + vkGetDeviceProcAddr(device.handle(), "vkTransitionImageLayoutEXT")); + auto vkCopyMemoryToImageEXT = reinterpret_cast( + vkGetDeviceProcAddr(device.handle(), "vkCopyMemoryToImageEXT")); + + const VkHostImageLayoutTransitionInfoEXT transitionInfo{ + .sType = VK_STRUCTURE_TYPE_HOST_IMAGE_LAYOUT_TRANSITION_INFO_EXT, + .image = image.handle(), + .oldLayout = image.getLayout(), + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .subresourceRange = { + .aspectMask = image.getAspectFlags(), + .levelCount = 1, + .layerCount = 1 + } + }; + image.setLayout(VK_IMAGE_LAYOUT_GENERAL); + auto res = vkTransitionImageLayoutEXT(device.handle(), 1, &transitionInfo); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Failed to transition image layout for upload"); + + // read shader bytecode + std::ifstream file(path, std::ios::ate | std::ios::binary); + if (!file) + throw std::system_error(errno, std::generic_category(), "Failed to open shader file: " + path); + + std::streamsize size = file.tellg(); + size -= 124 - 4; + std::vector code(static_cast(size)); + + file.seekg(0, std::ios::beg); + if (!file.read(reinterpret_cast(code.data()), size)) + throw std::system_error(errno, std::generic_category(), "Failed to read shader file: " + path); + + file.close(); + + // copy data to image + auto extent = image.getExtent(); + const VkMemoryToImageCopyEXT copyInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_TO_IMAGE_COPY_EXT, + .pHostPointer = code.data(), + .imageSubresource = { + .aspectMask = image.getAspectFlags(), + .layerCount = 1 + }, + .imageExtent = { + .width = extent.width, + .height = extent.height, + .depth = 1 + }, + }; + + const VkCopyMemoryToImageInfoEXT operationInfo{ + .sType = VK_STRUCTURE_TYPE_COPY_MEMORY_TO_IMAGE_INFO_EXT, + .dstImage = image.handle(), + .dstImageLayout = image.getLayout(), + .regionCount = 1, + .pRegions = ©Info, + }; + res = vkCopyMemoryToImageEXT(device.handle(), &operationInfo); + if (res != VK_SUCCESS) + throw ls::vulkan_error(res, "Failed to copy memory to image"); +} From 10ac4933123e9f44307e56d0873320a418a797cb Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 01:58:36 +0200 Subject: [PATCH 023/253] host image copyless host image copy --- include/core/buffer.hpp | 15 ++++++ include/utils/upload.hpp | 16 ++++++- src/device.cpp | 7 +-- src/main.cpp | 32 +++++++++++-- src/utils/upload.cpp | 98 ++++++++++++++++++++++------------------ 5 files changed, 112 insertions(+), 56 deletions(-) diff --git a/include/core/buffer.hpp b/include/core/buffer.hpp index e0a1bc9..ebe1889 100644 --- a/include/core/buffer.hpp +++ b/include/core/buffer.hpp @@ -33,6 +33,21 @@ namespace Vulkan::Core { construct(device, reinterpret_cast(&data), usage); } + /// + /// Create the buffer. + /// + /// @param device Vulkan device + /// @param data Initial data for the buffer + /// @param size Size of the buffer in bytes + /// @param usage Usage flags for the buffer + /// + /// @throws ls::vulkan_error if object creation fails. + /// + Buffer(const 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. diff --git a/include/utils/upload.hpp b/include/utils/upload.hpp index c8a9af6..b00967e 100644 --- a/include/utils/upload.hpp +++ b/include/utils/upload.hpp @@ -1,6 +1,7 @@ #ifndef UPLOAD_HPP #define UPLOAD_HPP +#include "core/commandpool.hpp" #include "core/image.hpp" #include "device.hpp" @@ -8,8 +9,19 @@ namespace Upload { - void upload(const Vulkan::Device& device, - Vulkan::Core::Image& image, const std::string& path); + /// + /// 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 upload(const Vulkan::Device& device, const Vulkan::Core::CommandPool& commandPool, + Vulkan::Core::Image& image, const std::string& path); } diff --git a/src/device.cpp b/src/device.cpp index 0ca4622..97c0208 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -7,7 +7,7 @@ using namespace Vulkan; const std::vector requiredExtensions = { - "VK_EXT_host_image_copy" + "VK_KHR_external_memory_fd" }; Device::Device(const Instance& instance) { @@ -53,13 +53,8 @@ Device::Device(const Instance& instance) { // create logical device const float queuePriority{1.0F}; // highest priority - VkPhysicalDeviceHostImageCopyFeaturesEXT hostImageCopyFeatures{ - .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_FEATURES_EXT, - .hostImageCopy = VK_TRUE - }; VkPhysicalDeviceVulkan13Features features13{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, - .pNext = &hostImageCopyFeatures, .synchronization2 = VK_TRUE }; const VkPhysicalDeviceVulkan12Features features12{ diff --git a/src/main.cpp b/src/main.cpp index cc66998..e2f6c83 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,9 +11,28 @@ #include +#include +#include +#include +#include + using namespace Vulkan; int main() { + // attempt to load renderdoc + RENDERDOC_API_1_6_0* rdoc = nullptr; + if (void* mod_renderdoc = dlopen("/usr/lib/librenderdoc.so", RTLD_NOLOAD | RTLD_NOW)) { + std::cerr << "Found RenderDoc library, setting up frame capture." << '\n'; + + auto GetAPI = reinterpret_cast(dlsym(mod_renderdoc, "RENDERDOC_GetAPI")); + const int ret = GetAPI(eRENDERDOC_API_Version_1_6_0, reinterpret_cast(&rdoc)); + if (ret == 0) { + std::cerr << "Unable to initialize RenderDoc API. Is your RenderDoc up to date?" << '\n'; + rdoc = nullptr; + } + usleep(1000 * 100); // give renderdoc time to load + } + // initialize application const Instance instance; const Device device(instance); @@ -26,16 +45,18 @@ int main() { // create initialization resources Core::Image inputImage( device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT - | VK_IMAGE_USAGE_HOST_TRANSFER_BIT, // (remove in prod) + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT ); - Upload::upload(device, inputImage, "rsc/images/source.dds"); + Upload::upload(device, commandPool, inputImage, "rsc/images/source.dds"); // create the shaderchains Shaderchains::Downsample downsample(device, descriptorPool, inputImage); // start the rendering pipeline + if (rdoc) + rdoc->StartFrameCapture(nullptr, nullptr); + Core::CommandBuffer commandBuffer(device, commandPool); commandBuffer.begin(); @@ -52,6 +73,11 @@ int main() { return 1; } + if (rdoc) + rdoc->EndFrameCapture(nullptr, nullptr); + + usleep(1000 * 100); // give renderdoc time to capture + Globals::uninitializeGlobals(); std::cerr << "Application finished" << '\n'; diff --git a/src/utils/upload.cpp b/src/utils/upload.cpp index 2dae8dd..95b239a 100644 --- a/src/utils/upload.cpp +++ b/src/utils/upload.cpp @@ -1,4 +1,6 @@ #include "utils/upload.hpp" +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" #include "utils/exceptions.hpp" #include @@ -6,34 +8,14 @@ #include using namespace Upload; +using namespace Vulkan; -void Upload::upload(const Vulkan::Device& device, - Vulkan::Core::Image& image, const std::string& path) { - auto vkTransitionImageLayoutEXT = reinterpret_cast( - vkGetDeviceProcAddr(device.handle(), "vkTransitionImageLayoutEXT")); - auto vkCopyMemoryToImageEXT = reinterpret_cast( - vkGetDeviceProcAddr(device.handle(), "vkCopyMemoryToImageEXT")); - - const VkHostImageLayoutTransitionInfoEXT transitionInfo{ - .sType = VK_STRUCTURE_TYPE_HOST_IMAGE_LAYOUT_TRANSITION_INFO_EXT, - .image = image.handle(), - .oldLayout = image.getLayout(), - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .subresourceRange = { - .aspectMask = image.getAspectFlags(), - .levelCount = 1, - .layerCount = 1 - } - }; - image.setLayout(VK_IMAGE_LAYOUT_GENERAL); - auto res = vkTransitionImageLayoutEXT(device.handle(), 1, &transitionInfo); - if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Failed to transition image layout for upload"); - - // read shader bytecode +void Upload::upload(const Device& device, const Core::CommandPool& commandPool, + Core::Image& image, const std::string& path) { + // read iamge bytecode std::ifstream file(path, std::ios::ate | std::ios::binary); if (!file) - throw std::system_error(errno, std::generic_category(), "Failed to open shader file: " + path); + throw std::system_error(errno, std::generic_category(), "Failed to open image: " + path); std::streamsize size = file.tellg(); size -= 124 - 4; @@ -41,34 +23,60 @@ void Upload::upload(const Vulkan::Device& device, file.seekg(0, std::ios::beg); if (!file.read(reinterpret_cast(code.data()), size)) - throw std::system_error(errno, std::generic_category(), "Failed to read shader file: " + path); + throw std::system_error(errno, std::generic_category(), "Failed to read image: " + path); file.close(); - // copy data to image + // copy data to buffer + const Vulkan::Core::Buffer stagingBuffer( + device, code.data(), static_cast(code.size()), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT + ); + + // perform the upload + Core::CommandBuffer commandBuffer(device, commandPool); + commandBuffer.begin(); + + const VkImageMemoryBarrier barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_NONE, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = image.getLayout(), + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = image.handle(), + .subresourceRange = { + .aspectMask = image.getAspectFlags(), + .levelCount = 1, + .layerCount = 1 + } + }; + image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + vkCmdPipelineBarrier( + commandBuffer.handle(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &barrier + ); + auto extent = image.getExtent(); - const VkMemoryToImageCopyEXT copyInfo{ - .sType = VK_STRUCTURE_TYPE_MEMORY_TO_IMAGE_COPY_EXT, - .pHostPointer = code.data(), + const VkBufferImageCopy region{ + .bufferImageHeight = 0, .imageSubresource = { .aspectMask = image.getAspectFlags(), .layerCount = 1 }, - .imageExtent = { - .width = extent.width, - .height = extent.height, - .depth = 1 - }, + .imageExtent = { extent.width, extent.height, 1 } }; + vkCmdCopyBufferToImage( + commandBuffer.handle(), + stagingBuffer.handle(), image.handle(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion + ); - const VkCopyMemoryToImageInfoEXT operationInfo{ - .sType = VK_STRUCTURE_TYPE_COPY_MEMORY_TO_IMAGE_INFO_EXT, - .dstImage = image.handle(), - .dstImageLayout = image.getLayout(), - .regionCount = 1, - .pRegions = ©Info, - }; - res = vkCopyMemoryToImageEXT(device.handle(), &operationInfo); - if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Failed to copy memory to image"); + commandBuffer.end(); + + Core::Fence fence(device); + commandBuffer.submit(device.getComputeQueue(), fence); + + if (!fence.wait(device)) + throw ls::vulkan_error(VK_TIMEOUT, "Upload operation timed out"); } From 0345ec9e69780abd90354b792cc01c9e9176cc99 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 02:18:01 +0200 Subject: [PATCH 024/253] reorganize utils --- CMakeLists.txt | 1 - include/shaderchains/alpha.hpp | 0 include/utils.hpp | 106 +++++++++++++++++++++ include/utils/barriers.hpp | 29 ------ include/utils/exceptions.hpp | 37 -------- include/utils/global.hpp | 41 -------- include/utils/upload.hpp | 28 ------ src/core/buffer.cpp | 2 +- src/core/commandbuffer.cpp | 2 +- src/core/commandpool.cpp | 2 +- src/core/descriptorpool.cpp | 2 +- src/core/descriptorset.cpp | 2 +- src/core/fence.cpp | 2 +- src/core/image.cpp | 2 +- src/core/pipeline.cpp | 2 +- src/core/sampler.cpp | 2 +- src/core/semaphore.cpp | 2 +- src/core/shadermodule.cpp | 2 +- src/device.cpp | 2 +- src/instance.cpp | 2 +- src/main.cpp | 5 +- src/shaderchains/alpha.cpp | 0 src/shaderchains/downsample.cpp | 5 +- src/utils.cpp | 162 ++++++++++++++++++++++++++++++++ src/utils/barriers.cpp | 54 ----------- src/utils/exceptions.cpp | 10 -- src/utils/globals.cpp | 31 ------ src/utils/upload.cpp | 82 ---------------- 28 files changed, 285 insertions(+), 332 deletions(-) create mode 100644 include/shaderchains/alpha.hpp create mode 100644 include/utils.hpp delete mode 100644 include/utils/barriers.hpp delete mode 100644 include/utils/exceptions.hpp delete mode 100644 include/utils/global.hpp delete mode 100644 include/utils/upload.hpp create mode 100644 src/shaderchains/alpha.cpp create mode 100644 src/utils.cpp delete mode 100644 src/utils/barriers.cpp delete mode 100644 src/utils/exceptions.cpp delete mode 100644 src/utils/globals.cpp delete mode 100644 src/utils/upload.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c80adf9..9f01bed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) file(GLOB SOURCES "src/core/*.cpp" "src/shaderchains/*.cpp" - "src/utils/*.cpp" "src/*.cpp" ) diff --git a/include/shaderchains/alpha.hpp b/include/shaderchains/alpha.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/utils.hpp b/include/utils.hpp new file mode 100644 index 0000000..930a31e --- /dev/null +++ b/include/utils.hpp @@ -0,0 +1,106 @@ +#ifndef UTILS_HPP +#define UTILS_HPP + +#include "core/commandbuffer.hpp" +#include "core/image.hpp" +#include "core/sampler.hpp" + +#include +#include +#include + +namespace Vulkan::Utils { + + /// + /// Insert memory barriers for images in a command buffer. + /// + /// @param buffer Command buffer to insert barriers into + /// @param r2wImages Images that are being read and will be written to + /// @param w2rImages Images that are being written to and will be read from + /// + /// @throws std::logic_error if the command buffer is not in Recording state + /// + void insertBarrier( + const Vulkan::Core::CommandBuffer& buffer, + std::vector r2wImages, + std::vector w2rImages); + + /// + /// 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 Vulkan::Device& device, + const Vulkan::Core::CommandPool& commandPool, + Vulkan::Core::Image& image, const std::string& path); + +} + +namespace Vulkan::Globals { + + /// Global sampler with address mode set to clamp to border. + extern Core::Sampler samplerClampBorder; + /// Global sampler with address mode set to clamp to edge. + extern Core::Sampler samplerClampEdge; + + /// Commonly used constant buffer structure for shaders. + struct FgBuffer { + std::array inputOffset; + uint32_t firstIter; + uint32_t firstIterS; + uint32_t advancedColorKind; + uint32_t hdrSupport; + float resolutionInvScale; + float timestamp; + float uiThreshold; + std::array pad; + }; + + /// Default instance of the FgBuffer. + extern FgBuffer fgBuffer; + + static_assert(sizeof(FgBuffer) == 48, "FgBuffer must be 48 bytes in size."); + + /// Initialize global resources. + void initializeGlobals(const Device& device); + + /// Uninitialize global resources. + void uninitializeGlobals() noexcept; + +} + +namespace ls { + + /// 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; + }; + +} + +#endif // UTILS_HPP diff --git a/include/utils/barriers.hpp b/include/utils/barriers.hpp deleted file mode 100644 index c462d0e..0000000 --- a/include/utils/barriers.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef BARRIERS_HPP -#define BARRIERS_HPP - -#include "core/commandbuffer.hpp" -#include "core/image.hpp" - -#include - -#include - -namespace Barriers { - - /// - /// Insert memory barriers for images in a command buffer. - /// - /// @param buffer Command buffer to insert barriers into - /// @param r2wImages Images that are being read and will be written to - /// @param w2rImages Images that are being written to and will be read from - /// - /// @throws std::logic_error if the command buffer is not in Recording state - /// - void insertBarrier( - const Vulkan::Core::CommandBuffer& buffer, - std::vector r2wImages, - std::vector w2rImages); - -}; - -#endif // BARRIERS_HPP diff --git a/include/utils/exceptions.hpp b/include/utils/exceptions.hpp deleted file mode 100644 index 9b47718..0000000 --- a/include/utils/exceptions.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef EXCEPTIONS_HPP -#define EXCEPTIONS_HPP - -#include - -#include -#include - -namespace ls { - - /// 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; - }; - -} - -#endif // EXCEPTIONS_HPP diff --git a/include/utils/global.hpp b/include/utils/global.hpp deleted file mode 100644 index b694ab1..0000000 --- a/include/utils/global.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef GLOBAL_HPP -#define GLOBAL_HPP - -#include "core/sampler.hpp" - -#include - -namespace Vulkan::Globals { - - /// Global sampler with address mode set to clamp to border. - extern Core::Sampler samplerClampBorder; - /// Global sampler with address mode set to clamp to edge. - extern Core::Sampler samplerClampEdge; - - /// Commonly used constant buffer structure for shaders. - struct FgBuffer { - std::array inputOffset; - uint32_t firstIter; - uint32_t firstIterS; - uint32_t advancedColorKind; - uint32_t hdrSupport; - float resolutionInvScale; - float timestamp; - float uiThreshold; - std::array pad; - }; - - /// Default instance of the FgBuffer. - extern FgBuffer fgBuffer; - - static_assert(sizeof(FgBuffer) == 48, "FgBuffer must be 48 bytes in size."); - - /// Initialize global resources. - void initializeGlobals(const Device& device); - - /// Uninitialize global resources. - void uninitializeGlobals() noexcept; - -} - -#endif // GLOBAL_HPP diff --git a/include/utils/upload.hpp b/include/utils/upload.hpp deleted file mode 100644 index b00967e..0000000 --- a/include/utils/upload.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef UPLOAD_HPP -#define UPLOAD_HPP - -#include "core/commandpool.hpp" -#include "core/image.hpp" -#include "device.hpp" - -#include - -namespace Upload { - - /// - /// 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 upload(const Vulkan::Device& device, const Vulkan::Core::CommandPool& commandPool, - Vulkan::Core::Image& image, const std::string& path); - -} - -#endif // UPLOAD_HPP diff --git a/src/core/buffer.cpp b/src/core/buffer.cpp index e8d5f7c..d6982b3 100644 --- a/src/core/buffer.cpp +++ b/src/core/buffer.cpp @@ -1,5 +1,5 @@ #include "core/buffer.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" #include #include diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp index 6136538..bee6792 100644 --- a/src/core/commandbuffer.cpp +++ b/src/core/commandbuffer.cpp @@ -1,5 +1,5 @@ #include "core/commandbuffer.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" using namespace Vulkan::Core; diff --git a/src/core/commandpool.cpp b/src/core/commandpool.cpp index e1fba1c..cb99d9f 100644 --- a/src/core/commandpool.cpp +++ b/src/core/commandpool.cpp @@ -1,5 +1,5 @@ #include "core/commandpool.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" using namespace Vulkan::Core; diff --git a/src/core/descriptorpool.cpp b/src/core/descriptorpool.cpp index 1013bc9..5a12e43 100644 --- a/src/core/descriptorpool.cpp +++ b/src/core/descriptorpool.cpp @@ -1,5 +1,5 @@ #include "core/descriptorpool.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" #include diff --git a/src/core/descriptorset.cpp b/src/core/descriptorset.cpp index 49e839f..6a83cb4 100644 --- a/src/core/descriptorset.cpp +++ b/src/core/descriptorset.cpp @@ -1,5 +1,5 @@ #include "core/descriptorset.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" using namespace Vulkan::Core; diff --git a/src/core/fence.cpp b/src/core/fence.cpp index 3108ede..ee20e08 100644 --- a/src/core/fence.cpp +++ b/src/core/fence.cpp @@ -1,5 +1,5 @@ #include "core/fence.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" using namespace Vulkan::Core; diff --git a/src/core/image.cpp b/src/core/image.cpp index d58ed95..78b5f01 100644 --- a/src/core/image.cpp +++ b/src/core/image.cpp @@ -1,5 +1,5 @@ #include "core/image.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" #include diff --git a/src/core/pipeline.cpp b/src/core/pipeline.cpp index 00a8274..b9fd68b 100644 --- a/src/core/pipeline.cpp +++ b/src/core/pipeline.cpp @@ -1,5 +1,5 @@ #include "core/pipeline.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" using namespace Vulkan::Core; diff --git a/src/core/sampler.cpp b/src/core/sampler.cpp index 2f7d5a0..5612925 100644 --- a/src/core/sampler.cpp +++ b/src/core/sampler.cpp @@ -1,5 +1,5 @@ #include "core/sampler.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" using namespace Vulkan::Core; diff --git a/src/core/semaphore.cpp b/src/core/semaphore.cpp index 510a1e9..8269477 100644 --- a/src/core/semaphore.cpp +++ b/src/core/semaphore.cpp @@ -1,5 +1,5 @@ #include "core/semaphore.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" using namespace Vulkan::Core; diff --git a/src/core/shadermodule.cpp b/src/core/shadermodule.cpp index 006832d..7880f09 100644 --- a/src/core/shadermodule.cpp +++ b/src/core/shadermodule.cpp @@ -1,5 +1,5 @@ #include "core/shadermodule.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" #include diff --git a/src/device.cpp b/src/device.cpp index 97c0208..5950d0d 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -1,5 +1,5 @@ #include "device.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" #include #include diff --git a/src/instance.cpp b/src/instance.cpp index 3b829c9..1fc90e0 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -1,5 +1,5 @@ #include "instance.hpp" -#include "utils/exceptions.hpp" +#include "utils.hpp" #include diff --git a/src/main.cpp b/src/main.cpp index e2f6c83..84cf91e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,8 +6,7 @@ #include "device.hpp" #include "instance.hpp" #include "shaderchains/downsample.hpp" -#include "utils/global.hpp" -#include "utils/upload.hpp" +#include "utils.hpp" #include @@ -48,7 +47,7 @@ int main() { VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT ); - Upload::upload(device, commandPool, inputImage, "rsc/images/source.dds"); + Utils::uploadImage(device, commandPool, inputImage, "rsc/images/source.dds"); // create the shaderchains Shaderchains::Downsample downsample(device, descriptorPool, inputImage); diff --git a/src/shaderchains/alpha.cpp b/src/shaderchains/alpha.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/shaderchains/downsample.cpp b/src/shaderchains/downsample.cpp index 347fdb9..7614100 100644 --- a/src/shaderchains/downsample.cpp +++ b/src/shaderchains/downsample.cpp @@ -1,6 +1,5 @@ #include "shaderchains/downsample.hpp" -#include "utils/global.hpp" -#include "utils/barriers.hpp" +#include "utils.hpp" using namespace Vulkan::Shaderchains; @@ -43,7 +42,7 @@ void Downsample::Dispatch(const Core::CommandBuffer& buf) { const uint32_t threadsX = (extent.width + 63) >> 6; const uint32_t threadsY = (extent.height + 63) >> 6; - Barriers::insertBarrier( + Utils::insertBarrier( buf, this->outImages, { this->inImage } diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..079b250 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,162 @@ +#include "utils.hpp" +#include "core/buffer.hpp" + +#include +#include + +using namespace Vulkan; + +void Utils::insertBarrier( + const Core::CommandBuffer& buffer, + std::vector r2wImages, + std::vector w2rImages) { + std::vector barriers(r2wImages.size() + w2rImages.size()); + + size_t index = 0; + for (auto& image : r2wImages) { + barriers[index++] = { + .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); + } + for (auto& image : w2rImages) { + barriers[index++] = { + .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); + } + const VkDependencyInfo dependencyInfo = { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .imageMemoryBarrierCount = static_cast(barriers.size()), + .pImageMemoryBarriers = barriers.data() + }; + vkCmdPipelineBarrier2(buffer.handle(), &dependencyInfo); +} + +void Utils::uploadImage(const Device& device, const Core::CommandPool& commandPool, + Core::Image& image, const std::string& path) { + // read image bytecode + std::ifstream file(path, std::ios::ate | std::ios::binary); + if (!file) + throw std::system_error(errno, std::generic_category(), "Failed to open image: " + path); + + std::streamsize size = file.tellg(); + size -= 124 - 4; // dds header and magic bytes + std::vector code(static_cast(size)); + + file.seekg(0, std::ios::beg); + if (!file.read(reinterpret_cast(code.data()), size)) + throw std::system_error(errno, std::generic_category(), "Failed to read image: " + path); + + file.close(); + + // copy data to buffer + const Core::Buffer stagingBuffer( + device, code.data(), static_cast(code.size()), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT + ); + + // perform the upload + Core::CommandBuffer commandBuffer(device, commandPool); + commandBuffer.begin(); + + const VkImageMemoryBarrier barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_NONE, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = image.getLayout(), + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = image.handle(), + .subresourceRange = { + .aspectMask = image.getAspectFlags(), + .levelCount = 1, + .layerCount = 1 + } + }; + image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + vkCmdPipelineBarrier( + commandBuffer.handle(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &barrier + ); + + auto extent = image.getExtent(); + const VkBufferImageCopy region{ + .bufferImageHeight = 0, + .imageSubresource = { + .aspectMask = image.getAspectFlags(), + .layerCount = 1 + }, + .imageExtent = { extent.width, extent.height, 1 } + }; + vkCmdCopyBufferToImage( + commandBuffer.handle(), + stagingBuffer.handle(), image.handle(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion + ); + + commandBuffer.end(); + + Core::Fence fence(device); + commandBuffer.submit(device.getComputeQueue(), fence); + + // wait for the upload to complete + if (!fence.wait(device)) + throw ls::vulkan_error(VK_TIMEOUT, "Upload operation timed out"); +} + +Core::Sampler Globals::samplerClampBorder; +Core::Sampler Globals::samplerClampEdge; +Globals::FgBuffer Globals::fgBuffer; + +void Globals::initializeGlobals(const Device& device) { + // initialize global samplers + samplerClampBorder = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); + samplerClampEdge = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); + + // initialize global constant buffer + fgBuffer = { + .inputOffset = { 0, 29 }, + .resolutionInvScale = 1.0F, + .timestamp = 0.5F, + .uiThreshold = 0.1F, + }; +} + +void Globals::uninitializeGlobals() noexcept { + // uninitialize global samplers + samplerClampBorder = Core::Sampler(); + samplerClampEdge = Core::Sampler(); + + // uninitialize global constant buffer + fgBuffer = Globals::FgBuffer(); +} + +ls::vulkan_error::vulkan_error(VkResult result, const std::string& message) + : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} + +ls::vulkan_error::~vulkan_error() noexcept = default; diff --git a/src/utils/barriers.cpp b/src/utils/barriers.cpp deleted file mode 100644 index 50d5aa7..0000000 --- a/src/utils/barriers.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "utils/barriers.hpp" - -using namespace Barriers; - -void Barriers::insertBarrier( - const Vulkan::Core::CommandBuffer& buffer, - std::vector r2wImages, - std::vector w2rImages) { - std::vector barriers(r2wImages.size() + w2rImages.size()); - - size_t index = 0; - for (auto& image : r2wImages) { - barriers[index++] = { - .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); - } - for (auto& image : w2rImages) { - barriers[index++] = { - .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); - } - const VkDependencyInfo dependencyInfo = { - .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, - .imageMemoryBarrierCount = static_cast(barriers.size()), - .pImageMemoryBarriers = barriers.data() - }; - vkCmdPipelineBarrier2(buffer.handle(), &dependencyInfo); -} diff --git a/src/utils/exceptions.cpp b/src/utils/exceptions.cpp deleted file mode 100644 index 3c41429..0000000 --- a/src/utils/exceptions.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "utils/exceptions.hpp" - -#include - -using namespace ls; - -vulkan_error::vulkan_error(VkResult result, const std::string& message) - : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} - -vulkan_error::~vulkan_error() noexcept = default; diff --git a/src/utils/globals.cpp b/src/utils/globals.cpp deleted file mode 100644 index 62609fc..0000000 --- a/src/utils/globals.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "utils/global.hpp" - -using namespace Vulkan; - -Core::Sampler Globals::samplerClampBorder; -Core::Sampler Globals::samplerClampEdge; -Globals::FgBuffer Globals::fgBuffer; - -void Globals::initializeGlobals(const Device& device) { - // initialize global samplers - samplerClampBorder = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); - samplerClampEdge = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); - - // initialize global constant buffer - fgBuffer = { - .inputOffset = { 0, 29 }, - .resolutionInvScale = 1.0F, - .timestamp = 0.5F, - .uiThreshold = 0.1F, - }; - -} - -void Globals::uninitializeGlobals() noexcept { - // uninitialize global samplers - samplerClampBorder = Core::Sampler(); - samplerClampEdge = Core::Sampler(); - - // uninitialize global constant buffer - fgBuffer = Globals::FgBuffer(); -} diff --git a/src/utils/upload.cpp b/src/utils/upload.cpp deleted file mode 100644 index 95b239a..0000000 --- a/src/utils/upload.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "utils/upload.hpp" -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "utils/exceptions.hpp" - -#include -#include -#include - -using namespace Upload; -using namespace Vulkan; - -void Upload::upload(const Device& device, const Core::CommandPool& commandPool, - Core::Image& image, const std::string& path) { - // read iamge bytecode - std::ifstream file(path, std::ios::ate | std::ios::binary); - if (!file) - throw std::system_error(errno, std::generic_category(), "Failed to open image: " + path); - - std::streamsize size = file.tellg(); - size -= 124 - 4; - std::vector code(static_cast(size)); - - file.seekg(0, std::ios::beg); - if (!file.read(reinterpret_cast(code.data()), size)) - throw std::system_error(errno, std::generic_category(), "Failed to read image: " + path); - - file.close(); - - // copy data to buffer - const Vulkan::Core::Buffer stagingBuffer( - device, code.data(), static_cast(code.size()), - VK_BUFFER_USAGE_TRANSFER_SRC_BIT - ); - - // perform the upload - Core::CommandBuffer commandBuffer(device, commandPool); - commandBuffer.begin(); - - const VkImageMemoryBarrier barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_NONE, - .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .oldLayout = image.getLayout(), - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .image = image.handle(), - .subresourceRange = { - .aspectMask = image.getAspectFlags(), - .levelCount = 1, - .layerCount = 1 - } - }; - image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - vkCmdPipelineBarrier( - commandBuffer.handle(), - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, nullptr, 0, nullptr, 1, &barrier - ); - - auto extent = image.getExtent(); - const VkBufferImageCopy region{ - .bufferImageHeight = 0, - .imageSubresource = { - .aspectMask = image.getAspectFlags(), - .layerCount = 1 - }, - .imageExtent = { extent.width, extent.height, 1 } - }; - vkCmdCopyBufferToImage( - commandBuffer.handle(), - stagingBuffer.handle(), image.handle(), - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion - ); - - commandBuffer.end(); - - Core::Fence fence(device); - commandBuffer.submit(device.getComputeQueue(), fence); - - if (!fence.wait(device)) - throw ls::vulkan_error(VK_TIMEOUT, "Upload operation timed out"); -} From c3e1d0ded9400e6ececceb4dc2e604c84e54d8dc Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 03:27:22 +0200 Subject: [PATCH 025/253] downsample & alpha shaderchain --- include/shaderchains/alpha.hpp | 67 +++++++++++++ include/shaderchains/downsample.hpp | 2 +- include/utils.hpp | 4 +- src/core/sampler.cpp | 2 +- src/main.cpp | 10 +- src/shaderchains/alpha.cpp | 141 ++++++++++++++++++++++++++++ src/shaderchains/downsample.cpp | 5 +- src/utils.cpp | 16 ++-- 8 files changed, 231 insertions(+), 16 deletions(-) diff --git a/include/shaderchains/alpha.hpp b/include/shaderchains/alpha.hpp index e69de29..84a1b7a 100644 --- a/include/shaderchains/alpha.hpp +++ b/include/shaderchains/alpha.hpp @@ -0,0 +1,67 @@ +#ifndef ALPHA_HPP +#define ALPHA_HPP + +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" + +namespace Vulkan::Shaderchains { + + /// + /// Shader chain alpha. + /// + /// Takes an 8-bit R texture creates four quarter-sized 8-bit RGBA textures. + /// + class Alpha { + public: + /// + /// Initialize the shaderchain. + /// + /// @param device The Vulkan device to create the resources on. + /// @param pool The descriptor pool to allocate in. + /// @param inImage The input texture to process + /// + /// @throws ls::vulkan_error if resource creation fails. + /// + Alpha(const Device& device, const Core::DescriptorPool& pool, + const Core::Image& inImage); + + /// + /// Dispatch the shaderchain. + /// + /// @param buf The command buffer to use for dispatching. + /// + /// @throws std::logic_error if the command buffer is not recording. + /// + void Dispatch(const Core::CommandBuffer& buf); + + /// Get the output images. + [[nodiscard]] const auto& getOutImages() const { return this->outImages; } + + /// 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::vector shaderModules{4}; + std::vector pipelines{4}; + std::vector descriptorSets{4}; + + Core::Image inImage; + + std::vector tempTex1{2}; // half-size + std::vector tempTex2{2}; // half-size + std::vector tempTex3{4}; // quarter-size + + std::vector outImages{4}; // quarter-size + }; + +} + +#endif // ALPHA_HPP diff --git a/include/shaderchains/downsample.hpp b/include/shaderchains/downsample.hpp index 9584e48..21cc8c3 100644 --- a/include/shaderchains/downsample.hpp +++ b/include/shaderchains/downsample.hpp @@ -57,7 +57,7 @@ namespace Vulkan::Shaderchains { Core::Image inImage; - std::vector outImages; + std::vector outImages{7}; }; } diff --git a/include/utils.hpp b/include/utils.hpp index 930a31e..aa968b7 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -22,8 +22,8 @@ namespace Vulkan::Utils { /// void insertBarrier( const Vulkan::Core::CommandBuffer& buffer, - std::vector r2wImages, - std::vector w2rImages); + std::vector w2rImages, + std::vector r2wImages); /// /// Upload a DDS file to a Vulkan image. diff --git a/src/core/sampler.cpp b/src/core/sampler.cpp index 5612925..0bb2e1f 100644 --- a/src/core/sampler.cpp +++ b/src/core/sampler.cpp @@ -13,7 +13,7 @@ Sampler::Sampler(const Device& device, VkSamplerAddressMode mode) { .addressModeU = mode, .addressModeV = mode, .addressModeW = mode, - .maxLod = VK_LOD_CLAMP_NONE + .maxLod = 15.99609F }; VkSampler samplerHandle{}; auto res = vkCreateSampler(device.handle(), &desc, nullptr, &samplerHandle); diff --git a/src/main.cpp b/src/main.cpp index 84cf91e..f16aedb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include "core/image.hpp" #include "device.hpp" #include "instance.hpp" +#include "shaderchains/alpha.hpp" #include "shaderchains/downsample.hpp" #include "utils.hpp" @@ -13,7 +14,7 @@ #include #include #include -#include +#include using namespace Vulkan; @@ -52,6 +53,11 @@ int main() { // create the shaderchains Shaderchains::Downsample downsample(device, descriptorPool, inputImage); + std::vector alphas; + alphas.reserve(7); + for (size_t i = 0; i < 7; ++i) + alphas.emplace_back(device, descriptorPool, downsample.getOutImages().at(i)); + // start the rendering pipeline if (rdoc) rdoc->StartFrameCapture(nullptr, nullptr); @@ -60,6 +66,8 @@ int main() { commandBuffer.begin(); downsample.Dispatch(commandBuffer); + for (size_t i = 0; i < 7; i++) + alphas.at(6 - i).Dispatch(commandBuffer); // finish the rendering pipeline commandBuffer.end(); diff --git a/src/shaderchains/alpha.cpp b/src/shaderchains/alpha.cpp index e69de29..b99c4b1 100644 --- a/src/shaderchains/alpha.cpp +++ b/src/shaderchains/alpha.cpp @@ -0,0 +1,141 @@ +#include "shaderchains/alpha.hpp" +#include "utils.hpp" + +using namespace Vulkan::Shaderchains; + +Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, + const Core::Image& inImage) : inImage(inImage) { + this->shaderModules = {{ + Core::ShaderModule(device, "rsc/shaders/alpha/0.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + Core::ShaderModule(device, "rsc/shaders/alpha/1.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + Core::ShaderModule(device, "rsc/shaders/alpha/2.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + Core::ShaderModule(device, "rsc/shaders/alpha/3.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + for (size_t i = 0; i < 4; i++) { + this->pipelines.at(i) = Core::Pipeline(device, + this->shaderModules.at(i)); + this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, + this->shaderModules.at(i)); + } + + auto extent = inImage.getExtent(); + auto halfWidth = (extent.width + 1) >> 1; + auto halfHeight = (extent.height + 1) >> 1; + auto quarterWidth = (extent.width + 3) >> 2; + auto quarterHeight = (extent.height + 3) >> 2; + + for (size_t i = 0; i < 2; i++) { + this->tempTex1.at(i) = Core::Image(device, + { halfWidth, halfHeight }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + this->tempTex2.at(i) = Core::Image(device, + { halfWidth, halfHeight }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + } + for (size_t i = 0; i < 4; i++) { + this->tempTex3.at(i) = Core::Image(device, + { quarterWidth, quarterHeight }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + this->outImages.at(i) = Core::Image(device, + { quarterWidth, quarterHeight }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + } + + this->descriptorSets.at(0).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inImage) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempTex1) + .build(); + this->descriptorSets.at(1).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempTex1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempTex2) + .build(); + this->descriptorSets.at(2).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempTex2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempTex3) + .build(); + this->descriptorSets.at(3).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempTex3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImages) + .build(); +} + +void Alpha::Dispatch(const Core::CommandBuffer& buf) { + const auto halfExtent = this->tempTex1.at(0).getExtent(); + const auto quarterExtent = this->tempTex3.at(0).getExtent(); + + // first pass + Utils::insertBarrier( + buf, + { this->inImage }, + this->tempTex1 + ); + + this->pipelines.at(0).bind(buf); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(0)); + + uint32_t threadsX = (halfExtent.width + 7) >> 3; + uint32_t threadsY = (halfExtent.height + 7) >> 3; + buf.dispatch(threadsX, threadsY, 1); + + // second pass + Utils::insertBarrier( + buf, + this->tempTex1, + this->tempTex2 + ); + + this->pipelines.at(1).bind(buf); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(1)); + + buf.dispatch(threadsX, threadsY, 1); + + // third pass + Utils::insertBarrier( + buf, + this->tempTex2, + this->tempTex3 + ); + + this->pipelines.at(2).bind(buf); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(2)); + + threadsX = (quarterExtent.width + 7) >> 3; + threadsY = (quarterExtent.height + 7) >> 3; + buf.dispatch(threadsX, threadsY, 1); + + // fourth pass + Utils::insertBarrier( + buf, + this->tempTex3, + this->outImages + ); + + this->pipelines.at(3).bind(buf); + this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); + + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/src/shaderchains/downsample.cpp b/src/shaderchains/downsample.cpp index 7614100..d6dc2ea 100644 --- a/src/shaderchains/downsample.cpp +++ b/src/shaderchains/downsample.cpp @@ -20,7 +20,6 @@ Downsample::Downsample(const Device& device, const Core::DescriptorPool& pool, auto extent = inImage.getExtent(); // create output images - this->outImages.resize(7); for (size_t i = 0; i < 7; i++) this->outImages.at(i) = Core::Image(device, { extent.width >> i, extent.height >> i }, @@ -44,8 +43,8 @@ void Downsample::Dispatch(const Core::CommandBuffer& buf) { Utils::insertBarrier( buf, - this->outImages, - { this->inImage } + { this->inImage }, + this->outImages ); this->pipeline.bind(buf); diff --git a/src/utils.cpp b/src/utils.cpp index 079b250..bb03b3c 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -8,8 +8,8 @@ using namespace Vulkan; void Utils::insertBarrier( const Core::CommandBuffer& buffer, - std::vector r2wImages, - std::vector w2rImages) { + std::vector w2rImages, + std::vector r2wImages) { std::vector barriers(r2wImages.size() + w2rImages.size()); size_t index = 0; @@ -60,16 +60,16 @@ void Utils::insertBarrier( void Utils::uploadImage(const Device& device, const Core::CommandPool& commandPool, Core::Image& image, const std::string& path) { // read image bytecode - std::ifstream file(path, std::ios::ate | std::ios::binary); - if (!file) + std::ifstream file(path.data(), std::ios::binary | std::ios::ate); + if (!file.is_open()) throw std::system_error(errno, std::generic_category(), "Failed to open image: " + path); std::streamsize size = file.tellg(); - size -= 124 - 4; // dds header and magic bytes - std::vector code(static_cast(size)); + size -= 124 + 4; // dds header and magic bytes + std::vector code(static_cast(size)); - file.seekg(0, std::ios::beg); - if (!file.read(reinterpret_cast(code.data()), 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(); From 33e11eef3e51dfcc4a3baf1e1ccd79a4bf56d32d Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 04:57:55 +0200 Subject: [PATCH 026/253] shaderchain draft headers --- include/shaderchains/alpha.hpp | 18 +++--- include/shaderchains/beta.hpp | 72 +++++++++++++++++++++++ include/shaderchains/delta.hpp | 72 +++++++++++++++++++++++ include/shaderchains/downsample.hpp | 12 ++-- include/shaderchains/epsilon.hpp | 75 ++++++++++++++++++++++++ include/shaderchains/extract.hpp | 74 ++++++++++++++++++++++++ include/shaderchains/gamma.hpp | 89 +++++++++++++++++++++++++++++ include/shaderchains/magic.hpp | 84 +++++++++++++++++++++++++++ include/shaderchains/merge.hpp | 78 +++++++++++++++++++++++++ include/shaderchains/zeta.hpp | 75 ++++++++++++++++++++++++ 10 files changed, 634 insertions(+), 15 deletions(-) create mode 100644 include/shaderchains/beta.hpp create mode 100644 include/shaderchains/delta.hpp create mode 100644 include/shaderchains/epsilon.hpp create mode 100644 include/shaderchains/extract.hpp create mode 100644 include/shaderchains/gamma.hpp create mode 100644 include/shaderchains/magic.hpp create mode 100644 include/shaderchains/merge.hpp create mode 100644 include/shaderchains/zeta.hpp diff --git a/include/shaderchains/alpha.hpp b/include/shaderchains/alpha.hpp index 84a1b7a..1f0cbb1 100644 --- a/include/shaderchains/alpha.hpp +++ b/include/shaderchains/alpha.hpp @@ -14,7 +14,7 @@ namespace Vulkan::Shaderchains { /// /// Shader chain alpha. /// - /// Takes an 8-bit R texture creates four quarter-sized 8-bit RGBA textures. + /// Takes an 8-bit R image creates four quarter-sized 8-bit RGBA images. /// class Alpha { public: @@ -23,12 +23,12 @@ namespace Vulkan::Shaderchains { /// /// @param device The Vulkan device to create the resources on. /// @param pool The descriptor pool to allocate in. - /// @param inImage The input texture to process + /// @param inImg The input image to process /// /// @throws ls::vulkan_error if resource creation fails. /// Alpha(const Device& device, const Core::DescriptorPool& pool, - const Core::Image& inImage); + const Core::Image& inImg); /// /// Dispatch the shaderchain. @@ -40,7 +40,7 @@ namespace Vulkan::Shaderchains { void Dispatch(const Core::CommandBuffer& buf); /// Get the output images. - [[nodiscard]] const auto& getOutImages() const { return this->outImages; } + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } /// Trivially copyable, moveable and destructible Alpha(const Alpha&) noexcept = default; @@ -53,13 +53,13 @@ namespace Vulkan::Shaderchains { std::vector pipelines{4}; std::vector descriptorSets{4}; - Core::Image inImage; + Core::Image inImg; - std::vector tempTex1{2}; // half-size - std::vector tempTex2{2}; // half-size - std::vector tempTex3{4}; // quarter-size + std::vector tempImgs1{2}; // half-size + std::vector tempImgs2{2}; // half-size + std::vector tempImgs3{4}; // quarter-size - std::vector outImages{4}; // quarter-size + std::vector outImgs{4}; // quarter-size }; } diff --git a/include/shaderchains/beta.hpp b/include/shaderchains/beta.hpp new file mode 100644 index 0000000..e9efe7d --- /dev/null +++ b/include/shaderchains/beta.hpp @@ -0,0 +1,72 @@ +#ifndef BETA_HPP +#define BETA_HPP + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" + +namespace Vulkan::Shaderchains { + + /// + /// Shader chain beta. + /// + /// Takes eight temporal 8-bit RGBA images, as well as the four output images from alpha, + /// and creates six 8-bit R images, halving in resolution each step. + /// + class Beta { + public: + /// + /// Initialize the shaderchain. + /// + /// @param device The Vulkan device to create the resources on. + /// @param pool The descriptor pool to allocate in. + /// @param temporalImgs The temporal images to use for processing. + /// @param inImgs The input images to process + /// + /// @throws ls::vulkan_error if resource creation fails. + /// + Beta(const Device& device, const Core::DescriptorPool& pool, + const std::vector& temporalImgs, + const std::vector& inImgs); + + /// + /// Dispatch the shaderchain. + /// + /// @param buf The command buffer to use for dispatching. + /// + /// @throws std::logic_error if the command buffer is not recording. + /// + void Dispatch(const Core::CommandBuffer& buf); + + /// 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::vector shaderModules{5}; + std::vector pipelines{5}; + std::vector descriptorSets{5}; + Core::Buffer buffer; + + std::vector temporalImgs{8}; + std::vector inImgs{4}; + + std::vector tempImgs1{2}; + std::vector tempImgs2{2}; + + std::vector outImgs{6}; + }; + +} + +#endif // BETA_HPP diff --git a/include/shaderchains/delta.hpp b/include/shaderchains/delta.hpp new file mode 100644 index 0000000..4a5d7e1 --- /dev/null +++ b/include/shaderchains/delta.hpp @@ -0,0 +1,72 @@ +#ifndef DELTA_HPP +#define DELTA_HPP + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" + +namespace Vulkan::Shaderchains { + + /// + /// Shader chain delta. + /// + /// Takes two 8-bit RGBA images and an optional third 16-bit half-res RGBA image, + /// producing a full-res 16-bit RGBA image. + /// + class Delta { + public: + /// + /// Initialize the shaderchain. + /// + /// @param device The Vulkan device to create the resources on. + /// @param pool The descriptor pool to allocate in. + /// @param inImgs The input images to process. + /// @param optImg An optional additional input from the previous pass. + /// + /// @throws ls::vulkan_error if resource creation fails. + /// + Delta(const Device& device, const Core::DescriptorPool& pool, + const std::vector& inImgs, + const std::optional& optImg); + + /// + /// Dispatch the shaderchain. + /// + /// @param buf The command buffer to use for dispatching. + /// + /// @throws std::logic_error if the command buffer is not recording. + /// + void Dispatch(const Core::CommandBuffer& buf); + + /// Get the output image. + [[nodiscard]] const auto& getOutImage() const { return this->outImg; } + + /// 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::vector shaderModules{4}; + std::vector pipelines{4}; + std::vector descriptorSets{4}; + Core::Buffer buffer; + + std::vector inImg{2}; + std::optional optImg; + + std::vector tempImgs1{2}; + std::vector tempImgs2{2}; + + Core::Image outImg; + }; + +} + +#endif // DELTA_HPP diff --git a/include/shaderchains/downsample.hpp b/include/shaderchains/downsample.hpp index 21cc8c3..0f9c523 100644 --- a/include/shaderchains/downsample.hpp +++ b/include/shaderchains/downsample.hpp @@ -15,7 +15,7 @@ namespace Vulkan::Shaderchains { /// /// Downsample shader. /// - /// Takes an 8-bit RGBA texture and downsamples it into 7x 8-bit R textures. + /// Takes an 8-bit RGBA image and downsamples it into 7x 8-bit R images. /// class Downsample { public: @@ -24,12 +24,12 @@ namespace Vulkan::Shaderchains { /// /// @param device The Vulkan device to create the resources on. /// @param pool The descriptor pool to allocate in. - /// @param inImage The input image to downsample. + /// @param inImg The input image to downsample. /// /// @throws ls::vulkan_error if resource creation fails. /// Downsample(const Device& device, const Core::DescriptorPool& pool, - const Core::Image& inImage); + const Core::Image& inImg); /// /// Dispatch the shaderchain. @@ -41,7 +41,7 @@ namespace Vulkan::Shaderchains { void Dispatch(const Core::CommandBuffer& buf); /// Get the output images. - [[nodiscard]] const auto& getOutImages() const { return this->outImages; } + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } /// Trivially copyable, moveable and destructible Downsample(const Downsample&) noexcept = default; @@ -55,9 +55,9 @@ namespace Vulkan::Shaderchains { Core::DescriptorSet descriptorSet; Core::Buffer buffer; - Core::Image inImage; + Core::Image inImg; - std::vector outImages{7}; + std::vector outImgs{7}; }; } diff --git a/include/shaderchains/epsilon.hpp b/include/shaderchains/epsilon.hpp new file mode 100644 index 0000000..b875a26 --- /dev/null +++ b/include/shaderchains/epsilon.hpp @@ -0,0 +1,75 @@ +#ifndef EPSILON_HPP +#define EPSILON_HPP + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" + +namespace Vulkan::Shaderchains { + + /// + /// Shader chain epsilon. + /// + /// Takes three 8-bit RGBA textures, a fourth 8-bit R texture, an optional fifth + /// half-res 16-bit RGBA texture and produces a full-res 16-bit RGBA texture. + /// + class Epsilon { + public: + /// + /// Initialize the shaderchain. + /// + /// @param device The Vulkan device to create the resources on. + /// @param pool The descriptor pool to use for descriptor sets. + /// @param inImgs1 The first set of input images to process. + /// @param inImg2 The second type image to process. + /// @param optImg An optional additional input from the previous pass. + /// + /// @throws ls::vulkan_error if resource creation fails. + /// + Epsilon(const Device& device, const Core::DescriptorPool& pool, + const std::vector& inImgs1, + const Core::Image& inImg2, + const std::optional& optImg); + + /// + /// Dispatch the shaderchain. + /// + /// @param buf The command buffer to use for dispatching. + /// + /// @throws std::logic_error if the command buffer is not recording. + /// + void Dispatch(const Core::CommandBuffer& buf); + + /// Get the output image. + [[nodiscard]] const auto& getOutImage() const { return this->outImg; } + + /// Trivially copyable, moveable and destructible + Epsilon(const Epsilon&) noexcept = default; + Epsilon& operator=(const Epsilon&) noexcept = default; + Epsilon(Epsilon&&) noexcept = default; + Epsilon& operator=(Epsilon&&) noexcept = default; + ~Epsilon() = default; + private: + std::vector shaderModules{4}; + std::vector pipelines{4}; + std::vector descriptorSets{4}; + Core::Buffer buffer; + + std::vector inImgs1{3}; + Core::Image inImg2; + std::optional optImg; + + std::vector tempImgs1{4}; + std::vector tempImgs2{4}; + + Core::Image outImg; + }; + +} + +#endif // EPSILON_HPP diff --git a/include/shaderchains/extract.hpp b/include/shaderchains/extract.hpp new file mode 100644 index 0000000..b8677de --- /dev/null +++ b/include/shaderchains/extract.hpp @@ -0,0 +1,74 @@ +#ifndef EXTRACT_HPP +#define EXTRACT_HPP + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" +#include + +namespace Vulkan::Shaderchains { + + /// + /// Shader chain extract. + /// + /// Takes two half-res 16-bit RGBA textures, producing + /// an full-res 8-bit RGBA texture. + /// + class Extract { + public: + /// + /// Initialize the shaderchain. + /// + /// @param device The Vulkan device to create the resources on. + /// @param pool The descriptor pool to use for descriptor sets. + /// @param inImg1 The first set of input images to process. + /// @param inImg2 The second type image to process. + /// @param outExtent The extent of the output image. + /// + /// @throws ls::vulkan_error if resource creation fails. + /// + Extract(const Device& device, const Core::DescriptorPool& pool, + const Core::Image& inImg1, + const Core::Image& inImg2, + VkExtent2D outExtent); + + /// + /// Dispatch the shaderchain. + /// + /// @param buf The command buffer to use for dispatching. + /// + /// @throws std::logic_error if the command buffer is not recording. + /// + void Dispatch(const Core::CommandBuffer& buf); + + /// Get the output image. + [[nodiscard]] const auto& getOutImage() const { return this->outImg; } + + /// Trivially copyable, moveable and destructible + Extract(const Extract&) noexcept = default; + Extract& operator=(const Extract&) noexcept = default; + Extract(Extract&&) noexcept = default; + Extract& operator=(Extract&&) noexcept = default; + ~Extract() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + Core::DescriptorSet descriptorSet; + Core::Buffer buffer; + + Core::Image inImg1; + Core::Image inImg2; + + Core::Image whiteImg; + + Core::Image outImg; + }; + +} + +#endif // EXTRACT_HPP diff --git a/include/shaderchains/gamma.hpp b/include/shaderchains/gamma.hpp new file mode 100644 index 0000000..693a16d --- /dev/null +++ b/include/shaderchains/gamma.hpp @@ -0,0 +1,89 @@ +#ifndef GAMMA_HPP +#define GAMMA_HPP + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" + +namespace Vulkan::Shaderchains { + + /// + /// Shader chain gamma. + /// + /// Takes four temporal 8-bit RGBA images, as well as four output images from a given alpha stage. + /// Also takes the corresponding (smallest if oob) output image from the beta pass. + /// On non-first passes optionally takes 2 output images from previous gamma pass. + /// Creates two images, one at twice the resolution of input images and the other with R16G16B16A16_FLOAT. + /// + class Gamma { + public: + /// + /// Initialize the shaderchain. + /// + /// @param device The Vulkan device to create the resources on. + /// @param pool The descriptor pool to allocate in. + /// @param temporalImgs The temporal images to use for processing. + /// @param inImgs1 The input images to process. + /// @param inImg2 The second input image to process, next step up the resolution. + /// @param optImg1 An optional additional input from the previous pass. + /// @param optImg2 An optional additional input image for processing non-first passes. + /// @param outExtent The extent of the output image. + /// + /// @throws ls::vulkan_error if resource creation fails. + /// + Gamma(const Device& device, const Core::DescriptorPool& pool, + const std::vector& temporalImgs, + const std::vector& inImgs1, + const Core::Image& inImg2, + const std::optional& optImg1, + const std::optional& optImg2, + VkExtent2D outExtent); + + /// + /// Dispatch the shaderchain. + /// + /// @param buf The command buffer to use for dispatching. + /// + /// @throws std::logic_error if the command buffer is not recording. + /// + void Dispatch(const Core::CommandBuffer& buf); + + /// 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 + Gamma(const Gamma&) noexcept = default; + Gamma& operator=(const Gamma&) noexcept = default; + Gamma(Gamma&&) noexcept = default; + Gamma& operator=(Gamma&&) noexcept = default; + ~Gamma() = default; + private: + std::vector shaderModules{6}; + std::vector pipelines{6}; + std::vector descriptorSets{6}; + Core::Buffer buffer; + + std::vector temporalImgs{4}; + std::vector inImgs1{4}; + Core::Image inImg2; + Core::Image optImg1; // specified or created black + std::optional optImg2; + + std::vector tempImgs1{4}; + std::vector tempImgs2{4}; + Core::Image whiteImg; + + Core::Image outImg1; + Core::Image outImg2; + }; + +} + +#endif // GAMMA_HPP diff --git a/include/shaderchains/magic.hpp b/include/shaderchains/magic.hpp new file mode 100644 index 0000000..188df72 --- /dev/null +++ b/include/shaderchains/magic.hpp @@ -0,0 +1,84 @@ +#ifndef MAGIC_HPP +#define MAGIC_HPP + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" + +namespace Vulkan::Shaderchains { + + /// + /// Shader chain magic. + /// + /// Takes textures similar to gamma shader chain, produces intermediary + /// results in groups of 3, 2, 2. + /// + class Magic { + public: + /// + /// Initialize the shaderchain. + /// + /// @param device The Vulkan device to create the resources on. + /// @param pool The descriptor pool to use for descriptor sets. + /// @param temporalImgs The temporal images to use for processing. + /// @param inImgs1 The first set of input images to process. + /// @param inImg2 The second input image to process. + /// @param inImg3 The third input image to process, next step up the resolution. + /// @param optImg An optional additional input from the previous pass. + /// + /// @throws ls::vulkan_error if resource creation fails. + /// + Magic(const Device& device, const Core::DescriptorPool& pool, + const std::array& temporalImgs, + const std::array& inImgs1, + const Core::Image& inImg2, + const Core::Image& inImg3, + const std::optional& optImg); + + /// + /// Dispatch the shaderchain. + /// + /// @param buf The command buffer to use for dispatching. + /// + /// @throws std::logic_error if the command buffer is not recording. + /// + void Dispatch(const Core::CommandBuffer& buf); + + /// Get the first set of output images + [[nodiscard]] const auto& getOutImages1() const { return this->outImgs1; } + /// Get the second set of output images + [[nodiscard]] const auto& getOutImages2() const { return this->outImgs2; } + /// Get the third set of output images + [[nodiscard]] const auto& getOutImages3() const { return this->outImgs3; } + + /// Trivially copyable, moveable and destructible + Magic(const Magic&) noexcept = default; + Magic& operator=(const Magic&) noexcept = default; + Magic(Magic&&) noexcept = default; + Magic& operator=(Magic&&) noexcept = default; + ~Magic() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + Core::DescriptorSet descriptorSet; + Core::Buffer buffer; + + std::vector temporalImgs{4}; + std::vector inImgs1{4}; + Core::Image inImg2; + Core::Image inImg3; + std::optional optImg; + + std::vector outImgs1{3}; + std::vector outImgs2{3}; + std::vector outImgs3{3}; + }; + +} + +#endif // MAGIC_HPP diff --git a/include/shaderchains/merge.hpp b/include/shaderchains/merge.hpp new file mode 100644 index 0000000..520cd47 --- /dev/null +++ b/include/shaderchains/merge.hpp @@ -0,0 +1,78 @@ +#ifndef MERGE_HPP +#define MERGE_HPP + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" + +namespace Vulkan::Shaderchains { + + /// + /// Shader chain merge. + /// + /// Takes the two previous frames as well as related resources + /// and merges them into a new frame. + /// + class Merge { + public: + /// + /// Initialize the shaderchain. + /// + /// @param device The Vulkan device to create the resources on. + /// @param pool The descriptor pool to use for descriptor sets. + /// @param inImg1 The first frame texture + /// @param inImg2 The second frame texture + /// @param inImg3 The first related input texture + /// @param inImg4 The second related input texture + /// @param inImg5 The third related input texture + /// + /// @throws ls::vulkan_error if resource creation fails. + /// + Merge(const Device& device, const Core::DescriptorPool& pool, + const Core::Image& inImg1, + const Core::Image& inImg2, + const Core::Image& inImg3, + const Core::Image& inImg4, + const Core::Image& inImg5); + + /// + /// Dispatch the shaderchain. + /// + /// @param buf The command buffer to use for dispatching. + /// + /// @throws std::logic_error if the command buffer is not recording. + /// + void Dispatch(const Core::CommandBuffer& buf); + + /// Get the output image + [[nodiscard]] const auto& getOutImage() const { return this->outImg; } + + /// Trivially copyable, moveable and destructible + Merge(const Merge&) noexcept = default; + Merge& operator=(const Merge&) noexcept = default; + Merge(Merge&&) noexcept = default; + Merge& operator=(Merge&&) noexcept = default; + ~Merge() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + Core::DescriptorSet descriptorSet; + Core::Buffer buffer; + + Core::Image inImg1; + Core::Image inImg2; + Core::Image inImg3; + Core::Image inImg4; + Core::Image inImg5; + + Core::Image outImg; + }; + +} + +#endif // MERGE_HPP diff --git a/include/shaderchains/zeta.hpp b/include/shaderchains/zeta.hpp new file mode 100644 index 0000000..e0847d1 --- /dev/null +++ b/include/shaderchains/zeta.hpp @@ -0,0 +1,75 @@ +#ifndef ZETA_HPP +#define ZETA_HPP + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "device.hpp" + +namespace Vulkan::Shaderchains { + + /// + /// Shader chain zeta. + /// + /// Takes three 8-bit RGBA textures, a fourth 8-bit R texture, a fifth + /// half-res 16-bit RGBA texture and produces a full-res 16-bit RGBA texture. + /// + class Zeta { + public: + /// + /// Initialize the shaderchain. + /// + /// @param device The Vulkan device to create the resources on. + /// @param pool The descriptor pool to use for descriptor sets. + /// @param inImgs1 The first set of input images to process. + /// @param inImg2 The second type image to process. + /// @param inImg3 The third type image to process. + /// + /// @throws ls::vulkan_error if resource creation fails. + /// + Zeta(const Device& device, const Core::DescriptorPool& pool, + const std::vector& inImgs1, + const Core::Image& inImg2, + const Core::Image& inImg3); + + /// + /// Dispatch the shaderchain. + /// + /// @param buf The command buffer to use for dispatching. + /// + /// @throws std::logic_error if the command buffer is not recording. + /// + void Dispatch(const Core::CommandBuffer& buf); + + /// Get the output image. + [[nodiscard]] const auto& getOutImage() const { return this->outImg; } + + /// Trivially copyable, moveable and destructible + Zeta(const Zeta&) noexcept = default; + Zeta& operator=(const Zeta&) noexcept = default; + Zeta(Zeta&&) noexcept = default; + Zeta& operator=(Zeta&&) noexcept = default; + ~Zeta() = default; + private: + std::vector shaderModules{4}; + std::vector pipelines{4}; + std::vector descriptorSets{4}; + Core::Buffer buffer; + + std::vector inImgs1{3}; + Core::Image inImg2; + Core::Image inImg3; + + std::vector tempImgs1{4}; + std::vector tempImgs2{4}; + + Core::Image outImg; + }; + +} + +#endif // ZETA_HPP From 50f5153374a7ea89eb1daac957aff6eb8a016ab4 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 05:27:41 +0200 Subject: [PATCH 027/253] initial extract, magic & merge shaderchains --- include/shaderchains/extract.hpp | 4 +- include/shaderchains/magic.hpp | 8 +-- include/shaderchains/merge.hpp | 10 ++-- src/shaderchains/extract.cpp | 65 ++++++++++++++++++++++++ src/shaderchains/magic.cpp | 84 ++++++++++++++++++++++++++++++++ src/shaderchains/merge.cpp | 69 ++++++++++++++++++++++++++ 6 files changed, 229 insertions(+), 11 deletions(-) create mode 100644 src/shaderchains/extract.cpp create mode 100644 src/shaderchains/magic.cpp create mode 100644 src/shaderchains/merge.cpp diff --git a/include/shaderchains/extract.hpp b/include/shaderchains/extract.hpp index b8677de..e70a8e0 100644 --- a/include/shaderchains/extract.hpp +++ b/include/shaderchains/extract.hpp @@ -33,8 +33,8 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Extract(const Device& device, const Core::DescriptorPool& pool, - const Core::Image& inImg1, - const Core::Image& inImg2, + Core::Image inImg1, + Core::Image inImg2, VkExtent2D outExtent); /// diff --git a/include/shaderchains/magic.hpp b/include/shaderchains/magic.hpp index 188df72..2d5f1cb 100644 --- a/include/shaderchains/magic.hpp +++ b/include/shaderchains/magic.hpp @@ -34,10 +34,10 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Magic(const Device& device, const Core::DescriptorPool& pool, - const std::array& temporalImgs, - const std::array& inImgs1, - const Core::Image& inImg2, - const Core::Image& inImg3, + const std::vector& temporalImgs, + const std::vector& inImgs1, + Core::Image inImg2, + Core::Image inImg3, const std::optional& optImg); /// diff --git a/include/shaderchains/merge.hpp b/include/shaderchains/merge.hpp index 520cd47..aeb229a 100644 --- a/include/shaderchains/merge.hpp +++ b/include/shaderchains/merge.hpp @@ -34,11 +34,11 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Merge(const Device& device, const Core::DescriptorPool& pool, - const Core::Image& inImg1, - const Core::Image& inImg2, - const Core::Image& inImg3, - const Core::Image& inImg4, - const Core::Image& inImg5); + Core::Image inImg1, + Core::Image inImg2, + Core::Image inImg3, + Core::Image inImg4, + Core::Image inImg5); /// /// Dispatch the shaderchain. diff --git a/src/shaderchains/extract.cpp b/src/shaderchains/extract.cpp new file mode 100644 index 0000000..3b12f92 --- /dev/null +++ b/src/shaderchains/extract.cpp @@ -0,0 +1,65 @@ +#include "shaderchains/extract.hpp" +#include "utils.hpp" +#include + +using namespace Vulkan::Shaderchains; + +Extract::Extract(const Device& device, const Core::DescriptorPool& pool, + Core::Image inImg1, + Core::Image inImg2, + VkExtent2D outExtent) + : inImg1(std::move(inImg1)), + inImg2(std::move(inImg2)) { + // create internal resources + this->shaderModule = Core::ShaderModule(device, "rsc/shaders/extract.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); + this->pipeline = Core::Pipeline(device, this->shaderModule); + this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); + + const Globals::FgBuffer data = Globals::fgBuffer; + this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + this->whiteImg = Core::Image(device, + { outExtent.width, outExtent.height }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + + // create output images + this->outImg = Core::Image(device, + { outExtent.width, outExtent.height }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + + // update descriptor set + this->descriptorSet.update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); +} + +void Extract::Dispatch(const Core::CommandBuffer& buf) { + auto extent = this->whiteImg.getExtent(); + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + // FIXME: clear to white + + Utils::insertBarrier( + buf, + { this->whiteImg, this->inImg1, this->inImg2 }, + { this->outImg } + ); + + this->pipeline.bind(buf); + this->descriptorSet.bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/src/shaderchains/magic.cpp b/src/shaderchains/magic.cpp new file mode 100644 index 0000000..3f641d4 --- /dev/null +++ b/src/shaderchains/magic.cpp @@ -0,0 +1,84 @@ +#include "shaderchains/magic.hpp" +#include "utils.hpp" + +using namespace Vulkan::Shaderchains; + +Magic::Magic(const Device& device, const Core::DescriptorPool& pool, + const std::vector& temporalImgs, + const std::vector& inImgs1, + Core::Image inImg2, + Core::Image inImg3, + const std::optional& optImg) + : temporalImgs(temporalImgs), inImgs1(inImgs1), + inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), optImg(optImg) { + // create internal resources + this->shaderModule = Core::ShaderModule(device, "rsc/shaders/magic.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4+4+2+1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 3+3+2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); + this->pipeline = Core::Pipeline(device, this->shaderModule); + this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); + + Globals::FgBuffer data = Globals::fgBuffer; + data.firstIterS = !optImg.has_value(); + this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + auto extent = temporalImgs.at(0).getExtent(); + + // create output images + for (size_t i = 0; i < 2; i++) + this->outImgs1.at(i) = Core::Image(device, + { extent.width, extent.height }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + for (size_t i = 0; i < 3; i++) + this->outImgs2.at(i) = Core::Image(device, + { extent.width, extent.height }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + for (size_t i = 0; i < 3; i++) + this->outImgs3.at(i) = Core::Image(device, + { extent.width, extent.height }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + + // update descriptor set + this->descriptorSet.update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->temporalImgs) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *this->optImg) // FIXME: invalid resource + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); +} + +void Magic::Dispatch(const Core::CommandBuffer& buf) { + auto extent = this->temporalImgs.at(0).getExtent(); + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::insertBarrier( + buf, + { this->temporalImgs.at(0), this->temporalImgs.at(1), + this->temporalImgs.at(2), this->temporalImgs.at(3), + this->inImgs1.at(0), this->inImgs1.at(1), + this->inImgs1.at(2), this->inImgs1.at(3), + this->inImg2, this->inImg3, *this->optImg }, // FIXME: invalid resource + { this->outImgs3.at(0), this->outImgs3.at(1), this->outImgs3.at(2), + this->outImgs2.at(0), this->outImgs2.at(1), this->outImgs2.at(2), + this->outImgs1.at(0), this->outImgs1.at(1) } + ); + + this->pipeline.bind(buf); + this->descriptorSet.bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/src/shaderchains/merge.cpp b/src/shaderchains/merge.cpp new file mode 100644 index 0000000..590779b --- /dev/null +++ b/src/shaderchains/merge.cpp @@ -0,0 +1,69 @@ +#include "shaderchains/merge.hpp" +#include "utils.hpp" +#include + +using namespace Vulkan::Shaderchains; + +Merge::Merge(const Device& device, const Core::DescriptorPool& pool, + Core::Image inImg1, + Core::Image inImg2, + Core::Image inImg3, + Core::Image inImg4, + Core::Image inImg5) + : inImg1(std::move(inImg1)), + inImg2(std::move(inImg2)), + inImg3(std::move(inImg3)), + inImg4(std::move(inImg4)), + inImg5(std::move(inImg5)) { + // create internal resources + this->shaderModule = Core::ShaderModule(device, "rsc/shaders/merge.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); + this->pipeline = Core::Pipeline(device, this->shaderModule); + this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); + + const Globals::FgBuffer data = Globals::fgBuffer; + this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + auto extent = this->inImg1.getExtent(); + + // create output image + this->outImg = Core::Image(device, + { extent.width, extent.height }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + + // update descriptor set + this->descriptorSet.update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 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->outImg) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); +} + +void Merge::Dispatch(const Core::CommandBuffer& buf) { + auto extent = this->inImg1.getExtent(); + const uint32_t threadsX = (extent.width + 15) >> 4; + const uint32_t threadsY = (extent.height + 15) >> 4; + + // FIXME: clear to white + + Utils::insertBarrier( + buf, + { this->inImg1, this->inImg2, this->inImg3, + this->inImg4, this->inImg5 }, + { this->outImg } + ); + + this->pipeline.bind(buf); + this->descriptorSet.bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} From faff05fb4f1ca1a5cb3d459c19a5f9b9e199c21d Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 06:52:42 +0200 Subject: [PATCH 028/253] more refactors and fixes --- include/core/descriptorset.hpp | 9 ++ include/shaderchains/alpha.hpp | 18 ++-- include/shaderchains/beta.hpp | 22 +++-- include/shaderchains/delta.hpp | 18 ++-- include/shaderchains/downsample.hpp | 6 +- include/shaderchains/epsilon.hpp | 20 ++-- include/shaderchains/extract.hpp | 1 - include/shaderchains/gamma.hpp | 26 +++--- include/shaderchains/magic.hpp | 18 ++-- include/shaderchains/zeta.hpp | 20 ++-- include/utils.hpp | 64 ++++++++++--- src/core/descriptorset.cpp | 13 +++ src/shaderchains/alpha.cpp | 104 ++++++++++----------- src/shaderchains/downsample.cpp | 32 +++---- src/shaderchains/extract.cpp | 31 +++---- src/shaderchains/magic.cpp | 46 +++++----- src/shaderchains/merge.cpp | 27 +++--- src/utils.cpp | 137 +++++++++++++++++++--------- 18 files changed, 364 insertions(+), 248 deletions(-) diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp index 6ca32f3..3bbacbb 100644 --- a/include/core/descriptorset.hpp +++ b/include/core/descriptorset.hpp @@ -77,6 +77,7 @@ namespace Vulkan::Core { DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Image& image); DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Sampler& sampler); DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Buffer& buffer); + DescriptorSetUpdateBuilder& add(VkDescriptorType type); // empty entry /// Add a list of resources to the descriptor set update. DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& images) { @@ -97,6 +98,14 @@ namespace Vulkan::Core { DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array& buffers) { for (const auto& buffer : buffers) this->add(type, buffer); return *this; } + /// Add an optional resource to the descriptor set update. + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& image) { + if (image.has_value()) this->add(type, *image); return *this; } + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& sampler) { + if (sampler.has_value()) this->add(type, *sampler); return *this; } + DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& buffer) { + if (buffer.has_value()) this->add(type, *buffer); return *this; } + /// Finish building the descriptor set update. void build() const; private: diff --git a/include/shaderchains/alpha.hpp b/include/shaderchains/alpha.hpp index 1f0cbb1..403a1a5 100644 --- a/include/shaderchains/alpha.hpp +++ b/include/shaderchains/alpha.hpp @@ -9,6 +9,8 @@ #include "core/shadermodule.hpp" #include "device.hpp" +#include + namespace Vulkan::Shaderchains { /// @@ -28,7 +30,7 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Alpha(const Device& device, const Core::DescriptorPool& pool, - const Core::Image& inImg); + Core::Image inImg); /// /// Dispatch the shaderchain. @@ -49,17 +51,17 @@ namespace Vulkan::Shaderchains { Alpha& operator=(Alpha&&) noexcept = default; ~Alpha() = default; private: - std::vector shaderModules{4}; - std::vector pipelines{4}; - std::vector descriptorSets{4}; + std::array shaderModules; + std::array pipelines; + std::array descriptorSets; Core::Image inImg; - std::vector tempImgs1{2}; // half-size - std::vector tempImgs2{2}; // half-size - std::vector tempImgs3{4}; // quarter-size + std::array tempImgs1; // half-size + std::array tempImgs2; // half-size + std::array tempImgs3; // quarter-size - std::vector outImgs{4}; // quarter-size + std::array outImgs; // quarter-size }; } diff --git a/include/shaderchains/beta.hpp b/include/shaderchains/beta.hpp index e9efe7d..ca52bd5 100644 --- a/include/shaderchains/beta.hpp +++ b/include/shaderchains/beta.hpp @@ -10,6 +10,8 @@ #include "core/shadermodule.hpp" #include "device.hpp" +#include + namespace Vulkan::Shaderchains { /// @@ -31,8 +33,8 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Beta(const Device& device, const Core::DescriptorPool& pool, - const std::vector& temporalImgs, - const std::vector& inImgs); + std::array temporalImgs, + std::array inImgs); /// /// Dispatch the shaderchain. @@ -53,18 +55,18 @@ namespace Vulkan::Shaderchains { Beta& operator=(Beta&&) noexcept = default; ~Beta() = default; private: - std::vector shaderModules{5}; - std::vector pipelines{5}; - std::vector descriptorSets{5}; + std::array shaderModules; + std::array pipelines; + std::array descriptorSets; Core::Buffer buffer; - std::vector temporalImgs{8}; - std::vector inImgs{4}; + std::array temporalImgs; + std::array inImgs; - std::vector tempImgs1{2}; - std::vector tempImgs2{2}; + std::array tempImgs1; + std::array tempImgs2; - std::vector outImgs{6}; + std::array outImgs; }; } diff --git a/include/shaderchains/delta.hpp b/include/shaderchains/delta.hpp index 4a5d7e1..c880e90 100644 --- a/include/shaderchains/delta.hpp +++ b/include/shaderchains/delta.hpp @@ -10,6 +10,8 @@ #include "core/shadermodule.hpp" #include "device.hpp" +#include + namespace Vulkan::Shaderchains { /// @@ -31,8 +33,8 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Delta(const Device& device, const Core::DescriptorPool& pool, - const std::vector& inImgs, - const std::optional& optImg); + std::array inImgs, + std::optional optImg); /// /// Dispatch the shaderchain. @@ -53,16 +55,16 @@ namespace Vulkan::Shaderchains { Delta& operator=(Delta&&) noexcept = default; ~Delta() = default; private: - std::vector shaderModules{4}; - std::vector pipelines{4}; - std::vector descriptorSets{4}; + std::array shaderModules; + std::array pipelines; + std::array descriptorSets; Core::Buffer buffer; - std::vector inImg{2}; + std::array inImg; std::optional optImg; - std::vector tempImgs1{2}; - std::vector tempImgs2{2}; + std::array tempImgs1; + std::array tempImgs2; Core::Image outImg; }; diff --git a/include/shaderchains/downsample.hpp b/include/shaderchains/downsample.hpp index 0f9c523..1a5d399 100644 --- a/include/shaderchains/downsample.hpp +++ b/include/shaderchains/downsample.hpp @@ -10,6 +10,8 @@ #include "core/shadermodule.hpp" #include "device.hpp" +#include + namespace Vulkan::Shaderchains { /// @@ -29,7 +31,7 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Downsample(const Device& device, const Core::DescriptorPool& pool, - const Core::Image& inImg); + Core::Image inImg); /// /// Dispatch the shaderchain. @@ -57,7 +59,7 @@ namespace Vulkan::Shaderchains { Core::Image inImg; - std::vector outImgs{7}; + std::array outImgs; }; } diff --git a/include/shaderchains/epsilon.hpp b/include/shaderchains/epsilon.hpp index b875a26..6ad438a 100644 --- a/include/shaderchains/epsilon.hpp +++ b/include/shaderchains/epsilon.hpp @@ -10,6 +10,8 @@ #include "core/shadermodule.hpp" #include "device.hpp" +#include + namespace Vulkan::Shaderchains { /// @@ -32,9 +34,9 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Epsilon(const Device& device, const Core::DescriptorPool& pool, - const std::vector& inImgs1, - const Core::Image& inImg2, - const std::optional& optImg); + std::array inImgs1, + Core::Image inImg2, + std::optional optImg); /// /// Dispatch the shaderchain. @@ -55,17 +57,17 @@ namespace Vulkan::Shaderchains { Epsilon& operator=(Epsilon&&) noexcept = default; ~Epsilon() = default; private: - std::vector shaderModules{4}; - std::vector pipelines{4}; - std::vector descriptorSets{4}; + std::array shaderModules; + std::array pipelines; + std::array descriptorSets; Core::Buffer buffer; - std::vector inImgs1{3}; + std::array inImgs1; Core::Image inImg2; std::optional optImg; - std::vector tempImgs1{4}; - std::vector tempImgs2{4}; + std::array tempImgs1; + std::array tempImgs2; Core::Image outImg; }; diff --git a/include/shaderchains/extract.hpp b/include/shaderchains/extract.hpp index e70a8e0..e1a7a72 100644 --- a/include/shaderchains/extract.hpp +++ b/include/shaderchains/extract.hpp @@ -9,7 +9,6 @@ #include "core/pipeline.hpp" #include "core/shadermodule.hpp" #include "device.hpp" -#include namespace Vulkan::Shaderchains { diff --git a/include/shaderchains/gamma.hpp b/include/shaderchains/gamma.hpp index 693a16d..dad16c0 100644 --- a/include/shaderchains/gamma.hpp +++ b/include/shaderchains/gamma.hpp @@ -10,6 +10,8 @@ #include "core/shadermodule.hpp" #include "device.hpp" +#include + namespace Vulkan::Shaderchains { /// @@ -37,11 +39,11 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Gamma(const Device& device, const Core::DescriptorPool& pool, - const std::vector& temporalImgs, - const std::vector& inImgs1, - const Core::Image& inImg2, - const std::optional& optImg1, - const std::optional& optImg2, + std::array temporalImgs, + std::array inImgs1, + Core::Image inImg2, + std::optional optImg1, + std::optional optImg2, VkExtent2D outExtent); /// @@ -65,19 +67,19 @@ namespace Vulkan::Shaderchains { Gamma& operator=(Gamma&&) noexcept = default; ~Gamma() = default; private: - std::vector shaderModules{6}; - std::vector pipelines{6}; - std::vector descriptorSets{6}; + std::array shaderModules; + std::array pipelines; + std::array descriptorSets; Core::Buffer buffer; - std::vector temporalImgs{4}; - std::vector inImgs1{4}; + std::array temporalImgs; + std::array inImgs1; Core::Image inImg2; Core::Image optImg1; // specified or created black std::optional optImg2; - std::vector tempImgs1{4}; - std::vector tempImgs2{4}; + std::array tempImgs1; + std::array tempImgs2; Core::Image whiteImg; Core::Image outImg1; diff --git a/include/shaderchains/magic.hpp b/include/shaderchains/magic.hpp index 2d5f1cb..6de4525 100644 --- a/include/shaderchains/magic.hpp +++ b/include/shaderchains/magic.hpp @@ -10,6 +10,8 @@ #include "core/shadermodule.hpp" #include "device.hpp" +#include + namespace Vulkan::Shaderchains { /// @@ -34,11 +36,11 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Magic(const Device& device, const Core::DescriptorPool& pool, - const std::vector& temporalImgs, - const std::vector& inImgs1, + std::array& temporalImgs, + std::array& inImgs1, Core::Image inImg2, Core::Image inImg3, - const std::optional& optImg); + std::optional optImg); /// /// Dispatch the shaderchain. @@ -68,15 +70,15 @@ namespace Vulkan::Shaderchains { Core::DescriptorSet descriptorSet; Core::Buffer buffer; - std::vector temporalImgs{4}; - std::vector inImgs1{4}; + std::array temporalImgs; + std::array inImgs1; Core::Image inImg2; Core::Image inImg3; std::optional optImg; - std::vector outImgs1{3}; - std::vector outImgs2{3}; - std::vector outImgs3{3}; + std::array outImgs1; + std::array outImgs2; + std::array outImgs3; }; } diff --git a/include/shaderchains/zeta.hpp b/include/shaderchains/zeta.hpp index e0847d1..7e700d3 100644 --- a/include/shaderchains/zeta.hpp +++ b/include/shaderchains/zeta.hpp @@ -10,6 +10,8 @@ #include "core/shadermodule.hpp" #include "device.hpp" +#include + namespace Vulkan::Shaderchains { /// @@ -32,9 +34,9 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Zeta(const Device& device, const Core::DescriptorPool& pool, - const std::vector& inImgs1, - const Core::Image& inImg2, - const Core::Image& inImg3); + std::array inImgs1, + Core::Image inImg2, + Core::Image inImg3); /// /// Dispatch the shaderchain. @@ -55,17 +57,17 @@ namespace Vulkan::Shaderchains { Zeta& operator=(Zeta&&) noexcept = default; ~Zeta() = default; private: - std::vector shaderModules{4}; - std::vector pipelines{4}; - std::vector descriptorSets{4}; + std::array shaderModules; + std::array pipelines; + std::array descriptorSets; Core::Buffer buffer; - std::vector inImgs1{3}; + std::array inImgs1; Core::Image inImg2; Core::Image inImg3; - std::vector tempImgs1{4}; - std::vector tempImgs2{4}; + std::array tempImgs1; + std::array tempImgs2; Core::Image outImg; }; diff --git a/include/utils.hpp b/include/utils.hpp index aa968b7..9005e80 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -4,6 +4,7 @@ #include "core/commandbuffer.hpp" #include "core/image.hpp" #include "core/sampler.hpp" +#include "device.hpp" #include #include @@ -14,16 +15,47 @@ namespace Vulkan::Utils { /// /// Insert memory barriers for images in a command buffer. /// - /// @param buffer Command buffer to insert barriers into - /// @param r2wImages Images that are being read and will be written to - /// @param w2rImages Images that are being written to and will be read from - /// /// @throws std::logic_error if the command buffer is not in Recording state /// - void insertBarrier( - const Vulkan::Core::CommandBuffer& buffer, - std::vector w2rImages, - std::vector r2wImages); + class BarrierBuilder { + public: + /// Create a barrier builder. + BarrierBuilder(const Core::CommandBuffer& buffer) + : commandBuffer(&buffer) { + this->barriers.reserve(16); // this is performance critical + } + + // Add a resource to the barrier builder. + BarrierBuilder& addR2W(Core::Image& image); + BarrierBuilder& addW2R(Core::Image& image); + + // Add an optional resource to the barrier builder. + BarrierBuilder& addR2W(std::optional& image) { + if (image.has_value()) this->addR2W(*image); return *this; } + BarrierBuilder& addW2R(std::optional& image) { + if (image.has_value()) this->addW2R(*image); return *this; } + + /// Add a list of resources to the barrier builder. + BarrierBuilder& addR2W(std::vector& images) { + for (auto& image : images) this->addR2W(image); return *this; } + BarrierBuilder& addW2R(std::vector& images) { + for (auto& image : images) this->addW2R(image); return *this; } + + /// Add an array of resources to the barrier builder. + template + BarrierBuilder& addR2W(std::array& images) { + for (auto& image : images) this->addR2W(image); return *this; } + template + BarrierBuilder& addW2R(std::array& images) { + for (auto& image : images) this->addW2R(image); return *this; } + + /// Finish building the barrier + void build() const; + private: + const Core::CommandBuffer* commandBuffer; + + std::vector barriers; + }; /// /// Upload a DDS file to a Vulkan image. @@ -36,9 +68,19 @@ namespace Vulkan::Utils { /// @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 Vulkan::Device& device, - const Vulkan::Core::CommandPool& commandPool, - Vulkan::Core::Image& image, const std::string& path); + void uploadImage(const 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. + /// + /// @throws ls::vulkan_error If the Vulkan image cannot be cleared. + /// + void clearWhiteImage(const Device& device, Core::Image& image); } diff --git a/src/core/descriptorset.cpp b/src/core/descriptorset.cpp index 6a83cb4..b33d1c5 100644 --- a/src/core/descriptorset.cpp +++ b/src/core/descriptorset.cpp @@ -87,6 +87,19 @@ DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType typ return *this; } +DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type) { + this->entries.push_back({ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = this->descriptorSet->handle(), + .dstBinding = static_cast(this->entries.size()), + .descriptorCount = 0, + .descriptorType = type, + .pImageInfo = nullptr, + .pBufferInfo = nullptr + }); + return *this; +} + void DescriptorSetUpdateBuilder::build() const { if (this->entries.empty()) return; diff --git a/src/shaderchains/alpha.cpp b/src/shaderchains/alpha.cpp index b99c4b1..8bd270e 100644 --- a/src/shaderchains/alpha.cpp +++ b/src/shaderchains/alpha.cpp @@ -4,7 +4,8 @@ using namespace Vulkan::Shaderchains; Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, - const Core::Image& inImage) : inImage(inImage) { + Core::Image inImg) + : inImg(std::move(inImg)) { this->shaderModules = {{ Core::ShaderModule(device, "rsc/shaders/alpha/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, @@ -30,32 +31,37 @@ Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, this->shaderModules.at(i)); } - auto extent = inImage.getExtent(); - auto halfWidth = (extent.width + 1) >> 1; - auto halfHeight = (extent.height + 1) >> 1; - auto quarterWidth = (extent.width + 3) >> 2; - auto quarterHeight = (extent.height + 3) >> 2; + const auto 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->tempTex1.at(i) = Core::Image(device, - { halfWidth, halfHeight }, + this->tempImgs1.at(i) = Core::Image(device, + halfExtent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - this->tempTex2.at(i) = Core::Image(device, - { halfWidth, halfHeight }, + this->tempImgs2.at(i) = Core::Image(device, + halfExtent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); } + + const VkExtent2D quarterExtent = { + .width = (extent.width + 3) >> 2, + .height = (extent.height + 3) >> 2 + }; for (size_t i = 0; i < 4; i++) { - this->tempTex3.at(i) = Core::Image(device, - { quarterWidth, quarterHeight }, + this->tempImgs3.at(i) = Core::Image(device, + quarterExtent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - this->outImages.at(i) = Core::Image(device, - { quarterWidth, quarterHeight }, + this->outImgs.at(i) = Core::Image(device, + quarterExtent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); @@ -63,79 +69,73 @@ Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, this->descriptorSets.at(0).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inImage) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempTex1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) .build(); this->descriptorSets.at(1).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempTex1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempTex2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) .build(); this->descriptorSets.at(2).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempTex2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempTex3) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs3) .build(); this->descriptorSets.at(3).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempTex3) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImages) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) .build(); } void Alpha::Dispatch(const Core::CommandBuffer& buf) { - const auto halfExtent = this->tempTex1.at(0).getExtent(); - const auto quarterExtent = this->tempTex3.at(0).getExtent(); + const auto halfExtent = this->tempImgs1.at(0).getExtent(); + const auto quarterExtent = this->tempImgs3.at(0).getExtent(); // first pass - Utils::insertBarrier( - buf, - { this->inImage }, - this->tempTex1 - ); + 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)); - - uint32_t threadsX = (halfExtent.width + 7) >> 3; - uint32_t threadsY = (halfExtent.height + 7) >> 3; buf.dispatch(threadsX, threadsY, 1); // second pass - Utils::insertBarrier( - buf, - this->tempTex1, - this->tempTex2 - ); + 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 - Utils::insertBarrier( - buf, - this->tempTex2, - this->tempTex3 - ); + 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)); - - threadsX = (quarterExtent.width + 7) >> 3; - threadsY = (quarterExtent.height + 7) >> 3; buf.dispatch(threadsX, threadsY, 1); // fourth pass - Utils::insertBarrier( - buf, - this->tempTex3, - this->outImages - ); + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs3) + .addR2W(this->outImgs) + .build(); this->pipelines.at(3).bind(buf); this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); - buf.dispatch(threadsX, threadsY, 1); } diff --git a/src/shaderchains/downsample.cpp b/src/shaderchains/downsample.cpp index d6dc2ea..2d880ba 100644 --- a/src/shaderchains/downsample.cpp +++ b/src/shaderchains/downsample.cpp @@ -4,8 +4,8 @@ using namespace Vulkan::Shaderchains; Downsample::Downsample(const Device& device, const Core::DescriptorPool& pool, - const Core::Image& inImage) : inImage(inImage) { - // create internal resources + Core::Image inImg) + : inImg(std::move(inImg)) { this->shaderModule = Core::ShaderModule(device, "rsc/shaders/downsample.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, @@ -13,39 +13,35 @@ Downsample::Downsample(const Device& device, const Core::DescriptorPool& pool, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); this->pipeline = Core::Pipeline(device, this->shaderModule); this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); + this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - const Globals::FgBuffer data = Globals::fgBuffer; - this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - - auto extent = inImage.getExtent(); - - // create output images + auto extent = this->inImg.getExtent(); for (size_t i = 0; i < 7; i++) - this->outImages.at(i) = Core::Image(device, + this->outImgs.at(i) = Core::Image(device, { extent.width >> i, extent.height >> i }, VK_FORMAT_R8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - // update descriptor set this->descriptorSet.update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inImage) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImages) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); } void Downsample::Dispatch(const Core::CommandBuffer& buf) { - auto extent = inImage.getExtent(); + auto extent = this->inImg.getExtent(); + + // first pass const uint32_t threadsX = (extent.width + 63) >> 6; const uint32_t threadsY = (extent.height + 63) >> 6; - Utils::insertBarrier( - buf, - { this->inImage }, - this->outImages - ); + Utils::BarrierBuilder(buf) + .addW2R(this->inImg) + .addR2W(this->outImgs) + .build(); this->pipeline.bind(buf); this->descriptorSet.bind(buf, this->pipeline); diff --git a/src/shaderchains/extract.cpp b/src/shaderchains/extract.cpp index 3b12f92..58284f6 100644 --- a/src/shaderchains/extract.cpp +++ b/src/shaderchains/extract.cpp @@ -1,6 +1,5 @@ #include "shaderchains/extract.hpp" #include "utils.hpp" -#include using namespace Vulkan::Shaderchains; @@ -10,7 +9,6 @@ Extract::Extract(const Device& device, const Core::DescriptorPool& pool, VkExtent2D outExtent) : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)) { - // create internal resources this->shaderModule = Core::ShaderModule(device, "rsc/shaders/extract.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, @@ -18,24 +16,19 @@ Extract::Extract(const Device& device, const Core::DescriptorPool& pool, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); this->pipeline = Core::Pipeline(device, this->shaderModule); this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); - - const Globals::FgBuffer data = Globals::fgBuffer; - this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); this->whiteImg = Core::Image(device, - { outExtent.width, outExtent.height }, + outExtent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - - // create output images this->outImg = Core::Image(device, - { outExtent.width, outExtent.height }, + outExtent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - // update descriptor set this->descriptorSet.update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) @@ -44,20 +37,24 @@ Extract::Extract(const Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); + + // clear white image + Utils::clearWhiteImage(device, this->whiteImg); } void Extract::Dispatch(const Core::CommandBuffer& buf) { auto extent = this->whiteImg.getExtent(); + + // first pass const uint32_t threadsX = (extent.width + 7) >> 3; const uint32_t threadsY = (extent.height + 7) >> 3; - // FIXME: clear to white - - Utils::insertBarrier( - buf, - { this->whiteImg, this->inImg1, this->inImg2 }, - { this->outImg } - ); + Utils::BarrierBuilder(buf) + .addW2R(this->whiteImg) + .addW2R(this->inImg1) + .addW2R(this->inImg2) + .addR2W(this->outImg) + .build(); this->pipeline.bind(buf); this->descriptorSet.bind(buf, this->pipeline); diff --git a/src/shaderchains/magic.cpp b/src/shaderchains/magic.cpp index 3f641d4..da77a8c 100644 --- a/src/shaderchains/magic.cpp +++ b/src/shaderchains/magic.cpp @@ -4,14 +4,15 @@ using namespace Vulkan::Shaderchains; Magic::Magic(const Device& device, const Core::DescriptorPool& pool, - const std::vector& temporalImgs, - const std::vector& inImgs1, + std::array& temporalImgs, + std::array& inImgs1, Core::Image inImg2, Core::Image inImg3, - const std::optional& optImg) - : temporalImgs(temporalImgs), inImgs1(inImgs1), - inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), optImg(optImg) { - // create internal resources + std::optional optImg) + : temporalImgs(std::move(temporalImgs)), + inImgs1(std::move(inImgs1)), + inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), + optImg(std::move(optImg)) { this->shaderModule = Core::ShaderModule(device, "rsc/shaders/magic.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4+4+2+1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, @@ -26,34 +27,32 @@ Magic::Magic(const Device& device, const Core::DescriptorPool& pool, auto extent = temporalImgs.at(0).getExtent(); - // create output images for (size_t i = 0; i < 2; i++) this->outImgs1.at(i) = Core::Image(device, - { extent.width, extent.height }, + extent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); for (size_t i = 0; i < 3; i++) this->outImgs2.at(i) = Core::Image(device, - { extent.width, extent.height }, + extent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); for (size_t i = 0; i < 3; i++) this->outImgs3.at(i) = Core::Image(device, - { extent.width, extent.height }, + extent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - // update descriptor set this->descriptorSet.update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->temporalImgs) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *this->optImg) // FIXME: invalid resource + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs3) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs1) @@ -63,20 +62,21 @@ Magic::Magic(const Device& device, const Core::DescriptorPool& pool, void Magic::Dispatch(const Core::CommandBuffer& buf) { auto extent = this->temporalImgs.at(0).getExtent(); + + // first pass const uint32_t threadsX = (extent.width + 7) >> 3; const uint32_t threadsY = (extent.height + 7) >> 3; - Utils::insertBarrier( - buf, - { this->temporalImgs.at(0), this->temporalImgs.at(1), - this->temporalImgs.at(2), this->temporalImgs.at(3), - this->inImgs1.at(0), this->inImgs1.at(1), - this->inImgs1.at(2), this->inImgs1.at(3), - this->inImg2, this->inImg3, *this->optImg }, // FIXME: invalid resource - { this->outImgs3.at(0), this->outImgs3.at(1), this->outImgs3.at(2), - this->outImgs2.at(0), this->outImgs2.at(1), this->outImgs2.at(2), - this->outImgs1.at(0), this->outImgs1.at(1) } - ); + Utils::BarrierBuilder(buf) + .addW2R(this->temporalImgs) + .addW2R(this->inImgs1) + .addW2R(this->inImg2) + .addW2R(this->inImg3) + .addW2R(this->optImg) + .addR2W(this->outImgs3) + .addR2W(this->outImgs2) + .addR2W(this->outImgs1) + .build(); this->pipeline.bind(buf); this->descriptorSet.bind(buf, this->pipeline); diff --git a/src/shaderchains/merge.cpp b/src/shaderchains/merge.cpp index 590779b..e93d645 100644 --- a/src/shaderchains/merge.cpp +++ b/src/shaderchains/merge.cpp @@ -15,7 +15,6 @@ Merge::Merge(const Device& device, const Core::DescriptorPool& pool, inImg3(std::move(inImg3)), inImg4(std::move(inImg4)), inImg5(std::move(inImg5)) { - // create internal resources this->shaderModule = Core::ShaderModule(device, "rsc/shaders/merge.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, @@ -23,20 +22,16 @@ Merge::Merge(const Device& device, const Core::DescriptorPool& pool, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); this->pipeline = Core::Pipeline(device, this->shaderModule); this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); - - const Globals::FgBuffer data = Globals::fgBuffer; - this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); auto extent = this->inImg1.getExtent(); - // create output image this->outImg = Core::Image(device, - { extent.width, extent.height }, + extent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - // update descriptor set this->descriptorSet.update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg1) @@ -51,17 +46,19 @@ Merge::Merge(const Device& device, const Core::DescriptorPool& pool, void Merge::Dispatch(const Core::CommandBuffer& buf) { auto extent = this->inImg1.getExtent(); + + // first pass const uint32_t threadsX = (extent.width + 15) >> 4; const uint32_t threadsY = (extent.height + 15) >> 4; - // FIXME: clear to white - - Utils::insertBarrier( - buf, - { this->inImg1, this->inImg2, this->inImg3, - this->inImg4, this->inImg5 }, - { this->outImg } - ); + Utils::BarrierBuilder(buf) + .addW2R(this->inImg1) + .addW2R(this->inImg2) + .addW2R(this->inImg3) + .addW2R(this->inImg4) + .addW2R(this->inImg5) + .addR2W(this->outImg) + .build(); this->pipeline.bind(buf); this->descriptorSet.bind(buf, this->pipeline); diff --git a/src/utils.cpp b/src/utils.cpp index bb03b3c..f8fc6a1 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -3,58 +3,60 @@ #include #include +#include using namespace Vulkan; +using namespace Vulkan::Utils; -void Utils::insertBarrier( - const Core::CommandBuffer& buffer, - std::vector w2rImages, - std::vector r2wImages) { - std::vector barriers(r2wImages.size() + w2rImages.size()); +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); - size_t index = 0; - for (auto& image : r2wImages) { - barriers[index++] = { - .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); - } - for (auto& image : w2rImages) { - barriers[index++] = { - .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; +} + +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(barriers.size()), - .pImageMemoryBarriers = barriers.data() + .imageMemoryBarrierCount = static_cast(this->barriers.size()), + .pImageMemoryBarriers = this->barriers.data() }; - vkCmdPipelineBarrier2(buffer.handle(), &dependencyInfo); + vkCmdPipelineBarrier2(this->commandBuffer->handle(), &dependencyInfo); } void Utils::uploadImage(const Device& device, const Core::CommandPool& commandPool, @@ -129,6 +131,51 @@ void Utils::uploadImage(const Device& device, const Core::CommandPool& commandPo throw ls::vulkan_error(VK_TIMEOUT, "Upload operation timed out"); } +void Utils::clearWhiteImage(const Device& device, Core::Image& image) { + 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 VkClearColorValue clearColor = {{ 1.0F, 1.0F, 1.0F, 1.0F }}; + 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 ls::vulkan_error(VK_TIMEOUT, "Failed to wait for clearing fence."); +} + Core::Sampler Globals::samplerClampBorder; Core::Sampler Globals::samplerClampEdge; Globals::FgBuffer Globals::fgBuffer; From 42a692cd6206ff0e67e34e65a6aca26b71213622 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 07:49:11 +0200 Subject: [PATCH 029/253] remaining shaderchains --- include/core/descriptorset.hpp | 6 +- include/shaderchains/delta.hpp | 2 +- include/shaderchains/gamma.hpp | 2 +- include/shaderchains/magic.hpp | 6 +- src/shaderchains/alpha.cpp | 16 +-- src/shaderchains/beta.cpp | 161 +++++++++++++++++++++++ src/shaderchains/delta.cpp | 138 ++++++++++++++++++++ src/shaderchains/epsilon.cpp | 139 ++++++++++++++++++++ src/shaderchains/gamma.cpp | 227 +++++++++++++++++++++++++++++++++ src/shaderchains/magic.cpp | 8 +- src/shaderchains/zeta.cpp | 139 ++++++++++++++++++++ 11 files changed, 824 insertions(+), 20 deletions(-) create mode 100644 src/shaderchains/beta.cpp create mode 100644 src/shaderchains/delta.cpp create mode 100644 src/shaderchains/epsilon.cpp create mode 100644 src/shaderchains/gamma.cpp create mode 100644 src/shaderchains/zeta.cpp diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp index 3bbacbb..4f0d2b3 100644 --- a/include/core/descriptorset.hpp +++ b/include/core/descriptorset.hpp @@ -100,11 +100,11 @@ namespace Vulkan::Core { /// Add an optional resource to the descriptor set update. DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& image) { - if (image.has_value()) this->add(type, *image); return *this; } + if (image.has_value()) this->add(type, *image); else this->add(type); return *this; } DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& sampler) { - if (sampler.has_value()) this->add(type, *sampler); return *this; } + if (sampler.has_value()) this->add(type, *sampler); else this->add(type); return *this; } DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& buffer) { - if (buffer.has_value()) this->add(type, *buffer); return *this; } + if (buffer.has_value()) this->add(type, *buffer); else this->add(type); return *this; } /// Finish building the descriptor set update. void build() const; diff --git a/include/shaderchains/delta.hpp b/include/shaderchains/delta.hpp index c880e90..dcb561b 100644 --- a/include/shaderchains/delta.hpp +++ b/include/shaderchains/delta.hpp @@ -60,7 +60,7 @@ namespace Vulkan::Shaderchains { std::array descriptorSets; Core::Buffer buffer; - std::array inImg; + std::array inImgs; std::optional optImg; std::array tempImgs1; diff --git a/include/shaderchains/gamma.hpp b/include/shaderchains/gamma.hpp index dad16c0..d819760 100644 --- a/include/shaderchains/gamma.hpp +++ b/include/shaderchains/gamma.hpp @@ -42,7 +42,7 @@ namespace Vulkan::Shaderchains { std::array temporalImgs, std::array inImgs1, Core::Image inImg2, - std::optional optImg1, + std::optional& optImg1, std::optional optImg2, VkExtent2D outExtent); diff --git a/include/shaderchains/magic.hpp b/include/shaderchains/magic.hpp index 6de4525..7c21cb8 100644 --- a/include/shaderchains/magic.hpp +++ b/include/shaderchains/magic.hpp @@ -36,8 +36,8 @@ namespace Vulkan::Shaderchains { /// @throws ls::vulkan_error if resource creation fails. /// Magic(const Device& device, const Core::DescriptorPool& pool, - std::array& temporalImgs, - std::array& inImgs1, + std::array temporalImgs, + std::array inImgs1, Core::Image inImg2, Core::Image inImg3, std::optional optImg); @@ -76,7 +76,7 @@ namespace Vulkan::Shaderchains { Core::Image inImg3; std::optional optImg; - std::array outImgs1; + std::array outImgs1; std::array outImgs2; std::array outImgs3; }; diff --git a/src/shaderchains/alpha.cpp b/src/shaderchains/alpha.cpp index 8bd270e..325dc2c 100644 --- a/src/shaderchains/alpha.cpp +++ b/src/shaderchains/alpha.cpp @@ -9,20 +9,20 @@ Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, this->shaderModules = {{ Core::ShaderModule(device, "rsc/shaders/alpha/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/alpha/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/alpha/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/alpha/3.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; for (size_t i = 0; i < 4; i++) { this->pipelines.at(i) = Core::Pipeline(device, diff --git a/src/shaderchains/beta.cpp b/src/shaderchains/beta.cpp new file mode 100644 index 0000000..0e4659e --- /dev/null +++ b/src/shaderchains/beta.cpp @@ -0,0 +1,161 @@ +#include "shaderchains/beta.hpp" +#include "utils.hpp" + +using namespace Vulkan::Shaderchains; + +Beta::Beta(const Device& device, const Core::DescriptorPool& pool, + std::array temporalImgs, + std::array inImgs) + : temporalImgs(std::move(temporalImgs)), + inImgs(std::move(inImgs)) { + this->shaderModules = {{ + Core::ShaderModule(device, "rsc/shaders/beta/0.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 8+4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/beta/1.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/beta/2.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/beta/3.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/beta/4.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) + }}; + for (size_t i = 0; i < 5; i++) { + this->pipelines.at(i) = Core::Pipeline(device, + this->shaderModules.at(i)); + this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, + this->shaderModules.at(i)); + } + this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + const auto extent = this->temporalImgs.at(0).getExtent(); + + for (size_t i = 0; i < 2; i++) { + this->tempImgs1.at(i) = Core::Image(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + this->tempImgs2.at(i) = Core::Image(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + } + + for (size_t i = 0; i < 6; i++) { + this->outImgs.at(i) = Core::Image(device, + { extent.width >> i, extent.height >> i }, + VK_FORMAT_R8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + } + + this->descriptorSets.at(0).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->temporalImgs) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(1).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(2).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(3).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(4).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); +} + +void Beta::Dispatch(const Core::CommandBuffer& buf) { + const auto extent = this->tempImgs1.at(0).getExtent(); + + // first pass + uint32_t threadsX = (extent.width + 7) >> 3; + uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->temporalImgs) + .addW2R(this->inImgs) + .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 + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .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->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + this->descriptorSets.at(3).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(4).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/src/shaderchains/delta.cpp b/src/shaderchains/delta.cpp new file mode 100644 index 0000000..340d00a --- /dev/null +++ b/src/shaderchains/delta.cpp @@ -0,0 +1,138 @@ +#include "shaderchains/delta.hpp" +#include "utils.hpp" + +using namespace Vulkan::Shaderchains; + +Delta::Delta(const Device& device, const Core::DescriptorPool& pool, + std::array inImgs, + std::optional optImg) + : inImgs(std::move(inImgs)), + optImg(std::move(optImg)) { + this->shaderModules = {{ + Core::ShaderModule(device, "rsc/shaders/delta/0.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/delta/1.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/delta/2.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/delta/3.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) + }}; + for (size_t i = 0; i < 4; i++) { + this->pipelines.at(i) = Core::Pipeline(device, + this->shaderModules.at(i)); + this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, + this->shaderModules.at(i)); + } + + Globals::FgBuffer data = Globals::fgBuffer; + data.firstIterS = !this->optImg.has_value(); + this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + const auto extent = this->inImgs.at(0).getExtent(); + + for (size_t i = 0; i < 2; i++) { + this->tempImgs1.at(i) = Core::Image(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + this->tempImgs2.at(i) = Core::Image(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + } + + this->outImg = Core::Image(device, + extent, + VK_FORMAT_R16G16B16A16_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + + this->descriptorSets.at(0).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(1).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(2).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(3).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); +} + +void Delta::Dispatch(const Core::CommandBuffer& buf) { + const auto extent = this->tempImgs1.at(0).getExtent(); + + // first pass + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs) + .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 + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .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->tempImgs1) + .addW2R(this->optImg) + .addR2W(this->outImg) + .build(); + + this->pipelines.at(3).bind(buf); + this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/src/shaderchains/epsilon.cpp b/src/shaderchains/epsilon.cpp new file mode 100644 index 0000000..77d4269 --- /dev/null +++ b/src/shaderchains/epsilon.cpp @@ -0,0 +1,139 @@ +#include "shaderchains/epsilon.hpp" +#include "utils.hpp" + +using namespace Vulkan::Shaderchains; + +Epsilon::Epsilon(const Device& device, const Core::DescriptorPool& pool, + std::array inImgs1, + Core::Image inImg2, + std::optional optImg) + : inImgs1(std::move(inImgs1)), + inImg2(std::move(inImg2)), + optImg(std::move(optImg)) { + this->shaderModules = {{ + Core::ShaderModule(device, "rsc/shaders/epsilon/0.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/epsilon/1.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/epsilon/2.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/epsilon/3.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) + }}; + for (size_t i = 0; i < 4; i++) { + this->pipelines.at(i) = Core::Pipeline(device, + this->shaderModules.at(i)); + this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, + this->shaderModules.at(i)); + } + this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + const auto extent = this->inImgs1.at(0).getExtent(); + + for (size_t i = 0; i < 4; i++) { + this->tempImgs1.at(i) = Core::Image(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + this->tempImgs2.at(i) = Core::Image(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + } + + this->outImg = Core::Image(device, + extent, + VK_FORMAT_R16G16B16A16_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + + this->descriptorSets.at(0).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(1).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(2).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(3).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); +} + +void Epsilon::Dispatch(const Core::CommandBuffer& buf) { + const auto extent = this->tempImgs1.at(0).getExtent(); + + // first pass + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1) + .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 + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .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->tempImgs1) + .addW2R(this->optImg) + .addW2R(this->inImg2) + .addR2W(this->outImg) + .build(); + + this->pipelines.at(3).bind(buf); + this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/src/shaderchains/gamma.cpp b/src/shaderchains/gamma.cpp new file mode 100644 index 0000000..bef9de0 --- /dev/null +++ b/src/shaderchains/gamma.cpp @@ -0,0 +1,227 @@ +#include "shaderchains/gamma.hpp" +#include "utils.hpp" + +using namespace Vulkan::Shaderchains; + +Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, + std::array temporalImgs, + std::array inImgs1, + Core::Image inImg2, + std::optional& optImg1, + std::optional optImg2, + VkExtent2D outExtent) + : temporalImgs(std::move(temporalImgs)), + inImgs1(std::move(inImgs1)), + inImg2(std::move(inImg2)), + optImg2(std::move(optImg2)) { + this->shaderModules = {{ + Core::ShaderModule(device, "rsc/shaders/gamma/0.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 8+4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/gamma/1.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/gamma/2.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/gamma/3.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/gamma/4.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/gamma/5.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) + }}; + for (size_t i = 0; i < 6; i++) { + this->pipelines.at(i) = Core::Pipeline(device, + this->shaderModules.at(i)); + this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, + this->shaderModules.at(i)); + } + + Globals::FgBuffer data = Globals::fgBuffer; + data.firstIter = !optImg1.has_value(); + this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + const auto extent = this->temporalImgs.at(0).getExtent(); + + this->optImg1 = optImg1.value_or(Core::Image(device, extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT, + VK_IMAGE_ASPECT_COLOR_BIT)); + + for (size_t i = 0; i < 4; i++) { + this->tempImgs1.at(i) = Core::Image(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + this->tempImgs2.at(i) = Core::Image(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + } + this->whiteImg = Core::Image(device, outExtent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + + this->outImg1 = Core::Image(device, + extent, + VK_FORMAT_R16G16B16A16_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + this->outImg2 = Core::Image(device, + outExtent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + + this->descriptorSets.at(0).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->temporalImgs) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) + .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)) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(1).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .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) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(2).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(3).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(4).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(5).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->outImg1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + + // clear white image + Utils::clearWhiteImage(device, this->whiteImg); +} + +void Gamma::Dispatch(const Core::CommandBuffer& buf) { + const auto extent = this->tempImgs1.at(0).getExtent(); + + // first pass + uint32_t threadsX = (extent.width + 7) >> 3; + uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->temporalImgs) + .addW2R(this->inImgs1) + .addW2R(this->optImg1) + .addW2R(this->optImg2) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .addR2W(this->tempImgs1.at(2)) + .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.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addW2R(this->tempImgs1.at(2)) + .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 + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .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->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + this->descriptorSets.at(3).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) + .addW2R(this->optImg2) + .addW2R(this->inImg2) + .addR2W(this->outImg1) + .build(); + + this->pipelines.at(4).bind(buf); + this->descriptorSets.at(4).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); + + // sixth pass + threadsX = (extent.width + 3) >> 2; + threadsY = (extent.height + 3) >> 2; + + Utils::BarrierBuilder(buf) + .addW2R(this->whiteImg) + .addW2R(this->outImg1) + .addR2W(this->outImg2) + .build(); + + this->pipelines.at(5).bind(buf); + this->descriptorSets.at(5).bind(buf, this->pipelines.at(5)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/src/shaderchains/magic.cpp b/src/shaderchains/magic.cpp index da77a8c..4eb1c42 100644 --- a/src/shaderchains/magic.cpp +++ b/src/shaderchains/magic.cpp @@ -4,8 +4,8 @@ using namespace Vulkan::Shaderchains; Magic::Magic(const Device& device, const Core::DescriptorPool& pool, - std::array& temporalImgs, - std::array& inImgs1, + std::array temporalImgs, + std::array inImgs1, Core::Image inImg2, Core::Image inImg3, std::optional optImg) @@ -22,10 +22,10 @@ Magic::Magic(const Device& device, const Core::DescriptorPool& pool, this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); Globals::FgBuffer data = Globals::fgBuffer; - data.firstIterS = !optImg.has_value(); + data.firstIterS = !this->optImg.has_value(); this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - auto extent = temporalImgs.at(0).getExtent(); + auto extent = this->temporalImgs.at(0).getExtent(); for (size_t i = 0; i < 2; i++) this->outImgs1.at(i) = Core::Image(device, diff --git a/src/shaderchains/zeta.cpp b/src/shaderchains/zeta.cpp new file mode 100644 index 0000000..d614a53 --- /dev/null +++ b/src/shaderchains/zeta.cpp @@ -0,0 +1,139 @@ +#include "shaderchains/zeta.hpp" +#include "utils.hpp" + +using namespace Vulkan::Shaderchains; + +Zeta::Zeta(const Device& device, const Core::DescriptorPool& pool, + std::array inImgs1, + Core::Image inImg2, + Core::Image inImg3) + : inImgs1(std::move(inImgs1)), + inImg2(std::move(inImg2)), + inImg3(std::move(inImg3)) { + this->shaderModules = {{ + Core::ShaderModule(device, "rsc/shaders/zeta/0.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/zeta/1.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/zeta/2.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + Core::ShaderModule(device, "rsc/shaders/zeta/3.spv", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) + }}; + for (size_t i = 0; i < 4; i++) { + this->pipelines.at(i) = Core::Pipeline(device, + this->shaderModules.at(i)); + this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, + this->shaderModules.at(i)); + } + this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + const auto extent = this->inImgs1.at(0).getExtent(); + + for (size_t i = 0; i < 4; i++) { + this->tempImgs1.at(i) = Core::Image(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + this->tempImgs2.at(i) = Core::Image(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + } + + this->outImg = Core::Image(device, + extent, + VK_FORMAT_R16G16B16A16_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + + this->descriptorSets.at(0).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(1).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(2).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + this->descriptorSets.at(3).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); +} + +void Zeta::Dispatch(const Core::CommandBuffer& buf) { + const auto extent = this->tempImgs1.at(0).getExtent(); + + // first pass + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1) + .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 + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .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->tempImgs1) + .addW2R(this->inImg2) + .addW2R(this->inImg3) + .addR2W(this->outImg) + .build(); + + this->pipelines.at(3).bind(buf); + this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); +} From 5c7cc5a456c23cd07ebdd60a8c727e13b8f0c121 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 30 Jun 2025 09:44:37 +0200 Subject: [PATCH 030/253] finaly fixes --- include/core/descriptorset.hpp | 2 +- include/shaderchains/gamma.hpp | 2 +- include/utils.hpp | 3 +- src/core/descriptorset.cpp | 10 +-- src/device.cpp | 9 ++- src/main.cpp | 120 ++++++++++++++++++++++++++++++++- src/shaderchains/beta.cpp | 16 ++--- src/shaderchains/delta.cpp | 19 ++---- src/shaderchains/epsilon.cpp | 17 ++--- src/shaderchains/extract.cpp | 8 ++- src/shaderchains/gamma.cpp | 53 +++++++-------- src/shaderchains/magic.cpp | 3 +- src/shaderchains/merge.cpp | 3 +- src/shaderchains/zeta.cpp | 17 ++--- src/utils.cpp | 7 +- 15 files changed, 196 insertions(+), 93 deletions(-) diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp index 4f0d2b3..8d9d770 100644 --- a/include/core/descriptorset.hpp +++ b/include/core/descriptorset.hpp @@ -107,7 +107,7 @@ namespace Vulkan::Core { if (buffer.has_value()) this->add(type, *buffer); else this->add(type); return *this; } /// Finish building the descriptor set update. - void build() const; + void build(); private: const DescriptorSet* descriptorSet; const Device* device; diff --git a/include/shaderchains/gamma.hpp b/include/shaderchains/gamma.hpp index d819760..dad16c0 100644 --- a/include/shaderchains/gamma.hpp +++ b/include/shaderchains/gamma.hpp @@ -42,7 +42,7 @@ namespace Vulkan::Shaderchains { std::array temporalImgs, std::array inImgs1, Core::Image inImg2, - std::optional& optImg1, + std::optional optImg1, std::optional optImg2, VkExtent2D outExtent); diff --git a/include/utils.hpp b/include/utils.hpp index 9005e80..92e2a2b 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -77,10 +77,11 @@ namespace Vulkan::Utils { /// /// @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 ls::vulkan_error If the Vulkan image cannot be cleared. /// - void clearWhiteImage(const Device& device, Core::Image& image); + void clearImage(const Device& device, Core::Image& image, bool white = false); } diff --git a/src/core/descriptorset.cpp b/src/core/descriptorset.cpp index b33d1c5..16c7d93 100644 --- a/src/core/descriptorset.cpp +++ b/src/core/descriptorset.cpp @@ -1,5 +1,6 @@ #include "core/descriptorset.hpp" #include "utils.hpp" +#include using namespace Vulkan::Core; @@ -92,17 +93,16 @@ DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType typ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = this->descriptorSet->handle(), .dstBinding = static_cast(this->entries.size()), - .descriptorCount = 0, + .descriptorCount = 1, .descriptorType = type, - .pImageInfo = nullptr, + .pImageInfo = new VkDescriptorImageInfo { + }, .pBufferInfo = nullptr }); return *this; } -void DescriptorSetUpdateBuilder::build() const { - if (this->entries.empty()) return; - +void DescriptorSetUpdateBuilder::build() { vkUpdateDescriptorSets(this->device->handle(), static_cast(this->entries.size()), this->entries.data(), 0, nullptr); diff --git a/src/device.cpp b/src/device.cpp index 5950d0d..6eb7cd6 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -3,11 +3,13 @@ #include #include +#include using namespace Vulkan; const std::vector requiredExtensions = { - "VK_KHR_external_memory_fd" + "VK_KHR_external_memory_fd", + "VK_EXT_robustness2", }; Device::Device(const Instance& instance) { @@ -53,8 +55,13 @@ Device::Device(const Instance& instance) { // 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{ diff --git a/src/main.cpp b/src/main.cpp index f16aedb..ee0c50e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,9 +6,18 @@ #include "device.hpp" #include "instance.hpp" #include "shaderchains/alpha.hpp" +#include "shaderchains/beta.hpp" +#include "shaderchains/delta.hpp" #include "shaderchains/downsample.hpp" +#include "shaderchains/epsilon.hpp" +#include "shaderchains/extract.hpp" +#include "shaderchains/gamma.hpp" +#include "shaderchains/magic.hpp" +#include "shaderchains/merge.hpp" +#include "shaderchains/zeta.hpp" #include "utils.hpp" +#include #include #include @@ -42,7 +51,7 @@ int main() { Globals::initializeGlobals(device); - // create initialization resources + // create downsample shader chain Core::Image inputImage( device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, @@ -50,14 +59,109 @@ int main() { ); Utils::uploadImage(device, commandPool, inputImage, "rsc/images/source.dds"); - // create the shaderchains Shaderchains::Downsample downsample(device, descriptorPool, inputImage); + // create alpha shader chains std::vector alphas; alphas.reserve(7); for (size_t i = 0; i < 7; ++i) alphas.emplace_back(device, descriptorPool, downsample.getOutImages().at(i)); + // create beta shader chain + std::array betaTemporalImages; + auto betaInExtent = alphas.at(0).getOutImages().at(0).getExtent(); + for (size_t i = 0; i < 8; ++i) { + betaTemporalImages.at(i) = Core::Image( + device, betaInExtent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ); + Utils::uploadImage(device, commandPool, betaTemporalImages.at(i), + std::format("rsc/images/temporal_beta/{}.dds", i)); + } + + Shaderchains::Beta beta(device, descriptorPool, + betaTemporalImages, + alphas.at(0).getOutImages() + ); + + // create gamma to zeta shader chains + std::vector gammas; + std::vector magics; + std::vector deltas; + std::vector epsilons; + std::vector zetas; + std::vector extracts; + std::array, 7> otherTemporalImages; + for (size_t i = 0; i < 7; i++) { + auto otherInExtent = alphas.at(6 - i).getOutImages().at(0).getExtent(); + for (size_t j = 0; j < 4; j++) { + otherTemporalImages.at(i).at(j) = Core::Image( + device, otherInExtent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ); + Utils::uploadImage(device, commandPool, otherTemporalImages.at(i).at(j), + std::format("rsc/images/temporal_other/{}.{}.dds", i, j)); + } + if (i < 4) { + auto gammaOutExtent = alphas.at(6 - i - 1).getOutImages().at(0).getExtent(); + gammas.emplace_back(device, descriptorPool, + otherTemporalImages.at(i), + alphas.at(6 - i).getOutImages(), + beta.getOutImages().at(std::min(static_cast(5), 6 - i)), // smallest twice + i == 0 ? std::nullopt : std::optional{gammas.at(i - 1).getOutImage2()}, + i == 0 ? std::nullopt : std::optional{gammas.at(i - 1).getOutImage1()}, + gammaOutExtent); + } else { + magics.emplace_back(device, descriptorPool, + otherTemporalImages.at(i), + alphas.at(6 - i).getOutImages(), + i == 4 ? gammas.at(i - 1).getOutImage2() : extracts.at(i - 5).getOutImage(), + i == 4 ? gammas.at(i - 1).getOutImage1() : zetas.at(i - 5).getOutImage(), + i == 4 ? std::nullopt : std::optional{epsilons.at(i - 5).getOutImage()} + ); + deltas.emplace_back(device, descriptorPool, + magics.at(i - 4).getOutImages1(), + i == 4 ? std::nullopt : std::optional{deltas.at(i - 5).getOutImage()} + ); + epsilons.emplace_back(device, descriptorPool, + magics.at(i - 4).getOutImages2(), + beta.getOutImages().at(6 - i), + i == 4 ? std::nullopt : std::optional{epsilons.at(i - 5).getOutImage()} + ); + zetas.emplace_back(device, descriptorPool, + magics.at(i - 4).getOutImages3(), + i == 4 ? gammas.at(i - 1).getOutImage1() : zetas.at(i - 5).getOutImage(), + beta.getOutImages().at(6 - i) + ); + if (i < 6) { + auto extractOutExtent = alphas.at(6 - i - 1).getOutImages().at(0).getExtent(); + extracts.emplace_back(device, descriptorPool, + zetas.at(i - 4).getOutImage(), + epsilons.at(i - 4).getOutImage(), + extractOutExtent); + } + } + } + + // create merge shader chain + Core::Image inputImagePrev( + device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ); + Utils::uploadImage(device, commandPool, inputImagePrev, "rsc/images/source_prev.dds"); + Shaderchains::Merge merge(device, descriptorPool, + inputImagePrev, + inputImage, + zetas.at(2).getOutImage(), + epsilons.at(2).getOutImage(), + deltas.at(2).getOutImage() + ); + // start the rendering pipeline if (rdoc) rdoc->StartFrameCapture(nullptr, nullptr); @@ -68,6 +172,18 @@ int main() { downsample.Dispatch(commandBuffer); for (size_t i = 0; i < 7; i++) alphas.at(6 - i).Dispatch(commandBuffer); + beta.Dispatch(commandBuffer); + for (size_t i = 0; i < 4; i++) + gammas.at(i).Dispatch(commandBuffer); + for (size_t i = 0; i < 3; i++) { + magics.at(i).Dispatch(commandBuffer); + deltas.at(i).Dispatch(commandBuffer); + epsilons.at(i).Dispatch(commandBuffer); + zetas.at(i).Dispatch(commandBuffer); + if (i < 2) + extracts.at(i).Dispatch(commandBuffer); + } + merge.Dispatch(commandBuffer); // finish the rendering pipeline commandBuffer.end(); diff --git a/src/shaderchains/beta.cpp b/src/shaderchains/beta.cpp index 0e4659e..33c6b88 100644 --- a/src/shaderchains/beta.cpp +++ b/src/shaderchains/beta.cpp @@ -12,23 +12,19 @@ Beta::Beta(const Device& device, const Core::DescriptorPool& pool, Core::ShaderModule(device, "rsc/shaders/beta/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 8+4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/beta/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/beta/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/beta/3.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/beta/4.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, @@ -71,25 +67,21 @@ Beta::Beta(const Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->temporalImgs) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(1).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(2).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(3).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(4).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) diff --git a/src/shaderchains/delta.cpp b/src/shaderchains/delta.cpp index 340d00a..083968e 100644 --- a/src/shaderchains/delta.cpp +++ b/src/shaderchains/delta.cpp @@ -12,20 +12,17 @@ Delta::Delta(const Device& device, const Core::DescriptorPool& pool, Core::ShaderModule(device, "rsc/shaders/delta/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/delta/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/delta/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/delta/3.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) @@ -58,7 +55,7 @@ Delta::Delta(const Device& device, const Core::DescriptorPool& pool, this->outImg = Core::Image(device, extent, - VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); @@ -66,25 +63,23 @@ Delta::Delta(const Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(1).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(2).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(3).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); } diff --git a/src/shaderchains/epsilon.cpp b/src/shaderchains/epsilon.cpp index 77d4269..be905cf 100644 --- a/src/shaderchains/epsilon.cpp +++ b/src/shaderchains/epsilon.cpp @@ -14,20 +14,17 @@ Epsilon::Epsilon(const Device& device, const Core::DescriptorPool& pool, Core::ShaderModule(device, "rsc/shaders/epsilon/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/epsilon/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/epsilon/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/epsilon/3.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) @@ -57,7 +54,7 @@ Epsilon::Epsilon(const Device& device, const Core::DescriptorPool& pool, this->outImg = Core::Image(device, extent, - VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); @@ -65,22 +62,20 @@ Epsilon::Epsilon(const Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(1).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(2).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(3).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) diff --git a/src/shaderchains/extract.cpp b/src/shaderchains/extract.cpp index 58284f6..c3a391a 100644 --- a/src/shaderchains/extract.cpp +++ b/src/shaderchains/extract.cpp @@ -10,7 +10,7 @@ Extract::Extract(const Device& device, const Core::DescriptorPool& pool, : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)) { this->shaderModule = Core::ShaderModule(device, "rsc/shaders/extract.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); @@ -21,7 +21,8 @@ Extract::Extract(const Device& device, const Core::DescriptorPool& pool, this->whiteImg = Core::Image(device, outExtent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT + | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT); this->outImg = Core::Image(device, outExtent, @@ -31,6 +32,7 @@ Extract::Extract(const Device& device, const Core::DescriptorPool& pool, this->descriptorSet.update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) @@ -39,7 +41,7 @@ Extract::Extract(const Device& device, const Core::DescriptorPool& pool, .build(); // clear white image - Utils::clearWhiteImage(device, this->whiteImg); + Utils::clearImage(device, this->whiteImg, true); } void Extract::Dispatch(const Core::CommandBuffer& buf) { diff --git a/src/shaderchains/gamma.cpp b/src/shaderchains/gamma.cpp index bef9de0..1d77549 100644 --- a/src/shaderchains/gamma.cpp +++ b/src/shaderchains/gamma.cpp @@ -7,7 +7,7 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, std::array temporalImgs, std::array inImgs1, Core::Image inImg2, - std::optional& optImg1, + std::optional optImg1, // NOLINT std::optional optImg2, VkExtent2D outExtent) : temporalImgs(std::move(temporalImgs)), @@ -16,34 +16,31 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, optImg2(std::move(optImg2)) { this->shaderModules = {{ Core::ShaderModule(device, "rsc/shaders/gamma/0.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 8+4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 10, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), Core::ShaderModule(device, "rsc/shaders/gamma/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/gamma/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/gamma/3.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/gamma/4.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), Core::ShaderModule(device, "rsc/shaders/gamma/5.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) }}; for (size_t i = 0; i < 6; i++) { @@ -78,12 +75,13 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, } this->whiteImg = Core::Image(device, outExtent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT + | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT); this->outImg1 = Core::Image(device, extent, - VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); this->outImg2 = Core::Image(device, @@ -94,6 +92,7 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, this->descriptorSets.at(0).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->temporalImgs) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) @@ -109,22 +108,20 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, .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) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(2).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(3).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(4).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) @@ -133,14 +130,17 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, .build(); this->descriptorSets.at(5).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->outImg1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2) .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); - // clear white image - Utils::clearWhiteImage(device, this->whiteImg); + // clear white image and optImg1 if needed + Utils::clearImage(device, this->whiteImg, true); + if (!optImg1.has_value()) + Utils::clearImage(device, this->optImg1); } void Gamma::Dispatch(const Core::CommandBuffer& buf) { @@ -197,9 +197,6 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf) { buf.dispatch(threadsX, threadsY, 1); // fifth pass - threadsX = (extent.width + 31) >> 5; - threadsY = (extent.height + 31) >> 5; - Utils::BarrierBuilder(buf) .addW2R(this->tempImgs2) .addW2R(this->optImg2) diff --git a/src/shaderchains/magic.cpp b/src/shaderchains/magic.cpp index 4eb1c42..41d1b49 100644 --- a/src/shaderchains/magic.cpp +++ b/src/shaderchains/magic.cpp @@ -14,7 +14,7 @@ Magic::Magic(const Device& device, const Core::DescriptorPool& pool, inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), optImg(std::move(optImg)) { this->shaderModule = Core::ShaderModule(device, "rsc/shaders/magic.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4+4+2+1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 3+3+2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); @@ -48,6 +48,7 @@ Magic::Magic(const Device& device, const Core::DescriptorPool& pool, this->descriptorSet.update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->temporalImgs) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) diff --git a/src/shaderchains/merge.cpp b/src/shaderchains/merge.cpp index e93d645..6dee8d9 100644 --- a/src/shaderchains/merge.cpp +++ b/src/shaderchains/merge.cpp @@ -16,7 +16,7 @@ Merge::Merge(const Device& device, const Core::DescriptorPool& pool, inImg4(std::move(inImg4)), inImg5(std::move(inImg5)) { this->shaderModule = Core::ShaderModule(device, "rsc/shaders/merge.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); @@ -34,6 +34,7 @@ Merge::Merge(const Device& device, const Core::DescriptorPool& pool, this->descriptorSet.update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) diff --git a/src/shaderchains/zeta.cpp b/src/shaderchains/zeta.cpp index d614a53..6700bf9 100644 --- a/src/shaderchains/zeta.cpp +++ b/src/shaderchains/zeta.cpp @@ -14,20 +14,17 @@ Zeta::Zeta(const Device& device, const Core::DescriptorPool& pool, Core::ShaderModule(device, "rsc/shaders/zeta/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/zeta/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/zeta/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), Core::ShaderModule(device, "rsc/shaders/zeta/3.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) @@ -57,7 +54,7 @@ Zeta::Zeta(const Device& device, const Core::DescriptorPool& pool, this->outImg = Core::Image(device, extent, - VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); @@ -65,22 +62,20 @@ Zeta::Zeta(const Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(1).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(2).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); this->descriptorSets.at(3).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) diff --git a/src/utils.cpp b/src/utils.cpp index f8fc6a1..0b07708 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -131,7 +131,7 @@ void Utils::uploadImage(const Device& device, const Core::CommandPool& commandPo throw ls::vulkan_error(VK_TIMEOUT, "Upload operation timed out"); } -void Utils::clearWhiteImage(const Device& device, Core::Image& image) { +void Utils::clearImage(const Device& device, Core::Image& image, bool white) { Core::Fence fence(device); const Core::CommandPool cmdPool(device); Core::CommandBuffer cmdBuf(device, cmdPool); @@ -158,7 +158,8 @@ void Utils::clearWhiteImage(const Device& device, Core::Image& image) { image.setLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); vkCmdPipelineBarrier2(cmdBuf.handle(), &dependencyInfo); - const VkClearColorValue clearColor = {{ 1.0F, 1.0F, 1.0F, 1.0F }}; + 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, @@ -204,6 +205,6 @@ void Globals::uninitializeGlobals() noexcept { } ls::vulkan_error::vulkan_error(VkResult result, const std::string& message) - : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} + : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} ls::vulkan_error::~vulkan_error() noexcept = default; From 64331fbb9a00b783f1ef1b1c0008170942930d9e Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 00:56:37 +0200 Subject: [PATCH 031/253] more testing for bugs --- src/main.cpp | 74 ++++++++++++++++++++------------------ src/shaderchains/gamma.cpp | 3 +- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ee0c50e..799f14a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,7 +47,6 @@ int main() { const Device device(instance); const Core::DescriptorPool descriptorPool(device); const Core::CommandPool commandPool(device); - const Core::Fence fence(device); Globals::initializeGlobals(device); @@ -162,42 +161,49 @@ int main() { deltas.at(2).getOutImage() ); - // start the rendering pipeline - if (rdoc) - rdoc->StartFrameCapture(nullptr, nullptr); + for (int i = 0; i < 1000; i++) { + const Core::Fence fence(device); - Core::CommandBuffer commandBuffer(device, commandPool); - commandBuffer.begin(); + // start the rendering pipeline + if (rdoc) + rdoc->StartFrameCapture(nullptr, nullptr); - downsample.Dispatch(commandBuffer); - for (size_t i = 0; i < 7; i++) - alphas.at(6 - i).Dispatch(commandBuffer); - beta.Dispatch(commandBuffer); - for (size_t i = 0; i < 4; i++) - gammas.at(i).Dispatch(commandBuffer); - for (size_t i = 0; i < 3; i++) { - magics.at(i).Dispatch(commandBuffer); - deltas.at(i).Dispatch(commandBuffer); - epsilons.at(i).Dispatch(commandBuffer); - zetas.at(i).Dispatch(commandBuffer); - if (i < 2) - extracts.at(i).Dispatch(commandBuffer); + Core::CommandBuffer commandBuffer(device, commandPool); + commandBuffer.begin(); + + downsample.Dispatch(commandBuffer); + for (size_t i = 0; i < 7; i++) + alphas.at(6 - i).Dispatch(commandBuffer); + beta.Dispatch(commandBuffer); + for (size_t i = 0; i < 4; i++) + gammas.at(i).Dispatch(commandBuffer); + for (size_t i = 0; i < 3; i++) { + magics.at(i).Dispatch(commandBuffer); + deltas.at(i).Dispatch(commandBuffer); + epsilons.at(i).Dispatch(commandBuffer); + zetas.at(i).Dispatch(commandBuffer); + if (i < 2) + extracts.at(i).Dispatch(commandBuffer); + } + merge.Dispatch(commandBuffer); + + // finish the rendering pipeline + commandBuffer.end(); + + commandBuffer.submit(device.getComputeQueue(), fence); + if (!fence.wait(device)) { + Globals::uninitializeGlobals(); + + std::cerr << "Application failed due to timeout" << '\n'; + return 1; + } + + if (rdoc) + rdoc->EndFrameCapture(nullptr, nullptr); + + // sleep 8 ms + usleep(8000); } - merge.Dispatch(commandBuffer); - - // finish the rendering pipeline - commandBuffer.end(); - - commandBuffer.submit(device.getComputeQueue(), fence); - if (!fence.wait(device)) { - Globals::uninitializeGlobals(); - - std::cerr << "Application failed due to timeout" << '\n'; - return 1; - } - - if (rdoc) - rdoc->EndFrameCapture(nullptr, nullptr); usleep(1000 * 100); // give renderdoc time to capture diff --git a/src/shaderchains/gamma.cpp b/src/shaderchains/gamma.cpp index 1d77549..7001a9c 100644 --- a/src/shaderchains/gamma.cpp +++ b/src/shaderchains/gamma.cpp @@ -58,7 +58,8 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, this->optImg1 = optImg1.value_or(Core::Image(device, extent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT + | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT)); for (size_t i = 0; i < 4; i++) { From 3a7864ee19e21e4491c7f7c22aa60cbaf497cd62 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 03:17:40 +0200 Subject: [PATCH 032/253] switch namespace and initial draft for lsfg main --- include/core/buffer.hpp | 6 +- include/core/commandbuffer.hpp | 10 +- include/core/commandpool.hpp | 4 +- include/core/descriptorpool.hpp | 4 +- include/core/descriptorset.hpp | 4 +- include/core/fence.hpp | 8 +- include/core/image.hpp | 4 +- include/core/pipeline.hpp | 4 +- include/core/sampler.hpp | 4 +- include/core/semaphore.hpp | 8 +- include/core/shadermodule.hpp | 4 +- include/device.hpp | 7 +- include/instance.hpp | 4 +- include/lsfg.hpp | 128 +++++++++++++ include/shaderchains/alpha.hpp | 6 +- include/shaderchains/beta.hpp | 6 +- include/shaderchains/delta.hpp | 6 +- include/shaderchains/downsample.hpp | 6 +- include/shaderchains/epsilon.hpp | 6 +- include/shaderchains/extract.hpp | 6 +- include/shaderchains/gamma.hpp | 6 +- include/shaderchains/magic.hpp | 6 +- include/shaderchains/merge.hpp | 6 +- include/shaderchains/zeta.hpp | 6 +- include/utils.hpp | 34 +--- src/core/buffer.cpp | 14 +- src/core/commandbuffer.cpp | 12 +- src/core/commandpool.cpp | 6 +- src/core/descriptorpool.cpp | 6 +- src/core/descriptorset.cpp | 7 +- src/core/fence.cpp | 10 +- src/core/image.cpp | 14 +- src/core/pipeline.cpp | 8 +- src/core/sampler.cpp | 6 +- src/core/semaphore.cpp | 10 +- src/core/shadermodule.cpp | 8 +- src/device.cpp | 15 +- src/instance.cpp | 6 +- src/lsfg.cpp | 106 +++++++++++ src/main.cpp | 274 ++++++++++++++-------------- src/shaderchains/alpha.cpp | 2 +- src/shaderchains/beta.cpp | 2 +- src/shaderchains/delta.cpp | 2 +- src/shaderchains/downsample.cpp | 2 +- src/shaderchains/epsilon.cpp | 2 +- src/shaderchains/extract.cpp | 2 +- src/shaderchains/gamma.cpp | 2 +- src/shaderchains/magic.cpp | 2 +- src/shaderchains/merge.cpp | 3 +- src/shaderchains/zeta.cpp | 2 +- src/utils.cpp | 16 +- 51 files changed, 525 insertions(+), 307 deletions(-) create mode 100644 include/lsfg.hpp create mode 100644 src/lsfg.cpp diff --git a/include/core/buffer.hpp b/include/core/buffer.hpp index ebe1889..2e283f1 100644 --- a/include/core/buffer.hpp +++ b/include/core/buffer.hpp @@ -7,7 +7,7 @@ #include -namespace Vulkan::Core { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan buffer. @@ -25,7 +25,7 @@ namespace Vulkan::Core { /// @param data Initial data for the buffer, also specifies the size of the buffer. /// @param usage Usage flags for the buffer /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// template Buffer(const Device& device, const T& data, VkBufferUsageFlags usage) @@ -41,7 +41,7 @@ namespace Vulkan::Core { /// @param size Size of the buffer in bytes /// @param usage Usage flags for the buffer /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// Buffer(const Device& device, const void* data, size_t size, VkBufferUsageFlags usage) : size(size) { diff --git a/include/core/commandbuffer.hpp b/include/core/commandbuffer.hpp index f028435..aaed19e 100644 --- a/include/core/commandbuffer.hpp +++ b/include/core/commandbuffer.hpp @@ -12,7 +12,7 @@ #include #include -namespace Vulkan::Core { +namespace LSFG::Core { /// State of the command buffer. enum class CommandBufferState { @@ -43,7 +43,7 @@ namespace Vulkan::Core { /// @param device Vulkan device /// @param pool Vulkan command pool /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// CommandBuffer(const Device& device, const CommandPool& pool); @@ -51,7 +51,7 @@ namespace Vulkan::Core { /// Begin recording commands in the command buffer. /// /// @throws std::logic_error if the command buffer is in Empty state - /// @throws ls::vulkan_error if beginning the command buffer fails. + /// @throws LSFG::vulkan_error if beginning the command buffer fails. /// void begin(); @@ -70,7 +70,7 @@ namespace Vulkan::Core { /// End recording commands in the command buffer. /// /// @throws std::logic_error if the command buffer is not in Recording state - /// @throws ls::vulkan_error if ending the command buffer fails. + /// @throws LSFG::vulkan_error if ending the command buffer fails. /// void end(); @@ -85,7 +85,7 @@ namespace Vulkan::Core { /// @param signalSemaphoreValues Values for the semaphores to signal /// /// @throws std::logic_error if the command buffer is not in Full state. - /// @throws ls::vulkan_error if submission fails. + /// @throws LSFG::vulkan_error if submission fails. /// void submit(VkQueue queue, std::optional fence, const std::vector& waitSemaphores = {}, diff --git a/include/core/commandpool.hpp b/include/core/commandpool.hpp index 247b717..d444b4a 100644 --- a/include/core/commandpool.hpp +++ b/include/core/commandpool.hpp @@ -7,7 +7,7 @@ #include -namespace Vulkan::Core { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan command pool. @@ -23,7 +23,7 @@ namespace Vulkan::Core { /// /// @param device Vulkan device /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// CommandPool(const Device& device); diff --git a/include/core/descriptorpool.hpp b/include/core/descriptorpool.hpp index 0db8018..5ffac10 100644 --- a/include/core/descriptorpool.hpp +++ b/include/core/descriptorpool.hpp @@ -7,7 +7,7 @@ #include -namespace Vulkan::Core { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan descriptor pool. @@ -23,7 +23,7 @@ namespace Vulkan::Core { /// /// @param device Vulkan device /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// DescriptorPool(const Device& device); diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp index 8d9d770..79b2b91 100644 --- a/include/core/descriptorset.hpp +++ b/include/core/descriptorset.hpp @@ -14,7 +14,7 @@ #include -namespace Vulkan::Core { +namespace LSFG::Core { class DescriptorSetUpdateBuilder; @@ -34,7 +34,7 @@ namespace Vulkan::Core { /// @param pool Descriptor pool to allocate from /// @param shaderModule Shader module to use for the descriptor set /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// DescriptorSet(const Device& device, const DescriptorPool& pool, const ShaderModule& shaderModule); diff --git a/include/core/fence.hpp b/include/core/fence.hpp index c7cf82a..3d7df5f 100644 --- a/include/core/fence.hpp +++ b/include/core/fence.hpp @@ -7,7 +7,7 @@ #include -namespace Vulkan::Core { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan fence. @@ -23,7 +23,7 @@ namespace Vulkan::Core { /// /// @param device Vulkan device /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// Fence(const Device& device); @@ -32,7 +32,7 @@ namespace Vulkan::Core { /// /// @param device Vulkan device /// - /// @throws ls::vulkan_error if resetting fails. + /// @throws LSFG::vulkan_error if resetting fails. /// void reset(const Device& device) const; @@ -43,7 +43,7 @@ namespace Vulkan::Core { /// @param timeout The timeout in nanoseconds, or UINT64_MAX for no timeout. /// @returns true if the fence signaled, false if it timed out. /// - /// @throws ls::vulkan_error if waiting fails. + /// @throws LSFG::vulkan_error if waiting fails. /// [[nodiscard]] bool wait(const Device& device, uint64_t timeout = UINT64_MAX) const; diff --git a/include/core/image.hpp b/include/core/image.hpp index 8517473..2b016dc 100644 --- a/include/core/image.hpp +++ b/include/core/image.hpp @@ -7,7 +7,7 @@ #include -namespace Vulkan::Core { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan image. @@ -27,7 +27,7 @@ namespace Vulkan::Core { /// @param usage Usage flags for the image /// @param aspectFlags Aspect flags for the image view /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// Image(const Device& device, VkExtent2D extent, VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspectFlags); diff --git a/include/core/pipeline.hpp b/include/core/pipeline.hpp index 88a32f3..812e76a 100644 --- a/include/core/pipeline.hpp +++ b/include/core/pipeline.hpp @@ -9,7 +9,7 @@ #include -namespace Vulkan::Core { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan pipeline. @@ -26,7 +26,7 @@ namespace Vulkan::Core { /// @param device Vulkan device /// @param shader Shader module to use for the pipeline. /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// Pipeline(const Device& device, const ShaderModule& shader); diff --git a/include/core/sampler.hpp b/include/core/sampler.hpp index f08817b..325d783 100644 --- a/include/core/sampler.hpp +++ b/include/core/sampler.hpp @@ -7,7 +7,7 @@ #include -namespace Vulkan::Core { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan sampler. @@ -24,7 +24,7 @@ namespace Vulkan::Core { /// @param device Vulkan device /// @param mode Address mode for the sampler. /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// Sampler(const Device& device, VkSamplerAddressMode mode); diff --git a/include/core/semaphore.hpp b/include/core/semaphore.hpp index 41fa7e5..cd2ae53 100644 --- a/include/core/semaphore.hpp +++ b/include/core/semaphore.hpp @@ -8,7 +8,7 @@ #include #include -namespace Vulkan::Core { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan semaphore. @@ -25,7 +25,7 @@ namespace Vulkan::Core { /// @param device Vulkan device /// @param initial Optional initial value for creating a timeline semaphore. /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// Semaphore(const Device& device, std::optional initial = std::nullopt); @@ -36,7 +36,7 @@ namespace Vulkan::Core { /// @param value The value to signal the semaphore to. /// /// @throws std::logic_error if the semaphore is not a timeline semaphore. - /// @throws ls::vulkan_error if signaling fails. + /// @throws LSFG::vulkan_error if signaling fails. /// void signal(const Device& device, uint64_t value) const; @@ -49,7 +49,7 @@ namespace Vulkan::Core { /// @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 ls::vulkan_error if waiting fails. + /// @throws LSFG::vulkan_error if waiting fails. /// [[nodiscard]] bool wait(const Device& device, uint64_t value, uint64_t timeout = UINT64_MAX) const; diff --git a/include/core/shadermodule.hpp b/include/core/shadermodule.hpp index de1f0da..4a06541 100644 --- a/include/core/shadermodule.hpp +++ b/include/core/shadermodule.hpp @@ -10,7 +10,7 @@ #include #include -namespace Vulkan::Core { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan shader module. @@ -29,7 +29,7 @@ namespace Vulkan::Core { /// @param descriptorTypes Descriptor types used in the shader. /// /// @throws std::system_error if the shader file cannot be opened or read. - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// ShaderModule(const Device& device, const std::string& path, const std::vector>& descriptorTypes); diff --git a/include/device.hpp b/include/device.hpp index ef9ddcf..25d2702 100644 --- a/include/device.hpp +++ b/include/device.hpp @@ -2,12 +2,13 @@ #define DEVICE_HPP #include "instance.hpp" + #include #include #include -namespace Vulkan { +namespace LSFG { /// /// C++ wrapper class for a Vulkan device. @@ -21,9 +22,9 @@ namespace Vulkan { /// /// @param instance Vulkan instance /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// - Device(const Vulkan::Instance& instance); + Device(const Instance& instance); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->device; } diff --git a/include/instance.hpp b/include/instance.hpp index 7b9d7ff..c957f72 100644 --- a/include/instance.hpp +++ b/include/instance.hpp @@ -5,7 +5,7 @@ #include -namespace Vulkan { +namespace LSFG { /// /// C++ wrapper class for a Vulkan instance. @@ -17,7 +17,7 @@ namespace Vulkan { /// /// Create the instance. /// - /// @throws ls::vulkan_error if object creation fails. + /// @throws LSFG::vulkan_error if object creation fails. /// Instance(); diff --git a/include/lsfg.hpp b/include/lsfg.hpp new file mode 100644 index 0000000..1a459e7 --- /dev/null +++ b/include/lsfg.hpp @@ -0,0 +1,128 @@ +#ifndef LSFG_HPP +#define LSFG_HPP + +#include "core/commandpool.hpp" +#include "core/descriptorpool.hpp" +#include "core/image.hpp" +#include "device.hpp" +#include "instance.hpp" +#include "shaderchains/alpha.hpp" +#include "shaderchains/beta.hpp" +#include "shaderchains/delta.hpp" +#include "shaderchains/downsample.hpp" +#include "shaderchains/epsilon.hpp" +#include "shaderchains/extract.hpp" +#include "shaderchains/gamma.hpp" +#include "shaderchains/magic.hpp" +#include "shaderchains/merge.hpp" +#include "shaderchains/zeta.hpp" +#include "utils.hpp" + +namespace LSFG { + + class Generator; + + /// LSFG context. + class Context { + friend class Generator; // FIXME: getters, I'm lazy + public: + /// + /// Initialize the LSFG Vulkan instance. + /// + /// @throws LSFG::vulkan_error if the Vulkan objects cannot be created. + /// + Context() { Globals::initializeGlobals(device); } // FIXME: no need for globals + + /// + /// Create a generator instance. + /// + /// @throws LSFG::vulkan_error if the generator cannot be created. + /// + const Generator& create(); + + /// + /// Present a generator instance. + /// + /// @throws LSFG::vulkan_error if the generator fails to present. + /// + void present(const Generator& gen); + + /// Trivial copyable, moveable and destructible + Context(const Context&) = default; + Context& operator=(const Context&) = default; + Context(Context&&) = default; + Context& operator=(Context&&) = default; + ~Context() { Globals::uninitializeGlobals(); } + private: + Instance instance; + Device device{instance}; + Core::DescriptorPool descPool{device}; + Core::CommandPool cmdPool{device}; + }; + + /// Per-swapchain instance of LSFG. + class Generator { + public: + /// + /// Create a generator instance. + /// + /// @param context The LSFG context to use. + /// + Generator(const Context& context); + + /// + /// Present. + /// + /// @throws LSFG::vulkan_error if the generator fails to present. + /// + void present(const Context& context); + + // Trivially copyable, moveable and destructible + Generator(const Generator&) = default; + Generator(Generator&&) = default; + Generator& operator=(const Generator&) = default; + Generator& operator=(Generator&&) = default; + ~Generator() = default; + private: + Core::Image fullFrame0, fullFrame1; // next/prev for fc % 2 == 0 + uint64_t fc{0}; + + Shaderchains::Downsample downsampleChain; // FIXME: get rid of default constructors (+ core) + std::array alphaChains; + Shaderchains::Beta betaChain; + std::array gammaChains; + std::array magicChains; + std::array deltaChains; + std::array epsilonChains; + std::array zetaChains; + std::array extractChains; + Shaderchains::Merge mergeChain; + }; + + /// 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; + }; + +} + +#endif // LSFG_HPP diff --git a/include/shaderchains/alpha.hpp b/include/shaderchains/alpha.hpp index 403a1a5..5f8dec0 100644 --- a/include/shaderchains/alpha.hpp +++ b/include/shaderchains/alpha.hpp @@ -11,7 +11,7 @@ #include -namespace Vulkan::Shaderchains { +namespace LSFG::Shaderchains { /// /// Shader chain alpha. @@ -20,6 +20,8 @@ namespace Vulkan::Shaderchains { /// class Alpha { public: + Alpha() = default; + /// /// Initialize the shaderchain. /// @@ -27,7 +29,7 @@ namespace Vulkan::Shaderchains { /// @param pool The descriptor pool to allocate in. /// @param inImg The input image to process /// - /// @throws ls::vulkan_error if resource creation fails. + /// @throws LSFG::vulkan_error if resource creation fails. /// Alpha(const Device& device, const Core::DescriptorPool& pool, Core::Image inImg); diff --git a/include/shaderchains/beta.hpp b/include/shaderchains/beta.hpp index ca52bd5..3aa37e8 100644 --- a/include/shaderchains/beta.hpp +++ b/include/shaderchains/beta.hpp @@ -12,7 +12,7 @@ #include -namespace Vulkan::Shaderchains { +namespace LSFG::Shaderchains { /// /// Shader chain beta. @@ -22,6 +22,8 @@ namespace Vulkan::Shaderchains { /// class Beta { public: + Beta() = default; + /// /// Initialize the shaderchain. /// @@ -30,7 +32,7 @@ namespace Vulkan::Shaderchains { /// @param temporalImgs The temporal images to use for processing. /// @param inImgs The input images to process /// - /// @throws ls::vulkan_error if resource creation fails. + /// @throws LSFG::vulkan_error if resource creation fails. /// Beta(const Device& device, const Core::DescriptorPool& pool, std::array temporalImgs, diff --git a/include/shaderchains/delta.hpp b/include/shaderchains/delta.hpp index dcb561b..cd67869 100644 --- a/include/shaderchains/delta.hpp +++ b/include/shaderchains/delta.hpp @@ -12,7 +12,7 @@ #include -namespace Vulkan::Shaderchains { +namespace LSFG::Shaderchains { /// /// Shader chain delta. @@ -22,6 +22,8 @@ namespace Vulkan::Shaderchains { /// class Delta { public: + Delta() = default; + /// /// Initialize the shaderchain. /// @@ -30,7 +32,7 @@ namespace Vulkan::Shaderchains { /// @param inImgs The input images to process. /// @param optImg An optional additional input from the previous pass. /// - /// @throws ls::vulkan_error if resource creation fails. + /// @throws LSFG::vulkan_error if resource creation fails. /// Delta(const Device& device, const Core::DescriptorPool& pool, std::array inImgs, diff --git a/include/shaderchains/downsample.hpp b/include/shaderchains/downsample.hpp index 1a5d399..f799480 100644 --- a/include/shaderchains/downsample.hpp +++ b/include/shaderchains/downsample.hpp @@ -12,7 +12,7 @@ #include -namespace Vulkan::Shaderchains { +namespace LSFG::Shaderchains { /// /// Downsample shader. @@ -21,6 +21,8 @@ namespace Vulkan::Shaderchains { /// class Downsample { public: + Downsample() = default; + /// /// Initialize the shaderchain. /// @@ -28,7 +30,7 @@ namespace Vulkan::Shaderchains { /// @param pool The descriptor pool to allocate in. /// @param inImg The input image to downsample. /// - /// @throws ls::vulkan_error if resource creation fails. + /// @throws LSFG::vulkan_error if resource creation fails. /// Downsample(const Device& device, const Core::DescriptorPool& pool, Core::Image inImg); diff --git a/include/shaderchains/epsilon.hpp b/include/shaderchains/epsilon.hpp index 6ad438a..97f2d27 100644 --- a/include/shaderchains/epsilon.hpp +++ b/include/shaderchains/epsilon.hpp @@ -12,7 +12,7 @@ #include -namespace Vulkan::Shaderchains { +namespace LSFG::Shaderchains { /// /// Shader chain epsilon. @@ -22,6 +22,8 @@ namespace Vulkan::Shaderchains { /// class Epsilon { public: + Epsilon() = default; + /// /// Initialize the shaderchain. /// @@ -31,7 +33,7 @@ namespace Vulkan::Shaderchains { /// @param inImg2 The second type image to process. /// @param optImg An optional additional input from the previous pass. /// - /// @throws ls::vulkan_error if resource creation fails. + /// @throws LSFG::vulkan_error if resource creation fails. /// Epsilon(const Device& device, const Core::DescriptorPool& pool, std::array inImgs1, diff --git a/include/shaderchains/extract.hpp b/include/shaderchains/extract.hpp index e1a7a72..abf3fcc 100644 --- a/include/shaderchains/extract.hpp +++ b/include/shaderchains/extract.hpp @@ -10,7 +10,7 @@ #include "core/shadermodule.hpp" #include "device.hpp" -namespace Vulkan::Shaderchains { +namespace LSFG::Shaderchains { /// /// Shader chain extract. @@ -20,6 +20,8 @@ namespace Vulkan::Shaderchains { /// class Extract { public: + Extract() = default; + /// /// Initialize the shaderchain. /// @@ -29,7 +31,7 @@ namespace Vulkan::Shaderchains { /// @param inImg2 The second type image to process. /// @param outExtent The extent of the output image. /// - /// @throws ls::vulkan_error if resource creation fails. + /// @throws LSFG::vulkan_error if resource creation fails. /// Extract(const Device& device, const Core::DescriptorPool& pool, Core::Image inImg1, diff --git a/include/shaderchains/gamma.hpp b/include/shaderchains/gamma.hpp index dad16c0..790c1d6 100644 --- a/include/shaderchains/gamma.hpp +++ b/include/shaderchains/gamma.hpp @@ -12,7 +12,7 @@ #include -namespace Vulkan::Shaderchains { +namespace LSFG::Shaderchains { /// /// Shader chain gamma. @@ -24,6 +24,8 @@ namespace Vulkan::Shaderchains { /// class Gamma { public: + Gamma() = default; + /// /// Initialize the shaderchain. /// @@ -36,7 +38,7 @@ namespace Vulkan::Shaderchains { /// @param optImg2 An optional additional input image for processing non-first passes. /// @param outExtent The extent of the output image. /// - /// @throws ls::vulkan_error if resource creation fails. + /// @throws LSFG::vulkan_error if resource creation fails. /// Gamma(const Device& device, const Core::DescriptorPool& pool, std::array temporalImgs, diff --git a/include/shaderchains/magic.hpp b/include/shaderchains/magic.hpp index 7c21cb8..934a9f8 100644 --- a/include/shaderchains/magic.hpp +++ b/include/shaderchains/magic.hpp @@ -12,7 +12,7 @@ #include -namespace Vulkan::Shaderchains { +namespace LSFG::Shaderchains { /// /// Shader chain magic. @@ -22,6 +22,8 @@ namespace Vulkan::Shaderchains { /// class Magic { public: + Magic() = default; + /// /// Initialize the shaderchain. /// @@ -33,7 +35,7 @@ namespace Vulkan::Shaderchains { /// @param inImg3 The third input image to process, next step up the resolution. /// @param optImg An optional additional input from the previous pass. /// - /// @throws ls::vulkan_error if resource creation fails. + /// @throws LSFG::vulkan_error if resource creation fails. /// Magic(const Device& device, const Core::DescriptorPool& pool, std::array temporalImgs, diff --git a/include/shaderchains/merge.hpp b/include/shaderchains/merge.hpp index aeb229a..7ad12da 100644 --- a/include/shaderchains/merge.hpp +++ b/include/shaderchains/merge.hpp @@ -10,7 +10,7 @@ #include "core/shadermodule.hpp" #include "device.hpp" -namespace Vulkan::Shaderchains { +namespace LSFG::Shaderchains { /// /// Shader chain merge. @@ -20,6 +20,8 @@ namespace Vulkan::Shaderchains { /// class Merge { public: + Merge() = default; + /// /// Initialize the shaderchain. /// @@ -31,7 +33,7 @@ namespace Vulkan::Shaderchains { /// @param inImg4 The second related input texture /// @param inImg5 The third related input texture /// - /// @throws ls::vulkan_error if resource creation fails. + /// @throws LSFG::vulkan_error if resource creation fails. /// Merge(const Device& device, const Core::DescriptorPool& pool, Core::Image inImg1, diff --git a/include/shaderchains/zeta.hpp b/include/shaderchains/zeta.hpp index 7e700d3..56eeef4 100644 --- a/include/shaderchains/zeta.hpp +++ b/include/shaderchains/zeta.hpp @@ -12,7 +12,7 @@ #include -namespace Vulkan::Shaderchains { +namespace LSFG::Shaderchains { /// /// Shader chain zeta. @@ -22,6 +22,8 @@ namespace Vulkan::Shaderchains { /// class Zeta { public: + Zeta() = default; + /// /// Initialize the shaderchain. /// @@ -31,7 +33,7 @@ namespace Vulkan::Shaderchains { /// @param inImg2 The second type image to process. /// @param inImg3 The third type image to process. /// - /// @throws ls::vulkan_error if resource creation fails. + /// @throws LSFG::vulkan_error if resource creation fails. /// Zeta(const Device& device, const Core::DescriptorPool& pool, std::array inImgs1, diff --git a/include/utils.hpp b/include/utils.hpp index 92e2a2b..e6f7e64 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -10,7 +10,7 @@ #include #include -namespace Vulkan::Utils { +namespace LSFG::Utils { /// /// Insert memory barriers for images in a command buffer. @@ -79,13 +79,13 @@ namespace Vulkan::Utils { /// @param image The image to clear. /// @param white If true, the image will be cleared to white, otherwise to black. /// - /// @throws ls::vulkan_error If the Vulkan image cannot be cleared. + /// @throws LSFG::vulkan_error If the Vulkan image cannot be cleared. /// void clearImage(const Device& device, Core::Image& image, bool white = false); } -namespace Vulkan::Globals { +namespace LSFG::Globals { /// Global sampler with address mode set to clamp to border. extern Core::Sampler samplerClampBorder; @@ -118,32 +118,4 @@ namespace Vulkan::Globals { } -namespace ls { - - /// 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; - }; - -} - #endif // UTILS_HPP diff --git a/src/core/buffer.cpp b/src/core/buffer.cpp index d6982b3..b446cd4 100644 --- a/src/core/buffer.cpp +++ b/src/core/buffer.cpp @@ -1,10 +1,10 @@ #include "core/buffer.hpp" -#include "utils.hpp" +#include "lsfg.hpp" #include #include -using namespace Vulkan::Core; +using namespace LSFG::Core; void Buffer::construct(const Device& device, const void* data, VkBufferUsageFlags usage) { // create buffer @@ -17,7 +17,7 @@ void Buffer::construct(const Device& device, const void* data, VkBufferUsageFlag VkBuffer bufferHandle{}; auto res = vkCreateBuffer(device.handle(), &desc, nullptr, &bufferHandle); if (res != VK_SUCCESS || bufferHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Failed to create Vulkan buffer"); + throw LSFG::vulkan_error(res, "Failed to create Vulkan buffer"); // find memory type VkPhysicalDeviceMemoryProperties memProps; @@ -38,7 +38,7 @@ void Buffer::construct(const Device& device, const void* data, VkBufferUsageFlag } // NOLINTEND } if (!memType.has_value()) - throw ls::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for buffer"); + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for buffer"); #pragma clang diagnostic pop // allocate and bind memory @@ -50,17 +50,17 @@ void Buffer::construct(const Device& device, const void* data, VkBufferUsageFlag VkDeviceMemory memoryHandle{}; res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle); if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Failed to allocate memory for Vulkan buffer"); + throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan buffer"); res = vkBindBufferMemory(device.handle(), bufferHandle, memoryHandle, 0); if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Failed to bind memory to Vulkan buffer"); + throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan buffer"); // upload data to buffer uint8_t* buf{}; res = vkMapMemory(device.handle(), memoryHandle, 0, this->size, 0, reinterpret_cast(&buf)); if (res != VK_SUCCESS || buf == nullptr) - throw ls::vulkan_error(res, "Failed to map memory for Vulkan buffer"); + throw LSFG::vulkan_error(res, "Failed to map memory for Vulkan buffer"); std::copy_n(reinterpret_cast(data), this->size, buf); vkUnmapMemory(device.handle(), memoryHandle); diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp index bee6792..6c7d99d 100644 --- a/src/core/commandbuffer.cpp +++ b/src/core/commandbuffer.cpp @@ -1,7 +1,7 @@ #include "core/commandbuffer.hpp" -#include "utils.hpp" +#include "lsfg.hpp" -using namespace Vulkan::Core; +using namespace LSFG::Core; CommandBuffer::CommandBuffer(const Device& device, const CommandPool& pool) { // create command buffer @@ -14,7 +14,7 @@ CommandBuffer::CommandBuffer(const Device& device, const CommandPool& pool) { VkCommandBuffer commandBufferHandle{}; auto res = vkAllocateCommandBuffers(device.handle(), &desc, &commandBufferHandle); if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Unable to allocate command buffer"); + throw LSFG::vulkan_error(res, "Unable to allocate command buffer"); // store command buffer in shared ptr this->state = std::make_shared(CommandBufferState::Empty); @@ -36,7 +36,7 @@ void CommandBuffer::begin() { }; auto res = vkBeginCommandBuffer(*this->commandBuffer, &beginInfo); if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Unable to begin command buffer"); + throw LSFG::vulkan_error(res, "Unable to begin command buffer"); *this->state = CommandBufferState::Recording; } @@ -54,7 +54,7 @@ void CommandBuffer::end() { auto res = vkEndCommandBuffer(*this->commandBuffer); if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Unable to end command buffer"); + throw LSFG::vulkan_error(res, "Unable to end command buffer"); *this->state = CommandBufferState::Full; } @@ -104,7 +104,7 @@ void CommandBuffer::submit(VkQueue queue, std::optional fence, }; auto res = vkQueueSubmit(queue, 1, &submitInfo, fence ? fence->handle() : VK_NULL_HANDLE); if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Unable to submit command buffer"); + throw LSFG::vulkan_error(res, "Unable to submit command buffer"); *this->state = CommandBufferState::Submitted; } diff --git a/src/core/commandpool.cpp b/src/core/commandpool.cpp index cb99d9f..efd2098 100644 --- a/src/core/commandpool.cpp +++ b/src/core/commandpool.cpp @@ -1,7 +1,7 @@ #include "core/commandpool.hpp" -#include "utils.hpp" +#include "lsfg.hpp" -using namespace Vulkan::Core; +using namespace LSFG::Core; CommandPool::CommandPool(const Device& device) { // create command pool @@ -12,7 +12,7 @@ CommandPool::CommandPool(const Device& device) { VkCommandPool commandPoolHandle{}; auto res = vkCreateCommandPool(device.handle(), &desc, nullptr, &commandPoolHandle); if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Unable to create command pool"); + throw LSFG::vulkan_error(res, "Unable to create command pool"); // store command pool in shared ptr this->commandPool = std::shared_ptr( diff --git a/src/core/descriptorpool.cpp b/src/core/descriptorpool.cpp index 5a12e43..1761549 100644 --- a/src/core/descriptorpool.cpp +++ b/src/core/descriptorpool.cpp @@ -1,9 +1,9 @@ #include "core/descriptorpool.hpp" -#include "utils.hpp" +#include "lsfg.hpp" #include -using namespace Vulkan::Core; +using namespace LSFG::Core; DescriptorPool::DescriptorPool(const Device& device) { // create descriptor pool @@ -23,7 +23,7 @@ DescriptorPool::DescriptorPool(const Device& device) { VkDescriptorPool poolHandle{}; auto res = vkCreateDescriptorPool(device.handle(), &desc, nullptr, &poolHandle); if (res != VK_SUCCESS || poolHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Unable to create descriptor pool"); + throw LSFG::vulkan_error(res, "Unable to create descriptor pool"); // store pool in shared ptr this->descriptorPool = std::shared_ptr( diff --git a/src/core/descriptorset.cpp b/src/core/descriptorset.cpp index 16c7d93..cb1df1d 100644 --- a/src/core/descriptorset.cpp +++ b/src/core/descriptorset.cpp @@ -1,8 +1,7 @@ #include "core/descriptorset.hpp" -#include "utils.hpp" -#include +#include "lsfg.hpp" -using namespace Vulkan::Core; +using namespace LSFG::Core; DescriptorSet::DescriptorSet(const Device& device, const DescriptorPool& pool, const ShaderModule& shaderModule) { @@ -17,7 +16,7 @@ DescriptorSet::DescriptorSet(const Device& device, VkDescriptorSet descriptorSetHandle{}; auto res = vkAllocateDescriptorSets(device.handle(), &desc, &descriptorSetHandle); if (res != VK_SUCCESS || descriptorSetHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Unable to allocate descriptor set"); + throw LSFG::vulkan_error(res, "Unable to allocate descriptor set"); /// store set in shared ptr this->descriptorSet = std::shared_ptr( diff --git a/src/core/fence.cpp b/src/core/fence.cpp index ee20e08..f7a12c5 100644 --- a/src/core/fence.cpp +++ b/src/core/fence.cpp @@ -1,7 +1,7 @@ #include "core/fence.hpp" -#include "utils.hpp" +#include "lsfg.hpp" -using namespace Vulkan::Core; +using namespace LSFG::Core; Fence::Fence(const Device& device) { // create fence @@ -11,7 +11,7 @@ Fence::Fence(const Device& device) { VkFence fenceHandle{}; auto res = vkCreateFence(device.handle(), &desc, nullptr, &fenceHandle); if (res != VK_SUCCESS || fenceHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Unable to create fence"); + throw LSFG::vulkan_error(res, "Unable to create fence"); // store fence in shared ptr this->fence = std::shared_ptr( @@ -26,14 +26,14 @@ void Fence::reset(const Device& device) const { VkFence fenceHandle = this->handle(); auto res = vkResetFences(device.handle(), 1, &fenceHandle); if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Unable to reset fence"); + throw LSFG::vulkan_error(res, "Unable to reset fence"); } bool Fence::wait(const 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 ls::vulkan_error(res, "Unable to wait for fence"); + throw LSFG::vulkan_error(res, "Unable to wait for fence"); return res == VK_SUCCESS; } diff --git a/src/core/image.cpp b/src/core/image.cpp index 78b5f01..a24ff85 100644 --- a/src/core/image.cpp +++ b/src/core/image.cpp @@ -1,9 +1,9 @@ #include "core/image.hpp" -#include "utils.hpp" +#include "lsfg.hpp" #include -using namespace Vulkan::Core; +using namespace LSFG::Core; Image::Image(const Device& device, VkExtent2D extent, VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspectFlags) @@ -27,7 +27,7 @@ Image::Image(const Device& device, VkExtent2D extent, VkFormat format, VkImage imageHandle{}; auto res = vkCreateImage(device.handle(), &desc, nullptr, &imageHandle); if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Failed to create Vulkan image"); + throw LSFG::vulkan_error(res, "Failed to create Vulkan image"); // find memory type VkPhysicalDeviceMemoryProperties memProps; @@ -47,7 +47,7 @@ Image::Image(const Device& device, VkExtent2D extent, VkFormat format, } // NOLINTEND } if (!memType.has_value()) - throw ls::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image"); + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image"); #pragma clang diagnostic pop // allocate and bind memory @@ -59,11 +59,11 @@ Image::Image(const Device& device, VkExtent2D extent, VkFormat format, VkDeviceMemory memoryHandle{}; res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle); if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Failed to allocate memory for Vulkan image"); + throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image"); res = vkBindImageMemory(device.handle(), imageHandle, memoryHandle, 0); if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Failed to bind memory to Vulkan image"); + throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image"); // create image view const VkImageViewCreateInfo viewDesc{ @@ -87,7 +87,7 @@ Image::Image(const Device& device, VkExtent2D extent, VkFormat format, VkImageView viewHandle{}; res = vkCreateImageView(device.handle(), &viewDesc, nullptr, &viewHandle); if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Failed to create image view"); + throw LSFG::vulkan_error(res, "Failed to create image view"); // store objects in shared ptr this->layout = std::make_shared(VK_IMAGE_LAYOUT_UNDEFINED); diff --git a/src/core/pipeline.cpp b/src/core/pipeline.cpp index b9fd68b..4fc8fa4 100644 --- a/src/core/pipeline.cpp +++ b/src/core/pipeline.cpp @@ -1,7 +1,7 @@ #include "core/pipeline.hpp" -#include "utils.hpp" +#include "lsfg.hpp" -using namespace Vulkan::Core; +using namespace LSFG::Core; Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { // create pipeline layout @@ -14,7 +14,7 @@ Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { VkPipelineLayout layoutHandle{}; auto res = vkCreatePipelineLayout(device.handle(), &layoutDesc, nullptr, &layoutHandle); if (res != VK_SUCCESS || !layoutHandle) - throw ls::vulkan_error(res, "Failed to create pipeline layout"); + throw LSFG::vulkan_error(res, "Failed to create pipeline layout"); // create pipeline const VkPipelineShaderStageCreateInfo shaderStageInfo{ @@ -32,7 +32,7 @@ Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { res = vkCreateComputePipelines(device.handle(), VK_NULL_HANDLE, 1, &pipelineDesc, nullptr, &pipelineHandle); if (res != VK_SUCCESS || !pipelineHandle) - throw ls::vulkan_error(res, "Failed to create compute pipeline"); + throw LSFG::vulkan_error(res, "Failed to create compute pipeline"); // store layout and pipeline in shared ptr this->layout = std::shared_ptr( diff --git a/src/core/sampler.cpp b/src/core/sampler.cpp index 0bb2e1f..729fac5 100644 --- a/src/core/sampler.cpp +++ b/src/core/sampler.cpp @@ -1,7 +1,7 @@ #include "core/sampler.hpp" -#include "utils.hpp" +#include "lsfg.hpp" -using namespace Vulkan::Core; +using namespace LSFG::Core; Sampler::Sampler(const Device& device, VkSamplerAddressMode mode) { // create sampler @@ -18,7 +18,7 @@ Sampler::Sampler(const Device& device, VkSamplerAddressMode mode) { VkSampler samplerHandle{}; auto res = vkCreateSampler(device.handle(), &desc, nullptr, &samplerHandle); if (res != VK_SUCCESS || samplerHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Unable to create sampler"); + throw LSFG::vulkan_error(res, "Unable to create sampler"); // store sampler in shared ptr this->sampler = std::shared_ptr( diff --git a/src/core/semaphore.cpp b/src/core/semaphore.cpp index 8269477..20599cf 100644 --- a/src/core/semaphore.cpp +++ b/src/core/semaphore.cpp @@ -1,7 +1,7 @@ #include "core/semaphore.hpp" -#include "utils.hpp" +#include "lsfg.hpp" -using namespace Vulkan::Core; +using namespace LSFG::Core; Semaphore::Semaphore(const Device& device, std::optional initial) { // create semaphore @@ -17,7 +17,7 @@ Semaphore::Semaphore(const Device& device, std::optional initial) { VkSemaphore semaphoreHandle{}; auto res = vkCreateSemaphore(device.handle(), &desc, nullptr, &semaphoreHandle); if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Unable to create semaphore"); + throw LSFG::vulkan_error(res, "Unable to create semaphore"); // store semaphore in shared ptr this->isTimeline = initial.has_value(); @@ -40,7 +40,7 @@ void Semaphore::signal(const Device& device, uint64_t value) const { }; auto res = vkSignalSemaphore(device.handle(), &signalInfo); if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Unable to signal semaphore"); + throw LSFG::vulkan_error(res, "Unable to signal semaphore"); } bool Semaphore::wait(const Device& device, uint64_t value, uint64_t timeout) const { @@ -56,7 +56,7 @@ bool Semaphore::wait(const Device& device, uint64_t value, uint64_t timeout) con }; auto res = vkWaitSemaphores(device.handle(), &waitInfo, timeout); if (res != VK_SUCCESS && res != VK_TIMEOUT) - throw ls::vulkan_error(res, "Unable to wait for semaphore"); + throw LSFG::vulkan_error(res, "Unable to wait for semaphore"); return res == VK_SUCCESS; } diff --git a/src/core/shadermodule.cpp b/src/core/shadermodule.cpp index 7880f09..acf0f23 100644 --- a/src/core/shadermodule.cpp +++ b/src/core/shadermodule.cpp @@ -1,9 +1,9 @@ #include "core/shadermodule.hpp" -#include "utils.hpp" +#include "lsfg.hpp" #include -using namespace Vulkan::Core; +using namespace LSFG::Core; ShaderModule::ShaderModule(const Device& device, const std::string& path, const std::vector>& descriptorTypes) { @@ -31,7 +31,7 @@ ShaderModule::ShaderModule(const Device& device, const std::string& path, VkShaderModule shaderModuleHandle{}; auto res = vkCreateShaderModule(device.handle(), &createInfo, nullptr, &shaderModuleHandle); if (res != VK_SUCCESS || !shaderModuleHandle) - throw ls::vulkan_error(res, "Failed to create shader module"); + throw LSFG::vulkan_error(res, "Failed to create shader module"); // create descriptor set layout std::vector layoutBindings; @@ -53,7 +53,7 @@ ShaderModule::ShaderModule(const Device& device, const std::string& path, VkDescriptorSetLayout descriptorSetLayout{}; res = vkCreateDescriptorSetLayout(device.handle(), &layoutDesc, nullptr, &descriptorSetLayout); if (res != VK_SUCCESS || !descriptorSetLayout) - throw ls::vulkan_error(res, "Failed to create descriptor set layout"); + throw LSFG::vulkan_error(res, "Failed to create descriptor set layout"); // store module and layout in shared ptr this->shaderModule = std::shared_ptr( diff --git a/src/device.cpp b/src/device.cpp index 6eb7cd6..bbcd4ff 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -1,11 +1,10 @@ #include "device.hpp" -#include "utils.hpp" +#include "lsfg.hpp" #include #include -#include -using namespace Vulkan; +using namespace LSFG; const std::vector requiredExtensions = { "VK_KHR_external_memory_fd", @@ -17,12 +16,12 @@ Device::Device(const Instance& instance) { uint32_t deviceCount{}; auto res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, nullptr); if (res != VK_SUCCESS || deviceCount == 0) - throw ls::vulkan_error(res, "Failed to enumerate physical devices"); + throw LSFG::vulkan_error(res, "Failed to enumerate physical devices"); std::vector devices(deviceCount); res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, devices.data()); if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Failed to get physical devices"); + throw LSFG::vulkan_error(res, "Failed to get physical devices"); // find first discrete GPU std::optional physicalDevice; @@ -36,7 +35,7 @@ Device::Device(const Instance& instance) { } } if (!physicalDevice) - throw ls::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No discrete GPU found"); + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No discrete GPU found"); // find queue family indices uint32_t familyCount{}; @@ -51,7 +50,7 @@ Device::Device(const Instance& instance) { computeFamilyIdx = i; } if (!computeFamilyIdx) - throw ls::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No compute queue family found"); + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No compute queue family found"); // create logical device const float queuePriority{1.0F}; // highest priority @@ -87,7 +86,7 @@ Device::Device(const Instance& instance) { VkDevice deviceHandle{}; res = vkCreateDevice(*physicalDevice, &deviceCreateInfo, nullptr, &deviceHandle); if (res != VK_SUCCESS | deviceHandle == VK_NULL_HANDLE) - throw ls::vulkan_error(res, "Failed to create logical device"); + throw LSFG::vulkan_error(res, "Failed to create logical device"); // get compute queue VkQueue queueHandle{}; diff --git a/src/instance.cpp b/src/instance.cpp index 1fc90e0..a97de0f 100644 --- a/src/instance.cpp +++ b/src/instance.cpp @@ -1,9 +1,9 @@ #include "instance.hpp" -#include "utils.hpp" +#include "lsfg.hpp" #include -using namespace Vulkan; +using namespace LSFG; const std::vector requiredExtensions = { @@ -34,7 +34,7 @@ Instance::Instance() { VkInstance instanceHandle{}; auto res = vkCreateInstance(&createInfo, nullptr, &instanceHandle); if (res != VK_SUCCESS) - throw ls::vulkan_error(res, "Failed to create Vulkan instance"); + throw LSFG::vulkan_error(res, "Failed to create Vulkan instance"); // store in shared ptr this->instance = std::shared_ptr( diff --git a/src/lsfg.cpp b/src/lsfg.cpp new file mode 100644 index 0000000..80a3589 --- /dev/null +++ b/src/lsfg.cpp @@ -0,0 +1,106 @@ +#include "lsfg.hpp" +#include "core/commandbuffer.hpp" + +#include + +using namespace LSFG; + +Generator::Generator(const Context& context) { + // TODO: temporal frames + + // create shader chains + this->downsampleChain = Shaderchains::Downsample(context.device, context.descPool, + this->fullFrame0, this->fullFrame1); + for (size_t i = 0; i < 7; i++) + this->alphaChains.at(i) = Shaderchains::Alpha(context.device, context.descPool, + this->downsampleChain.getOutImages().at(i), i == 0); + this->betaChain = Shaderchains::Beta(context.device, context.descPool, + this->alphaChains.at(0).getOutImages0(), + this->alphaChains.at(0).getOutImages1(), + this->alphaChains.at(0).getOutImages2()); + for (size_t i = 0; i < 7; i++) { + if (i < 4) { + this->gammaChains.at(i) = Shaderchains::Gamma(context.device, context.descPool, + this->alphaChains.at(6 - i).getOutImages1(), + this->alphaChains.at(6 - i).getOutImages0(), + this->betaChain.getOutImages().at(std::min(5UL, 6 - i)), + i == 0 ? std::nullopt + : std::optional{this->gammaChains.at(i - 1).getOutImage2()}, + i == 0 ? std::nullopt + : std::optional{this->gammaChains.at(i - 1).getOutImage1()}, + this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() + ); + } else { + // this->magicChains.at(i - 4) = Shaderchains::Magic(context.device, context.descPool, + // this->alphaChains.at(6 - i).getOutImages(), + // i == 4 ? this->gammaChains.at(i - 1).getOutImage2() + // : this->extractChains.at(i - 5).getOutImage(), + // i == 4 ? this->gammaChains.at(i - 1).getOutImage1() + // : this->zetaChains.at(i - 5).getOutImage(), + // i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()} + // ); + this->deltaChains.at(i - 4) = Shaderchains::Delta(context.device, context.descPool, + this->magicChains.at(i - 4).getOutImages1(), + i == 4 ? std::nullopt + : std::optional{this->deltaChains.at(i - 5).getOutImage()} + ); + this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(context.device, context.descPool, + this->magicChains.at(i - 4).getOutImages2(), + this->betaChain.getOutImages().at(6 - i), + i == 4 ? std::nullopt + : std::optional{this->epsilonChains.at(i - 5).getOutImage()} + ); + this->zetaChains.at(i - 4) = Shaderchains::Zeta(context.device, context.descPool, + this->magicChains.at(i - 4).getOutImages3(), + i == 4 ? this->gammaChains.at(i - 1).getOutImage1() + : this->zetaChains.at(i - 5).getOutImage(), + this->betaChain.getOutImages().at(6 - i) + ); + if (i >= 6) + continue; // no extract for i >= 6 + this->extractChains.at(i - 4) = Shaderchains::Extract(context.device, context.descPool, + this->zetaChains.at(i - 4).getOutImage(), + this->epsilonChains.at(i - 4).getOutImage(), + this->extractChains.at(i - 5).getOutImage().getExtent() + ); + } + } + this->mergeChain = Shaderchains::Merge(context.device, context.descPool, + this->fullFrame0, + this->fullFrame1, + this->zetaChains.at(2).getOutImage(), + this->epsilonChains.at(2).getOutImage(), + this->deltaChains.at(2).getOutImage() + ); +} + +void Generator::present(const Context& context) { + Core::CommandBuffer cmdBuffer(context.device, context.cmdPool); + cmdBuffer.begin(); + + this->downsampleChain.Dispatch(cmdBuffer, fc); + for (size_t i = 0; i < 7; i++) + this->alphaChains.at(6 - i).Dispatch(cmdBuffer, fc); + this->betaChain.Dispatch(cmdBuffer, fc); + for (size_t i = 0; i < 4; i++) + this->gammaChains.at(i).Dispatch(cmdBuffer); + for (size_t i = 0; i < 3; i++) { + this->magicChains.at(i).Dispatch(cmdBuffer); + this->deltaChains.at(i).Dispatch(cmdBuffer); + this->epsilonChains.at(i).Dispatch(cmdBuffer); + this->zetaChains.at(i).Dispatch(cmdBuffer); + if (i < 2) + this->extractChains.at(i).Dispatch(cmdBuffer); + } + this->mergeChain.Dispatch(cmdBuffer, fc); + + cmdBuffer.end(); + + // TODO: submit logic + fc++; +} + +vulkan_error::vulkan_error(VkResult result, const std::string& message) + : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} + +vulkan_error::~vulkan_error() noexcept = default; diff --git a/src/main.cpp b/src/main.cpp index 799f14a..5cbc601 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,7 +25,7 @@ #include #include -using namespace Vulkan; +using namespace LSFG; int main() { // attempt to load renderdoc @@ -43,12 +43,12 @@ int main() { } // initialize application - const Instance instance; - const Device device(instance); - const Core::DescriptorPool descriptorPool(device); - const Core::CommandPool commandPool(device); + // const Instance instance; + // const Device device(instance); + // const Core::DescriptorPool descriptorPool(device); + // const Core::CommandPool commandPool(device); - Globals::initializeGlobals(device); + // Globals::initializeGlobals(device); // create downsample shader chain Core::Image inputImage( @@ -58,156 +58,156 @@ int main() { ); Utils::uploadImage(device, commandPool, inputImage, "rsc/images/source.dds"); - Shaderchains::Downsample downsample(device, descriptorPool, inputImage); + // Shaderchains::Downsample downsample(device, descriptorPool, inputImage); // create alpha shader chains - std::vector alphas; - alphas.reserve(7); - for (size_t i = 0; i < 7; ++i) - alphas.emplace_back(device, descriptorPool, downsample.getOutImages().at(i)); + // std::vector alphas; + // alphas.reserve(7); + // for (size_t i = 0; i < 7; ++i) + // alphas.emplace_back(device, descriptorPool, downsample.getOutImages().at(i)); - // create beta shader chain - std::array betaTemporalImages; - auto betaInExtent = alphas.at(0).getOutImages().at(0).getExtent(); - for (size_t i = 0; i < 8; ++i) { - betaTemporalImages.at(i) = Core::Image( - device, betaInExtent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ); - Utils::uploadImage(device, commandPool, betaTemporalImages.at(i), - std::format("rsc/images/temporal_beta/{}.dds", i)); - } + // // create beta shader chain + // std::array betaTemporalImages; + // auto betaInExtent = alphas.at(0).getOutImages().at(0).getExtent(); + // for (size_t i = 0; i < 8; ++i) { + // betaTemporalImages.at(i) = Core::Image( + // device, betaInExtent, VK_FORMAT_R8G8B8A8_UNORM, + // VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | + // VK_IMAGE_USAGE_TRANSFER_DST_BIT, + // VK_IMAGE_ASPECT_COLOR_BIT + // ); + // Utils::uploadImage(device, commandPool, betaTemporalImages.at(i), + // std::format("rsc/images/temporal_beta/{}.dds", i)); + // } - Shaderchains::Beta beta(device, descriptorPool, - betaTemporalImages, - alphas.at(0).getOutImages() + // Shaderchains::Beta beta(device, descriptorPool, + // betaTemporalImages, + // alphas.at(0).getOutImages() ); // create gamma to zeta shader chains - std::vector gammas; - std::vector magics; - std::vector deltas; - std::vector epsilons; - std::vector zetas; - std::vector extracts; - std::array, 7> otherTemporalImages; - for (size_t i = 0; i < 7; i++) { - auto otherInExtent = alphas.at(6 - i).getOutImages().at(0).getExtent(); - for (size_t j = 0; j < 4; j++) { - otherTemporalImages.at(i).at(j) = Core::Image( - device, otherInExtent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ); - Utils::uploadImage(device, commandPool, otherTemporalImages.at(i).at(j), - std::format("rsc/images/temporal_other/{}.{}.dds", i, j)); - } - if (i < 4) { - auto gammaOutExtent = alphas.at(6 - i - 1).getOutImages().at(0).getExtent(); - gammas.emplace_back(device, descriptorPool, - otherTemporalImages.at(i), - alphas.at(6 - i).getOutImages(), - beta.getOutImages().at(std::min(static_cast(5), 6 - i)), // smallest twice - i == 0 ? std::nullopt : std::optional{gammas.at(i - 1).getOutImage2()}, - i == 0 ? std::nullopt : std::optional{gammas.at(i - 1).getOutImage1()}, - gammaOutExtent); - } else { - magics.emplace_back(device, descriptorPool, - otherTemporalImages.at(i), - alphas.at(6 - i).getOutImages(), - i == 4 ? gammas.at(i - 1).getOutImage2() : extracts.at(i - 5).getOutImage(), - i == 4 ? gammas.at(i - 1).getOutImage1() : zetas.at(i - 5).getOutImage(), - i == 4 ? std::nullopt : std::optional{epsilons.at(i - 5).getOutImage()} - ); - deltas.emplace_back(device, descriptorPool, - magics.at(i - 4).getOutImages1(), - i == 4 ? std::nullopt : std::optional{deltas.at(i - 5).getOutImage()} - ); - epsilons.emplace_back(device, descriptorPool, - magics.at(i - 4).getOutImages2(), - beta.getOutImages().at(6 - i), - i == 4 ? std::nullopt : std::optional{epsilons.at(i - 5).getOutImage()} - ); - zetas.emplace_back(device, descriptorPool, - magics.at(i - 4).getOutImages3(), - i == 4 ? gammas.at(i - 1).getOutImage1() : zetas.at(i - 5).getOutImage(), - beta.getOutImages().at(6 - i) - ); - if (i < 6) { - auto extractOutExtent = alphas.at(6 - i - 1).getOutImages().at(0).getExtent(); - extracts.emplace_back(device, descriptorPool, - zetas.at(i - 4).getOutImage(), - epsilons.at(i - 4).getOutImage(), - extractOutExtent); - } - } - } + // std::vector gammas; + // std::vector magics; + // std::vector deltas; + // std::vector epsilons; + // std::vector zetas; + // std::vector extracts; + // std::array, 7> otherTemporalImages; + // for (size_t i = 0; i < 7; i++) { + // auto otherInExtent = alphas.at(6 - i).getOutImages().at(0).getExtent(); + // for (size_t j = 0; j < 4; j++) { + // otherTemporalImages.at(i).at(j) = Core::Image( + // device, otherInExtent, VK_FORMAT_R8G8B8A8_UNORM, + // VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | + // VK_IMAGE_USAGE_TRANSFER_DST_BIT, + // VK_IMAGE_ASPECT_COLOR_BIT + // ); + // Utils::uploadImage(device, commandPool, otherTemporalImages.at(i).at(j), + // std::format("rsc/images/temporal_other/{}.{}.dds", i, j)); + // } + // if (i < 4) { + // auto gammaOutExtent = alphas.at(6 - i - 1).getOutImages().at(0).getExtent(); + // gammas.emplace_back(device, descriptorPool, + // otherTemporalImages.at(i), + // alphas.at(6 - i).getOutImages(), + // beta.getOutImages().at(std::min(static_cast(5), 6 - i)), // smallest twice + // i == 0 ? std::nullopt : std::optional{gammas.at(i - 1).getOutImage2()}, + // i == 0 ? std::nullopt : std::optional{gammas.at(i - 1).getOutImage1()}, + // gammaOutExtent); + // } else { + // magics.emplace_back(device, descriptorPool, + // otherTemporalImages.at(i), + // alphas.at(6 - i).getOutImages(), + // i == 4 ? gammas.at(i - 1).getOutImage2() : extracts.at(i - 5).getOutImage(), + // i == 4 ? gammas.at(i - 1).getOutImage1() : zetas.at(i - 5).getOutImage(), + // i == 4 ? std::nullopt : std::optional{epsilons.at(i - 5).getOutImage()} + // ); + // deltas.emplace_back(device, descriptorPool, + // magics.at(i - 4).getOutImages1(), + // i == 4 ? std::nullopt : std::optional{deltas.at(i - 5).getOutImage()} + // ); + // epsilons.emplace_back(device, descriptorPool, + // magics.at(i - 4).getOutImages2(), + // beta.getOutImages().at(6 - i), + // i == 4 ? std::nullopt : std::optional{epsilons.at(i - 5).getOutImage()} + // ); + // zetas.emplace_back(device, descriptorPool, + // magics.at(i - 4).getOutImages3(), + // i == 4 ? gammas.at(i - 1).getOutImage1() : zetas.at(i - 5).getOutImage(), + // beta.getOutImages().at(6 - i) + // ); + // if (i < 6) { + // auto extractOutExtent = alphas.at(6 - i - 1).getOutImages().at(0).getExtent(); + // extracts.emplace_back(device, descriptorPool, + // zetas.at(i - 4).getOutImage(), + // epsilons.at(i - 4).getOutImage(), + // extractOutExtent); + // } + // } + // } - // create merge shader chain - Core::Image inputImagePrev( - device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ); - Utils::uploadImage(device, commandPool, inputImagePrev, "rsc/images/source_prev.dds"); - Shaderchains::Merge merge(device, descriptorPool, - inputImagePrev, - inputImage, - zetas.at(2).getOutImage(), - epsilons.at(2).getOutImage(), - deltas.at(2).getOutImage() - ); + // // create merge shader chain + // Core::Image inputImagePrev( + // device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, + // VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + // VK_IMAGE_ASPECT_COLOR_BIT + // ); + // Utils::uploadImage(device, commandPool, inputImagePrev, "rsc/images/source_prev.dds"); + // Shaderchains::Merge merge(device, descriptorPool, + // inputImagePrev, + // inputImage, + // zetas.at(2).getOutImage(), + // epsilons.at(2).getOutImage(), + // deltas.at(2).getOutImage() + // ); - for (int i = 0; i < 1000; i++) { - const Core::Fence fence(device); + // for (int i = 0; i < 1000; i++) { + // const Core::Fence fence(device); - // start the rendering pipeline - if (rdoc) - rdoc->StartFrameCapture(nullptr, nullptr); + // // start the rendering pipeline + // if (rdoc) + // rdoc->StartFrameCapture(nullptr, nullptr); - Core::CommandBuffer commandBuffer(device, commandPool); - commandBuffer.begin(); + // Core::CommandBuffer commandBuffer(device, commandPool); + // commandBuffer.begin(); - downsample.Dispatch(commandBuffer); - for (size_t i = 0; i < 7; i++) - alphas.at(6 - i).Dispatch(commandBuffer); - beta.Dispatch(commandBuffer); - for (size_t i = 0; i < 4; i++) - gammas.at(i).Dispatch(commandBuffer); - for (size_t i = 0; i < 3; i++) { - magics.at(i).Dispatch(commandBuffer); - deltas.at(i).Dispatch(commandBuffer); - epsilons.at(i).Dispatch(commandBuffer); - zetas.at(i).Dispatch(commandBuffer); - if (i < 2) - extracts.at(i).Dispatch(commandBuffer); - } - merge.Dispatch(commandBuffer); + // downsample.Dispatch(commandBuffer); + // for (size_t i = 0; i < 7; i++) + // alphas.at(6 - i).Dispatch(commandBuffer); + // beta.Dispatch(commandBuffer); + // for (size_t i = 0; i < 4; i++) + // gammas.at(i).Dispatch(commandBuffer); + // for (size_t i = 0; i < 3; i++) { + // magics.at(i).Dispatch(commandBuffer); + // deltas.at(i).Dispatch(commandBuffer); + // epsilons.at(i).Dispatch(commandBuffer); + // zetas.at(i).Dispatch(commandBuffer); + // if (i < 2) + // extracts.at(i).Dispatch(commandBuffer); + // } + // merge.Dispatch(commandBuffer); - // finish the rendering pipeline - commandBuffer.end(); + // // finish the rendering pipeline + // commandBuffer.end(); - commandBuffer.submit(device.getComputeQueue(), fence); - if (!fence.wait(device)) { - Globals::uninitializeGlobals(); + // commandBuffer.submit(device.getComputeQueue(), fence); + // if (!fence.wait(device)) { + // Globals::uninitializeGlobals(); - std::cerr << "Application failed due to timeout" << '\n'; - return 1; - } + // std::cerr << "Application failed due to timeout" << '\n'; + // return 1; + // } - if (rdoc) - rdoc->EndFrameCapture(nullptr, nullptr); + // if (rdoc) + // rdoc->EndFrameCapture(nullptr, nullptr); - // sleep 8 ms - usleep(8000); - } + // // sleep 8 ms + // usleep(8000); + // } - usleep(1000 * 100); // give renderdoc time to capture + // usleep(1000 * 100); // give renderdoc time to capture - Globals::uninitializeGlobals(); + // Globals::uninitializeGlobals(); std::cerr << "Application finished" << '\n'; return 0; diff --git a/src/shaderchains/alpha.cpp b/src/shaderchains/alpha.cpp index 325dc2c..41d9f9d 100644 --- a/src/shaderchains/alpha.cpp +++ b/src/shaderchains/alpha.cpp @@ -1,7 +1,7 @@ #include "shaderchains/alpha.hpp" #include "utils.hpp" -using namespace Vulkan::Shaderchains; +using namespace LSFG::Shaderchains; Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, Core::Image inImg) diff --git a/src/shaderchains/beta.cpp b/src/shaderchains/beta.cpp index 33c6b88..bf96ae8 100644 --- a/src/shaderchains/beta.cpp +++ b/src/shaderchains/beta.cpp @@ -1,7 +1,7 @@ #include "shaderchains/beta.hpp" #include "utils.hpp" -using namespace Vulkan::Shaderchains; +using namespace LSFG::Shaderchains; Beta::Beta(const Device& device, const Core::DescriptorPool& pool, std::array temporalImgs, diff --git a/src/shaderchains/delta.cpp b/src/shaderchains/delta.cpp index 083968e..ca25a39 100644 --- a/src/shaderchains/delta.cpp +++ b/src/shaderchains/delta.cpp @@ -1,7 +1,7 @@ #include "shaderchains/delta.hpp" #include "utils.hpp" -using namespace Vulkan::Shaderchains; +using namespace LSFG::Shaderchains; Delta::Delta(const Device& device, const Core::DescriptorPool& pool, std::array inImgs, diff --git a/src/shaderchains/downsample.cpp b/src/shaderchains/downsample.cpp index 2d880ba..790e210 100644 --- a/src/shaderchains/downsample.cpp +++ b/src/shaderchains/downsample.cpp @@ -1,7 +1,7 @@ #include "shaderchains/downsample.hpp" #include "utils.hpp" -using namespace Vulkan::Shaderchains; +using namespace LSFG::Shaderchains; Downsample::Downsample(const Device& device, const Core::DescriptorPool& pool, Core::Image inImg) diff --git a/src/shaderchains/epsilon.cpp b/src/shaderchains/epsilon.cpp index be905cf..f199d64 100644 --- a/src/shaderchains/epsilon.cpp +++ b/src/shaderchains/epsilon.cpp @@ -1,7 +1,7 @@ #include "shaderchains/epsilon.hpp" #include "utils.hpp" -using namespace Vulkan::Shaderchains; +using namespace LSFG::Shaderchains; Epsilon::Epsilon(const Device& device, const Core::DescriptorPool& pool, std::array inImgs1, diff --git a/src/shaderchains/extract.cpp b/src/shaderchains/extract.cpp index c3a391a..4f66938 100644 --- a/src/shaderchains/extract.cpp +++ b/src/shaderchains/extract.cpp @@ -1,7 +1,7 @@ #include "shaderchains/extract.hpp" #include "utils.hpp" -using namespace Vulkan::Shaderchains; +using namespace LSFG::Shaderchains; Extract::Extract(const Device& device, const Core::DescriptorPool& pool, Core::Image inImg1, diff --git a/src/shaderchains/gamma.cpp b/src/shaderchains/gamma.cpp index 7001a9c..42e20f9 100644 --- a/src/shaderchains/gamma.cpp +++ b/src/shaderchains/gamma.cpp @@ -1,7 +1,7 @@ #include "shaderchains/gamma.hpp" #include "utils.hpp" -using namespace Vulkan::Shaderchains; +using namespace LSFG::Shaderchains; Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, std::array temporalImgs, diff --git a/src/shaderchains/magic.cpp b/src/shaderchains/magic.cpp index 41d1b49..8d8c9b4 100644 --- a/src/shaderchains/magic.cpp +++ b/src/shaderchains/magic.cpp @@ -1,7 +1,7 @@ #include "shaderchains/magic.hpp" #include "utils.hpp" -using namespace Vulkan::Shaderchains; +using namespace LSFG::Shaderchains; Magic::Magic(const Device& device, const Core::DescriptorPool& pool, std::array temporalImgs, diff --git a/src/shaderchains/merge.cpp b/src/shaderchains/merge.cpp index 6dee8d9..63f74b7 100644 --- a/src/shaderchains/merge.cpp +++ b/src/shaderchains/merge.cpp @@ -1,8 +1,7 @@ #include "shaderchains/merge.hpp" #include "utils.hpp" -#include -using namespace Vulkan::Shaderchains; +using namespace LSFG::Shaderchains; Merge::Merge(const Device& device, const Core::DescriptorPool& pool, Core::Image inImg1, diff --git a/src/shaderchains/zeta.cpp b/src/shaderchains/zeta.cpp index 6700bf9..cb2bdd6 100644 --- a/src/shaderchains/zeta.cpp +++ b/src/shaderchains/zeta.cpp @@ -1,7 +1,7 @@ #include "shaderchains/zeta.hpp" #include "utils.hpp" -using namespace Vulkan::Shaderchains; +using namespace LSFG::Shaderchains; Zeta::Zeta(const Device& device, const Core::DescriptorPool& pool, std::array inImgs1, diff --git a/src/utils.cpp b/src/utils.cpp index 0b07708..1e13fbc 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,12 +1,11 @@ #include "utils.hpp" #include "core/buffer.hpp" +#include "lsfg.hpp" -#include #include -#include -using namespace Vulkan; -using namespace Vulkan::Utils; +using namespace LSFG; +using namespace LSFG::Utils; BarrierBuilder& BarrierBuilder::addR2W(Core::Image& image) { this->barriers.emplace_back(VkImageMemoryBarrier2 { @@ -128,7 +127,7 @@ void Utils::uploadImage(const Device& device, const Core::CommandPool& commandPo // wait for the upload to complete if (!fence.wait(device)) - throw ls::vulkan_error(VK_TIMEOUT, "Upload operation timed out"); + throw LSFG::vulkan_error(VK_TIMEOUT, "Upload operation timed out"); } void Utils::clearImage(const Device& device, Core::Image& image, bool white) { @@ -174,7 +173,7 @@ void Utils::clearImage(const Device& device, Core::Image& image, bool white) { cmdBuf.submit(device.getComputeQueue(), fence); if (!fence.wait(device)) - throw ls::vulkan_error(VK_TIMEOUT, "Failed to wait for clearing fence."); + throw LSFG::vulkan_error(VK_TIMEOUT, "Failed to wait for clearing fence."); } Core::Sampler Globals::samplerClampBorder; @@ -203,8 +202,3 @@ void Globals::uninitializeGlobals() noexcept { // uninitialize global constant buffer fgBuffer = Globals::FgBuffer(); } - -ls::vulkan_error::vulkan_error(VkResult result, const std::string& message) - : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} - -ls::vulkan_error::~vulkan_error() noexcept = default; From bd759399d4e25397dc169ddf278de96cd9bd3097 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 03:19:01 +0200 Subject: [PATCH 033/253] get application to compile temporarily --- src/lsfg.cpp | 156 +++++++++++++++++++++++++-------------------------- src/main.cpp | 14 ++--- 2 files changed, 85 insertions(+), 85 deletions(-) diff --git a/src/lsfg.cpp b/src/lsfg.cpp index 80a3589..98b3a7f 100644 --- a/src/lsfg.cpp +++ b/src/lsfg.cpp @@ -9,90 +9,90 @@ Generator::Generator(const Context& context) { // TODO: temporal frames // create shader chains - this->downsampleChain = Shaderchains::Downsample(context.device, context.descPool, - this->fullFrame0, this->fullFrame1); - for (size_t i = 0; i < 7; i++) - this->alphaChains.at(i) = Shaderchains::Alpha(context.device, context.descPool, - this->downsampleChain.getOutImages().at(i), i == 0); - this->betaChain = Shaderchains::Beta(context.device, context.descPool, - this->alphaChains.at(0).getOutImages0(), - this->alphaChains.at(0).getOutImages1(), - this->alphaChains.at(0).getOutImages2()); - for (size_t i = 0; i < 7; i++) { - if (i < 4) { - this->gammaChains.at(i) = Shaderchains::Gamma(context.device, context.descPool, - this->alphaChains.at(6 - i).getOutImages1(), - this->alphaChains.at(6 - i).getOutImages0(), - this->betaChain.getOutImages().at(std::min(5UL, 6 - i)), - i == 0 ? std::nullopt - : std::optional{this->gammaChains.at(i - 1).getOutImage2()}, - i == 0 ? std::nullopt - : std::optional{this->gammaChains.at(i - 1).getOutImage1()}, - this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() - ); - } else { - // this->magicChains.at(i - 4) = Shaderchains::Magic(context.device, context.descPool, - // this->alphaChains.at(6 - i).getOutImages(), - // i == 4 ? this->gammaChains.at(i - 1).getOutImage2() - // : this->extractChains.at(i - 5).getOutImage(), - // i == 4 ? this->gammaChains.at(i - 1).getOutImage1() - // : this->zetaChains.at(i - 5).getOutImage(), - // i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()} - // ); - this->deltaChains.at(i - 4) = Shaderchains::Delta(context.device, context.descPool, - this->magicChains.at(i - 4).getOutImages1(), - i == 4 ? std::nullopt - : std::optional{this->deltaChains.at(i - 5).getOutImage()} - ); - this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(context.device, context.descPool, - this->magicChains.at(i - 4).getOutImages2(), - this->betaChain.getOutImages().at(6 - i), - i == 4 ? std::nullopt - : std::optional{this->epsilonChains.at(i - 5).getOutImage()} - ); - this->zetaChains.at(i - 4) = Shaderchains::Zeta(context.device, context.descPool, - this->magicChains.at(i - 4).getOutImages3(), - i == 4 ? this->gammaChains.at(i - 1).getOutImage1() - : this->zetaChains.at(i - 5).getOutImage(), - this->betaChain.getOutImages().at(6 - i) - ); - if (i >= 6) - continue; // no extract for i >= 6 - this->extractChains.at(i - 4) = Shaderchains::Extract(context.device, context.descPool, - this->zetaChains.at(i - 4).getOutImage(), - this->epsilonChains.at(i - 4).getOutImage(), - this->extractChains.at(i - 5).getOutImage().getExtent() - ); - } - } - this->mergeChain = Shaderchains::Merge(context.device, context.descPool, - this->fullFrame0, - this->fullFrame1, - this->zetaChains.at(2).getOutImage(), - this->epsilonChains.at(2).getOutImage(), - this->deltaChains.at(2).getOutImage() - ); + // this->downsampleChain = Shaderchains::Downsample(context.device, context.descPool, + // this->fullFrame0, this->fullFrame1); + // for (size_t i = 0; i < 7; i++) + // this->alphaChains.at(i) = Shaderchains::Alpha(context.device, context.descPool, + // this->downsampleChain.getOutImages().at(i), i == 0); + // this->betaChain = Shaderchains::Beta(context.device, context.descPool, + // this->alphaChains.at(0).getOutImages0(), + // this->alphaChains.at(0).getOutImages1(), + // this->alphaChains.at(0).getOutImages2()); + // for (size_t i = 0; i < 7; i++) { + // if (i < 4) { + // this->gammaChains.at(i) = Shaderchains::Gamma(context.device, context.descPool, + // this->alphaChains.at(6 - i).getOutImages1(), + // this->alphaChains.at(6 - i).getOutImages0(), + // this->betaChain.getOutImages().at(std::min(5UL, 6 - i)), + // i == 0 ? std::nullopt + // : std::optional{this->gammaChains.at(i - 1).getOutImage2()}, + // i == 0 ? std::nullopt + // : std::optional{this->gammaChains.at(i - 1).getOutImage1()}, + // this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() + // ); + // } else { + // // this->magicChains.at(i - 4) = Shaderchains::Magic(context.device, context.descPool, + // // this->alphaChains.at(6 - i).getOutImages(), + // // i == 4 ? this->gammaChains.at(i - 1).getOutImage2() + // // : this->extractChains.at(i - 5).getOutImage(), + // // i == 4 ? this->gammaChains.at(i - 1).getOutImage1() + // // : this->zetaChains.at(i - 5).getOutImage(), + // // i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()} + // // ); + // this->deltaChains.at(i - 4) = Shaderchains::Delta(context.device, context.descPool, + // this->magicChains.at(i - 4).getOutImages1(), + // i == 4 ? std::nullopt + // : std::optional{this->deltaChains.at(i - 5).getOutImage()} + // ); + // this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(context.device, context.descPool, + // this->magicChains.at(i - 4).getOutImages2(), + // this->betaChain.getOutImages().at(6 - i), + // i == 4 ? std::nullopt + // : std::optional{this->epsilonChains.at(i - 5).getOutImage()} + // ); + // this->zetaChains.at(i - 4) = Shaderchains::Zeta(context.device, context.descPool, + // this->magicChains.at(i - 4).getOutImages3(), + // i == 4 ? this->gammaChains.at(i - 1).getOutImage1() + // : this->zetaChains.at(i - 5).getOutImage(), + // this->betaChain.getOutImages().at(6 - i) + // ); + // if (i >= 6) + // continue; // no extract for i >= 6 + // this->extractChains.at(i - 4) = Shaderchains::Extract(context.device, context.descPool, + // this->zetaChains.at(i - 4).getOutImage(), + // this->epsilonChains.at(i - 4).getOutImage(), + // this->extractChains.at(i - 5).getOutImage().getExtent() + // ); + // } + // } + // this->mergeChain = Shaderchains::Merge(context.device, context.descPool, + // this->fullFrame0, + // this->fullFrame1, + // this->zetaChains.at(2).getOutImage(), + // this->epsilonChains.at(2).getOutImage(), + // this->deltaChains.at(2).getOutImage() + // ); } void Generator::present(const Context& context) { Core::CommandBuffer cmdBuffer(context.device, context.cmdPool); cmdBuffer.begin(); - this->downsampleChain.Dispatch(cmdBuffer, fc); - for (size_t i = 0; i < 7; i++) - this->alphaChains.at(6 - i).Dispatch(cmdBuffer, fc); - this->betaChain.Dispatch(cmdBuffer, fc); - for (size_t i = 0; i < 4; i++) - this->gammaChains.at(i).Dispatch(cmdBuffer); - for (size_t i = 0; i < 3; i++) { - this->magicChains.at(i).Dispatch(cmdBuffer); - this->deltaChains.at(i).Dispatch(cmdBuffer); - this->epsilonChains.at(i).Dispatch(cmdBuffer); - this->zetaChains.at(i).Dispatch(cmdBuffer); - if (i < 2) - this->extractChains.at(i).Dispatch(cmdBuffer); - } - this->mergeChain.Dispatch(cmdBuffer, fc); + // this->downsampleChain.Dispatch(cmdBuffer, fc); + // for (size_t i = 0; i < 7; i++) + // this->alphaChains.at(6 - i).Dispatch(cmdBuffer, fc); + // this->betaChain.Dispatch(cmdBuffer, fc); + // for (size_t i = 0; i < 4; i++) + // this->gammaChains.at(i).Dispatch(cmdBuffer); + // for (size_t i = 0; i < 3; i++) { + // this->magicChains.at(i).Dispatch(cmdBuffer); + // this->deltaChains.at(i).Dispatch(cmdBuffer); + // this->epsilonChains.at(i).Dispatch(cmdBuffer); + // this->zetaChains.at(i).Dispatch(cmdBuffer); + // if (i < 2) + // this->extractChains.at(i).Dispatch(cmdBuffer); + // } + // this->mergeChain.Dispatch(cmdBuffer, fc); cmdBuffer.end(); diff --git a/src/main.cpp b/src/main.cpp index 5cbc601..de8e249 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -51,12 +51,12 @@ int main() { // Globals::initializeGlobals(device); // create downsample shader chain - Core::Image inputImage( - device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ); - Utils::uploadImage(device, commandPool, inputImage, "rsc/images/source.dds"); + // Core::Image inputImage( + // device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, + // VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + // VK_IMAGE_ASPECT_COLOR_BIT + // ); + // Utils::uploadImage(device, commandPool, inputImage, "rsc/images/source.dds"); // Shaderchains::Downsample downsample(device, descriptorPool, inputImage); @@ -83,7 +83,7 @@ int main() { // Shaderchains::Beta beta(device, descriptorPool, // betaTemporalImages, // alphas.at(0).getOutImages() - ); + // ); // create gamma to zeta shader chains // std::vector gammas; From 1a9925129becb632e1ff3e1227c10b5120f49197 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 03:38:11 +0200 Subject: [PATCH 034/253] temporal logic for downsample & merge --- include/lsfg.hpp | 2 +- include/shaderchains/downsample.hpp | 12 +++++----- include/shaderchains/merge.hpp | 11 ++++++---- src/lsfg.cpp | 22 +++++++++---------- src/shaderchains/downsample.cpp | 34 +++++++++++++++++------------ src/shaderchains/merge.cpp | 31 ++++++++++++++------------ 6 files changed, 63 insertions(+), 49 deletions(-) diff --git a/include/lsfg.hpp b/include/lsfg.hpp index 1a459e7..ea3459b 100644 --- a/include/lsfg.hpp +++ b/include/lsfg.hpp @@ -84,7 +84,7 @@ namespace LSFG { Generator& operator=(Generator&&) = default; ~Generator() = default; private: - Core::Image fullFrame0, fullFrame1; // next/prev for fc % 2 == 0 + Core::Image inImg_0, inImg_1; // inImg_0 is next (inImg_1 prev) when fc % 2 == 0 uint64_t fc{0}; Shaderchains::Downsample downsampleChain; // FIXME: get rid of default constructors (+ core) diff --git a/include/shaderchains/downsample.hpp b/include/shaderchains/downsample.hpp index f799480..fd7e280 100644 --- a/include/shaderchains/downsample.hpp +++ b/include/shaderchains/downsample.hpp @@ -28,21 +28,23 @@ namespace LSFG::Shaderchains { /// /// @param device The Vulkan device to create the resources on. /// @param pool The descriptor pool to allocate in. - /// @param inImg The input image to downsample. + /// @param inImg_0 The next full image to downsample (when fc % 2 == 0) + /// @param inImg_1 The next full image to downsample (when fc % 2 == 1) /// /// @throws LSFG::vulkan_error if resource creation fails. /// Downsample(const Device& device, const Core::DescriptorPool& pool, - Core::Image inImg); + Core::Image inImg_0, Core::Image inImg_1); /// /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. + /// @param fc The frame count, used to select the input image. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf); + void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); /// Get the output images. [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } @@ -56,10 +58,10 @@ namespace LSFG::Shaderchains { private: Core::ShaderModule shaderModule; Core::Pipeline pipeline; - Core::DescriptorSet descriptorSet; + std::array descriptorSets; // one for each input image Core::Buffer buffer; - Core::Image inImg; + Core::Image inImg_0, inImg_1; std::array outImgs; }; diff --git a/include/shaderchains/merge.hpp b/include/shaderchains/merge.hpp index 7ad12da..679522d 100644 --- a/include/shaderchains/merge.hpp +++ b/include/shaderchains/merge.hpp @@ -10,6 +10,8 @@ #include "core/shadermodule.hpp" #include "device.hpp" +#include + namespace LSFG::Shaderchains { /// @@ -27,8 +29,8 @@ namespace LSFG::Shaderchains { /// /// @param device The Vulkan device to create the resources on. /// @param pool The descriptor pool to use for descriptor sets. - /// @param inImg1 The first frame texture - /// @param inImg2 The second frame texture + /// @param inImg1 The prev full image when fc % 2 == 0 + /// @param inImg2 The next full image when fc % 2 == 0 /// @param inImg3 The first related input texture /// @param inImg4 The second related input texture /// @param inImg5 The third related input texture @@ -46,10 +48,11 @@ namespace LSFG::Shaderchains { /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. + /// @param fc The frame count, used to select the input images. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf); + void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); /// Get the output image [[nodiscard]] const auto& getOutImage() const { return this->outImg; } @@ -63,7 +66,7 @@ namespace LSFG::Shaderchains { private: Core::ShaderModule shaderModule; Core::Pipeline pipeline; - Core::DescriptorSet descriptorSet; + std::array descriptorSets; // one for each input combination Core::Buffer buffer; Core::Image inImg1; diff --git a/src/lsfg.cpp b/src/lsfg.cpp index 98b3a7f..130d95c 100644 --- a/src/lsfg.cpp +++ b/src/lsfg.cpp @@ -9,8 +9,8 @@ Generator::Generator(const Context& context) { // TODO: temporal frames // create shader chains - // this->downsampleChain = Shaderchains::Downsample(context.device, context.descPool, - // this->fullFrame0, this->fullFrame1); + this->downsampleChain = Shaderchains::Downsample(context.device, context.descPool, + this->inImg_0, this->inImg_1); // for (size_t i = 0; i < 7; i++) // this->alphaChains.at(i) = Shaderchains::Alpha(context.device, context.descPool, // this->downsampleChain.getOutImages().at(i), i == 0); @@ -65,20 +65,20 @@ Generator::Generator(const Context& context) { // ); // } // } - // this->mergeChain = Shaderchains::Merge(context.device, context.descPool, - // this->fullFrame0, - // this->fullFrame1, - // this->zetaChains.at(2).getOutImage(), - // this->epsilonChains.at(2).getOutImage(), - // this->deltaChains.at(2).getOutImage() - // ); + this->mergeChain = Shaderchains::Merge(context.device, context.descPool, + this->inImg_0, + this->inImg_1, + this->zetaChains.at(2).getOutImage(), + this->epsilonChains.at(2).getOutImage(), + this->deltaChains.at(2).getOutImage() + ); } void Generator::present(const Context& context) { Core::CommandBuffer cmdBuffer(context.device, context.cmdPool); cmdBuffer.begin(); - // this->downsampleChain.Dispatch(cmdBuffer, fc); + this->downsampleChain.Dispatch(cmdBuffer, fc); // for (size_t i = 0; i < 7; i++) // this->alphaChains.at(6 - i).Dispatch(cmdBuffer, fc); // this->betaChain.Dispatch(cmdBuffer, fc); @@ -92,7 +92,7 @@ void Generator::present(const Context& context) { // if (i < 2) // this->extractChains.at(i).Dispatch(cmdBuffer); // } - // this->mergeChain.Dispatch(cmdBuffer, fc); + this->mergeChain.Dispatch(cmdBuffer, fc); cmdBuffer.end(); diff --git a/src/shaderchains/downsample.cpp b/src/shaderchains/downsample.cpp index 790e210..c60e8bc 100644 --- a/src/shaderchains/downsample.cpp +++ b/src/shaderchains/downsample.cpp @@ -4,18 +4,20 @@ using namespace LSFG::Shaderchains; Downsample::Downsample(const Device& device, const Core::DescriptorPool& pool, - Core::Image inImg) - : inImg(std::move(inImg)) { + Core::Image inImg_0, Core::Image inImg_1) + : inImg_0(std::move(inImg_0)), + inImg_1(std::move(inImg_1)) { this->shaderModule = Core::ShaderModule(device, "rsc/shaders/downsample.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); this->pipeline = Core::Pipeline(device, this->shaderModule); - this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); + for (size_t i = 0; i < 2; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModule); this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - auto extent = this->inImg.getExtent(); + auto extent = this->inImg_0.getExtent(); for (size_t i = 0; i < 7; i++) this->outImgs.at(i) = Core::Image(device, { extent.width >> i, extent.height >> i }, @@ -23,27 +25,31 @@ Downsample::Downsample(const Device& device, const Core::DescriptorPool& pool, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - this->descriptorSet.update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t fc = 0; fc < 2; fc++) { + auto& inImg = (fc % 2 == 0) ? this->inImg_0 : this->inImg_1; + this->descriptorSets.at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + } } -void Downsample::Dispatch(const Core::CommandBuffer& buf) { - auto extent = this->inImg.getExtent(); +void Downsample::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { + auto extent = this->inImg_0.getExtent(); // first pass const uint32_t threadsX = (extent.width + 63) >> 6; const uint32_t threadsY = (extent.height + 63) >> 6; + auto& inImg = (fc % 2 == 0) ? this->inImg_0 : this->inImg_1; Utils::BarrierBuilder(buf) - .addW2R(this->inImg) + .addW2R(inImg) .addR2W(this->outImgs) .build(); this->pipeline.bind(buf); - this->descriptorSet.bind(buf, this->pipeline); + this->descriptorSets.at(fc % 2).bind(buf, this->pipeline); buf.dispatch(threadsX, threadsY, 1); } diff --git a/src/shaderchains/merge.cpp b/src/shaderchains/merge.cpp index 63f74b7..82f09a7 100644 --- a/src/shaderchains/merge.cpp +++ b/src/shaderchains/merge.cpp @@ -20,7 +20,8 @@ Merge::Merge(const Device& device, const Core::DescriptorPool& pool, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); this->pipeline = Core::Pipeline(device, this->shaderModule); - this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); + for (size_t i = 0; i < 2; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModule); this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); auto extent = this->inImg1.getExtent(); @@ -31,20 +32,22 @@ Merge::Merge(const Device& device, const Core::DescriptorPool& pool, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - this->descriptorSet.update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 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->outImg) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t fc = 0; fc < 2; fc++) { + this->descriptorSets.at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg1 : this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg2 : this->inImg1) + .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->outImg) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + } } -void Merge::Dispatch(const Core::CommandBuffer& buf) { +void Merge::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { auto extent = this->inImg1.getExtent(); // first pass @@ -61,6 +64,6 @@ void Merge::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipeline.bind(buf); - this->descriptorSet.bind(buf, this->pipeline); + this->descriptorSets.at(fc).bind(buf, this->pipeline); buf.dispatch(threadsX, threadsY, 1); } From bf3c30575e16263ac676807d32fe1cc572e308ed Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 03:58:24 +0200 Subject: [PATCH 035/253] temporal logic for alpha & beta --- include/shaderchains/alpha.hpp | 24 ++++++++---- include/shaderchains/beta.hpp | 21 +++++++---- src/lsfg.cpp | 20 +++++----- src/shaderchains/alpha.cpp | 40 +++++++++++++++----- src/shaderchains/beta.cpp | 68 ++++++++++++++++++++++------------ 5 files changed, 115 insertions(+), 58 deletions(-) diff --git a/include/shaderchains/alpha.hpp b/include/shaderchains/alpha.hpp index 5f8dec0..2f18daf 100644 --- a/include/shaderchains/alpha.hpp +++ b/include/shaderchains/alpha.hpp @@ -38,13 +38,18 @@ namespace LSFG::Shaderchains { /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. + /// @param fc The frame count, used to determine which output images to write to. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf); + void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); - /// Get the output images. - [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + /// Get the output images written to when fc % 2 == 0 + [[nodiscard]] const auto& getOutImages0() const { return this->outImgs_0; } + /// Get the output images written to when fc % 2 == 1 + [[nodiscard]] const auto& getOutImages1() const { return this->outImgs_1; } + /// Get the output images written to when fc % 2 == 2 + [[nodiscard]] const auto& getOutImages2() const { return this->outImgs_2; } /// Trivially copyable, moveable and destructible Alpha(const Alpha&) noexcept = default; @@ -55,15 +60,18 @@ namespace LSFG::Shaderchains { private: std::array shaderModules; std::array pipelines; - std::array descriptorSets; + std::array descriptorSets; // last shader is special + std::array specialDescriptorSets; Core::Image inImg; - std::array tempImgs1; // half-size - std::array tempImgs2; // half-size - std::array tempImgs3; // quarter-size + std::array tempImgs1; + std::array tempImgs2; + std::array tempImgs3; - std::array outImgs; // quarter-size + std::array outImgs_0; + std::array outImgs_1; + std::array outImgs_2; }; } diff --git a/include/shaderchains/beta.hpp b/include/shaderchains/beta.hpp index 3aa37e8..f0de6d0 100644 --- a/include/shaderchains/beta.hpp +++ b/include/shaderchains/beta.hpp @@ -29,23 +29,26 @@ namespace LSFG::Shaderchains { /// /// @param device The Vulkan device to create the resources on. /// @param pool The descriptor pool to allocate in. - /// @param temporalImgs The temporal images to use for processing. - /// @param inImgs The input images to process + /// @param inImgs_0 The next input images to process (when fc % 2 == 0) + /// @param inImgs_1 The prev input images to process (when fc % 2 == 0) + /// @param inImgs_2 The prev prev input images to process (when fc % 2 == 0) /// /// @throws LSFG::vulkan_error if resource creation fails. /// Beta(const Device& device, const Core::DescriptorPool& pool, - std::array temporalImgs, - std::array inImgs); + std::array inImgs_0, + std::array inImgs_1, + std::array inImgs_2); /// /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. + /// @param fc The frame count, used to select the input images. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf); + void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); /// Get the output images. [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } @@ -59,11 +62,13 @@ namespace LSFG::Shaderchains { private: std::array shaderModules; std::array pipelines; - std::array descriptorSets; + std::array descriptorSets; // first shader has special logic + std::array specialDescriptorSets; Core::Buffer buffer; - std::array temporalImgs; - std::array inImgs; + std::array inImgs_0; + std::array inImgs_1; + std::array inImgs_2; std::array tempImgs1; std::array tempImgs2; diff --git a/src/lsfg.cpp b/src/lsfg.cpp index 130d95c..5b5ec1b 100644 --- a/src/lsfg.cpp +++ b/src/lsfg.cpp @@ -11,13 +11,13 @@ Generator::Generator(const Context& context) { // create shader chains this->downsampleChain = Shaderchains::Downsample(context.device, context.descPool, this->inImg_0, this->inImg_1); - // for (size_t i = 0; i < 7; i++) - // this->alphaChains.at(i) = Shaderchains::Alpha(context.device, context.descPool, - // this->downsampleChain.getOutImages().at(i), i == 0); - // this->betaChain = Shaderchains::Beta(context.device, context.descPool, - // this->alphaChains.at(0).getOutImages0(), - // this->alphaChains.at(0).getOutImages1(), - // this->alphaChains.at(0).getOutImages2()); + for (size_t i = 0; i < 7; i++) + this->alphaChains.at(i) = Shaderchains::Alpha(context.device, context.descPool, + this->downsampleChain.getOutImages().at(i)); + this->betaChain = Shaderchains::Beta(context.device, context.descPool, + this->alphaChains.at(0).getOutImages0(), + this->alphaChains.at(0).getOutImages2(), + this->alphaChains.at(0).getOutImages1()); // for (size_t i = 0; i < 7; i++) { // if (i < 4) { // this->gammaChains.at(i) = Shaderchains::Gamma(context.device, context.descPool, @@ -79,9 +79,9 @@ void Generator::present(const Context& context) { cmdBuffer.begin(); this->downsampleChain.Dispatch(cmdBuffer, fc); - // for (size_t i = 0; i < 7; i++) - // this->alphaChains.at(6 - i).Dispatch(cmdBuffer, fc); - // this->betaChain.Dispatch(cmdBuffer, fc); + for (size_t i = 0; i < 7; i++) + this->alphaChains.at(6 - i).Dispatch(cmdBuffer, fc); + this->betaChain.Dispatch(cmdBuffer, fc); // for (size_t i = 0; i < 4; i++) // this->gammaChains.at(i).Dispatch(cmdBuffer); // for (size_t i = 0; i < 3; i++) { diff --git a/src/shaderchains/alpha.cpp b/src/shaderchains/alpha.cpp index 41d9f9d..8f18f90 100644 --- a/src/shaderchains/alpha.cpp +++ b/src/shaderchains/alpha.cpp @@ -27,9 +27,13 @@ Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, for (size_t i = 0; i < 4; i++) { this->pipelines.at(i) = Core::Pipeline(device, this->shaderModules.at(i)); + if (i == 3) continue; // last shader is special this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); } + for (size_t i = 0; i < 3; i++) + this->specialDescriptorSets.at(i) = Core::DescriptorSet(device, pool, + this->shaderModules.at(3)); const auto extent = this->inImg.getExtent(); @@ -60,7 +64,17 @@ Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - this->outImgs.at(i) = Core::Image(device, + this->outImgs_0.at(i) = Core::Image(device, + quarterExtent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + this->outImgs_1.at(i) = Core::Image(device, + quarterExtent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); + this->outImgs_2.at(i) = Core::Image(device, quarterExtent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, @@ -82,14 +96,19 @@ Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs3) .build(); - this->descriptorSets.at(3).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) - .build(); + for (size_t fc = 0; fc < 3; fc++) { + auto& outImgs = this->outImgs_0; + if (fc == 1) outImgs = this->outImgs_1; + else if (fc == 2) outImgs = this->outImgs_2; + this->specialDescriptorSets.at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outImgs) + .build(); + } } -void Alpha::Dispatch(const Core::CommandBuffer& buf) { +void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { const auto halfExtent = this->tempImgs1.at(0).getExtent(); const auto quarterExtent = this->tempImgs3.at(0).getExtent(); @@ -130,12 +149,15 @@ void Alpha::Dispatch(const Core::CommandBuffer& buf) { buf.dispatch(threadsX, threadsY, 1); // fourth pass + auto& outImgs = this->outImgs_0; + if ((fc % 3) == 1) outImgs = this->outImgs_1; + else if ((fc % 3) == 2) outImgs = this->outImgs_2; Utils::BarrierBuilder(buf) .addW2R(this->tempImgs3) - .addR2W(this->outImgs) + .addR2W(outImgs) .build(); this->pipelines.at(3).bind(buf); - this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); + this->specialDescriptorSets.at(fc % 3).bind(buf, this->pipelines.at(3)); buf.dispatch(threadsX, threadsY, 1); } diff --git a/src/shaderchains/beta.cpp b/src/shaderchains/beta.cpp index bf96ae8..dbbfab6 100644 --- a/src/shaderchains/beta.cpp +++ b/src/shaderchains/beta.cpp @@ -4,10 +4,12 @@ using namespace LSFG::Shaderchains; Beta::Beta(const Device& device, const Core::DescriptorPool& pool, - std::array temporalImgs, - std::array inImgs) - : temporalImgs(std::move(temporalImgs)), - inImgs(std::move(inImgs)) { + std::array inImgs_0, + std::array inImgs_1, + std::array inImgs_2) + : inImgs_0(std::move(inImgs_0)), + inImgs_1(std::move(inImgs_1)), + inImgs_2(std::move(inImgs_2)) { this->shaderModules = {{ Core::ShaderModule(device, "rsc/shaders/beta/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, @@ -34,12 +36,16 @@ Beta::Beta(const Device& device, const Core::DescriptorPool& pool, for (size_t i = 0; i < 5; i++) { this->pipelines.at(i) = Core::Pipeline(device, this->shaderModules.at(i)); - this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, + if (i == 0) continue; // first shader has special logic + this->descriptorSets.at(i+1) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); } + for (size_t i = 0; i < 3; i++) + this->specialDescriptorSets.at(i) = Core::DescriptorSet(device, pool, + this->shaderModules.at(4)); this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - const auto extent = this->temporalImgs.at(0).getExtent(); + const auto extent = this->inImgs_0.at(0).getExtent(); for (size_t i = 0; i < 2; i++) { this->tempImgs1.at(i) = Core::Image(device, @@ -62,28 +68,43 @@ Beta::Beta(const Device& device, const Core::DescriptorPool& pool, VK_IMAGE_ASPECT_COLOR_BIT); } + for (size_t fc = 0; fc < 3; fc++) { + auto& nextImgs = this->inImgs_0; + auto& prevImgs = this->inImgs_2; + auto& pprevImgs = this->inImgs_1; + if (fc == 1) { + nextImgs = this->inImgs_1; + prevImgs = this->inImgs_0; + pprevImgs = this->inImgs_2; + } else if (fc == 2) { + nextImgs = this->inImgs_2; + prevImgs = this->inImgs_1; + pprevImgs = this->inImgs_0; + } + this->specialDescriptorSets.at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, pprevImgs) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, prevImgs) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, nextImgs) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + } this->descriptorSets.at(0).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->temporalImgs) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - this->descriptorSets.at(1).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) .build(); - this->descriptorSets.at(2).update(device) + this->descriptorSets.at(1).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) .build(); - this->descriptorSets.at(3).update(device) + this->descriptorSets.at(2).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) .build(); - this->descriptorSets.at(4).update(device) + this->descriptorSets.at(3).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) @@ -91,7 +112,7 @@ Beta::Beta(const Device& device, const Core::DescriptorPool& pool, .build(); } -void Beta::Dispatch(const Core::CommandBuffer& buf) { +void Beta::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { const auto extent = this->tempImgs1.at(0).getExtent(); // first pass @@ -99,13 +120,14 @@ void Beta::Dispatch(const Core::CommandBuffer& buf) { uint32_t threadsY = (extent.height + 7) >> 3; Utils::BarrierBuilder(buf) - .addW2R(this->temporalImgs) - .addW2R(this->inImgs) + .addW2R(this->inImgs_0) + .addW2R(this->inImgs_1) + .addW2R(this->inImgs_2) .addR2W(this->tempImgs1) .build(); this->pipelines.at(0).bind(buf); - this->descriptorSets.at(0).bind(buf, this->pipelines.at(0)); + this->specialDescriptorSets.at(fc % 3).bind(buf, this->pipelines.at(0)); buf.dispatch(threadsX, threadsY, 1); // second pass @@ -115,7 +137,7 @@ void Beta::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipelines.at(1).bind(buf); - this->descriptorSets.at(1).bind(buf, this->pipelines.at(1)); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(1)); buf.dispatch(threadsX, threadsY, 1); // third pass @@ -125,7 +147,7 @@ void Beta::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipelines.at(2).bind(buf); - this->descriptorSets.at(2).bind(buf, this->pipelines.at(2)); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(2)); buf.dispatch(threadsX, threadsY, 1); // fourth pass @@ -135,7 +157,7 @@ void Beta::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipelines.at(3).bind(buf); - this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(3)); buf.dispatch(threadsX, threadsY, 1); // fifth pass @@ -148,6 +170,6 @@ void Beta::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipelines.at(4).bind(buf); - this->descriptorSets.at(4).bind(buf, this->pipelines.at(4)); + this->descriptorSets.at(3).bind(buf, this->pipelines.at(4)); buf.dispatch(threadsX, threadsY, 1); } From b66568f0b796702a028144c2b1f39bc52f447234 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 04:12:49 +0200 Subject: [PATCH 036/253] remaining temporal logic --- include/shaderchains/alpha.hpp | 6 +- include/shaderchains/beta.hpp | 6 +- include/shaderchains/gamma.hpp | 21 +++--- include/shaderchains/magic.hpp | 20 +++--- src/lsfg.cpp | 117 +++++++++++++++++---------------- src/shaderchains/alpha.cpp | 7 ++ src/shaderchains/beta.cpp | 2 +- src/shaderchains/gamma.cpp | 78 ++++++++++++++-------- src/shaderchains/magic.cpp | 71 +++++++++++++------- 9 files changed, 198 insertions(+), 130 deletions(-) diff --git a/include/shaderchains/alpha.hpp b/include/shaderchains/alpha.hpp index 2f18daf..fb97e3f 100644 --- a/include/shaderchains/alpha.hpp +++ b/include/shaderchains/alpha.hpp @@ -44,11 +44,11 @@ namespace LSFG::Shaderchains { /// void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); - /// Get the output images written to when fc % 2 == 0 + /// Get the output images written to when fc % 3 == 0 [[nodiscard]] const auto& getOutImages0() const { return this->outImgs_0; } - /// Get the output images written to when fc % 2 == 1 + /// Get the output images written to when fc % 3 == 1 [[nodiscard]] const auto& getOutImages1() const { return this->outImgs_1; } - /// Get the output images written to when fc % 2 == 2 + /// Get the output images written to when fc % 3 == 2 [[nodiscard]] const auto& getOutImages2() const { return this->outImgs_2; } /// Trivially copyable, moveable and destructible diff --git a/include/shaderchains/beta.hpp b/include/shaderchains/beta.hpp index f0de6d0..3420ae4 100644 --- a/include/shaderchains/beta.hpp +++ b/include/shaderchains/beta.hpp @@ -29,9 +29,9 @@ namespace LSFG::Shaderchains { /// /// @param device The Vulkan device to create the resources on. /// @param pool The descriptor pool to allocate in. - /// @param inImgs_0 The next input images to process (when fc % 2 == 0) - /// @param inImgs_1 The prev input images to process (when fc % 2 == 0) - /// @param inImgs_2 The prev prev input images to process (when fc % 2 == 0) + /// @param inImgs_0 The next input images to process (when fc % 3 == 0) + /// @param inImgs_1 The prev input images to process (when fc % 3 == 0) + /// @param inImgs_2 The prev prev input images to process (when fc % 3 == 0) /// /// @throws LSFG::vulkan_error if resource creation fails. /// diff --git a/include/shaderchains/gamma.hpp b/include/shaderchains/gamma.hpp index 790c1d6..a2767d8 100644 --- a/include/shaderchains/gamma.hpp +++ b/include/shaderchains/gamma.hpp @@ -31,8 +31,9 @@ namespace LSFG::Shaderchains { /// /// @param device The Vulkan device to create the resources on. /// @param pool The descriptor pool to allocate in. - /// @param temporalImgs The temporal images to use for processing. - /// @param inImgs1 The input images to process. + /// @param inImgs1_0 The next input images to process (when fc % 3 == 0). + /// @param inImgs1_1 The prev input images to process (when fc % 3 == 0). + /// @param inImgs1_2 Initially unprocessed prev prev input images (when fc % 3 == 0). /// @param inImg2 The second input image to process, next step up the resolution. /// @param optImg1 An optional additional input from the previous pass. /// @param optImg2 An optional additional input image for processing non-first passes. @@ -41,8 +42,9 @@ namespace LSFG::Shaderchains { /// @throws LSFG::vulkan_error if resource creation fails. /// Gamma(const Device& device, const Core::DescriptorPool& pool, - std::array temporalImgs, - std::array inImgs1, + std::array inImgs1_0, + std::array inImgs1_1, + std::array inImgs1_2, Core::Image inImg2, std::optional optImg1, std::optional optImg2, @@ -52,10 +54,11 @@ namespace LSFG::Shaderchains { /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. + /// @param fc The frame count, used to select the input images. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf); + void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); /// Get the first output image. [[nodiscard]] const auto& getOutImage1() const { return this->outImg1; } @@ -71,11 +74,13 @@ namespace LSFG::Shaderchains { private: std::array shaderModules; std::array pipelines; - std::array descriptorSets; + std::array descriptorSets; // first shader has special logic + std::array specialDescriptorSets; Core::Buffer buffer; - std::array temporalImgs; - std::array inImgs1; + std::array inImgs1_0; + std::array inImgs1_1; + std::array inImgs1_2; Core::Image inImg2; Core::Image optImg1; // specified or created black std::optional optImg2; diff --git a/include/shaderchains/magic.hpp b/include/shaderchains/magic.hpp index 934a9f8..f1b287a 100644 --- a/include/shaderchains/magic.hpp +++ b/include/shaderchains/magic.hpp @@ -29,8 +29,9 @@ namespace LSFG::Shaderchains { /// /// @param device The Vulkan device to create the resources on. /// @param pool The descriptor pool to use for descriptor sets. - /// @param temporalImgs The temporal images to use for processing. - /// @param inImgs1 The first set of input images to process. + /// @param inImgs1_0 The next input images to process (when fc % 3 == 0). + /// @param inImgs1_1 The prev input images to process (when fc % 3 == 0). + /// @param inImgs1_2 Initially unprocessed prev prev input images (when fc % 3 == 0). /// @param inImg2 The second input image to process. /// @param inImg3 The third input image to process, next step up the resolution. /// @param optImg An optional additional input from the previous pass. @@ -38,8 +39,9 @@ namespace LSFG::Shaderchains { /// @throws LSFG::vulkan_error if resource creation fails. /// Magic(const Device& device, const Core::DescriptorPool& pool, - std::array temporalImgs, - std::array inImgs1, + std::array inImgs1_0, + std::array inImgs1_1, + std::array inImgs1_2, Core::Image inImg2, Core::Image inImg3, std::optional optImg); @@ -48,10 +50,11 @@ namespace LSFG::Shaderchains { /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. + /// @param fc The frame count, used to select the input images. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf); + void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); /// Get the first set of output images [[nodiscard]] const auto& getOutImages1() const { return this->outImgs1; } @@ -69,11 +72,12 @@ namespace LSFG::Shaderchains { private: Core::ShaderModule shaderModule; Core::Pipeline pipeline; - Core::DescriptorSet descriptorSet; + std::array descriptorSets; Core::Buffer buffer; - std::array temporalImgs; - std::array inImgs1; + std::array inImgs1_0; + std::array inImgs1_1; + std::array inImgs1_2; Core::Image inImg2; Core::Image inImg3; std::optional optImg; diff --git a/src/lsfg.cpp b/src/lsfg.cpp index 5b5ec1b..b63c8f4 100644 --- a/src/lsfg.cpp +++ b/src/lsfg.cpp @@ -18,53 +18,56 @@ Generator::Generator(const Context& context) { this->alphaChains.at(0).getOutImages0(), this->alphaChains.at(0).getOutImages2(), this->alphaChains.at(0).getOutImages1()); - // for (size_t i = 0; i < 7; i++) { - // if (i < 4) { - // this->gammaChains.at(i) = Shaderchains::Gamma(context.device, context.descPool, - // this->alphaChains.at(6 - i).getOutImages1(), - // this->alphaChains.at(6 - i).getOutImages0(), - // this->betaChain.getOutImages().at(std::min(5UL, 6 - i)), - // i == 0 ? std::nullopt - // : std::optional{this->gammaChains.at(i - 1).getOutImage2()}, - // i == 0 ? std::nullopt - // : std::optional{this->gammaChains.at(i - 1).getOutImage1()}, - // this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() - // ); - // } else { - // // this->magicChains.at(i - 4) = Shaderchains::Magic(context.device, context.descPool, - // // this->alphaChains.at(6 - i).getOutImages(), - // // i == 4 ? this->gammaChains.at(i - 1).getOutImage2() - // // : this->extractChains.at(i - 5).getOutImage(), - // // i == 4 ? this->gammaChains.at(i - 1).getOutImage1() - // // : this->zetaChains.at(i - 5).getOutImage(), - // // i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()} - // // ); - // this->deltaChains.at(i - 4) = Shaderchains::Delta(context.device, context.descPool, - // this->magicChains.at(i - 4).getOutImages1(), - // i == 4 ? std::nullopt - // : std::optional{this->deltaChains.at(i - 5).getOutImage()} - // ); - // this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(context.device, context.descPool, - // this->magicChains.at(i - 4).getOutImages2(), - // this->betaChain.getOutImages().at(6 - i), - // i == 4 ? std::nullopt - // : std::optional{this->epsilonChains.at(i - 5).getOutImage()} - // ); - // this->zetaChains.at(i - 4) = Shaderchains::Zeta(context.device, context.descPool, - // this->magicChains.at(i - 4).getOutImages3(), - // i == 4 ? this->gammaChains.at(i - 1).getOutImage1() - // : this->zetaChains.at(i - 5).getOutImage(), - // this->betaChain.getOutImages().at(6 - i) - // ); - // if (i >= 6) - // continue; // no extract for i >= 6 - // this->extractChains.at(i - 4) = Shaderchains::Extract(context.device, context.descPool, - // this->zetaChains.at(i - 4).getOutImage(), - // this->epsilonChains.at(i - 4).getOutImage(), - // this->extractChains.at(i - 5).getOutImage().getExtent() - // ); - // } - // } + for (size_t i = 0; i < 7; i++) { + if (i < 4) { + this->gammaChains.at(i) = Shaderchains::Gamma(context.device, context.descPool, + this->alphaChains.at(6 - i).getOutImages0(), + this->alphaChains.at(6 - i).getOutImages2(), + this->alphaChains.at(6 - i).getOutImages1(), + this->betaChain.getOutImages().at(std::min(5UL, 6 - i)), + i == 0 ? std::nullopt + : std::optional{this->gammaChains.at(i - 1).getOutImage2()}, + i == 0 ? std::nullopt + : std::optional{this->gammaChains.at(i - 1).getOutImage1()}, + this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() + ); + } else { + this->magicChains.at(i - 4) = Shaderchains::Magic(context.device, context.descPool, + this->alphaChains.at(6 - i).getOutImages0(), + this->alphaChains.at(6 - i).getOutImages2(), + this->alphaChains.at(6 - i).getOutImages1(), + i == 4 ? this->gammaChains.at(i - 1).getOutImage2() + : this->extractChains.at(i - 5).getOutImage(), + i == 4 ? this->gammaChains.at(i - 1).getOutImage1() + : this->zetaChains.at(i - 5).getOutImage(), + i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()} + ); + this->deltaChains.at(i - 4) = Shaderchains::Delta(context.device, context.descPool, + this->magicChains.at(i - 4).getOutImages1(), + i == 4 ? std::nullopt + : std::optional{this->deltaChains.at(i - 5).getOutImage()} + ); + this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(context.device, context.descPool, + this->magicChains.at(i - 4).getOutImages2(), + this->betaChain.getOutImages().at(6 - i), + i == 4 ? std::nullopt + : std::optional{this->epsilonChains.at(i - 5).getOutImage()} + ); + this->zetaChains.at(i - 4) = Shaderchains::Zeta(context.device, context.descPool, + this->magicChains.at(i - 4).getOutImages3(), + i == 4 ? this->gammaChains.at(i - 1).getOutImage1() + : this->zetaChains.at(i - 5).getOutImage(), + this->betaChain.getOutImages().at(6 - i) + ); + if (i >= 6) + continue; // no extract for i >= 6 + this->extractChains.at(i - 4) = Shaderchains::Extract(context.device, context.descPool, + this->zetaChains.at(i - 4).getOutImage(), + this->epsilonChains.at(i - 4).getOutImage(), + this->extractChains.at(i - 5).getOutImage().getExtent() + ); + } + } this->mergeChain = Shaderchains::Merge(context.device, context.descPool, this->inImg_0, this->inImg_1, @@ -82,16 +85,16 @@ void Generator::present(const Context& context) { for (size_t i = 0; i < 7; i++) this->alphaChains.at(6 - i).Dispatch(cmdBuffer, fc); this->betaChain.Dispatch(cmdBuffer, fc); - // for (size_t i = 0; i < 4; i++) - // this->gammaChains.at(i).Dispatch(cmdBuffer); - // for (size_t i = 0; i < 3; i++) { - // this->magicChains.at(i).Dispatch(cmdBuffer); - // this->deltaChains.at(i).Dispatch(cmdBuffer); - // this->epsilonChains.at(i).Dispatch(cmdBuffer); - // this->zetaChains.at(i).Dispatch(cmdBuffer); - // if (i < 2) - // this->extractChains.at(i).Dispatch(cmdBuffer); - // } + for (size_t i = 0; i < 4; i++) + this->gammaChains.at(i).Dispatch(cmdBuffer, fc); + for (size_t i = 0; i < 3; i++) { + this->magicChains.at(i).Dispatch(cmdBuffer, fc); + this->deltaChains.at(i).Dispatch(cmdBuffer); + this->epsilonChains.at(i).Dispatch(cmdBuffer); + this->zetaChains.at(i).Dispatch(cmdBuffer); + if (i < 2) + this->extractChains.at(i).Dispatch(cmdBuffer); + } this->mergeChain.Dispatch(cmdBuffer, fc); cmdBuffer.end(); diff --git a/src/shaderchains/alpha.cpp b/src/shaderchains/alpha.cpp index 8f18f90..b19ab2b 100644 --- a/src/shaderchains/alpha.cpp +++ b/src/shaderchains/alpha.cpp @@ -106,6 +106,13 @@ Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outImgs) .build(); } + + // clear the output images (so they're not undefined) + for (size_t i = 0; i < 4; i++) { + Utils::clearImage(device, this->outImgs_0.at(i)); + Utils::clearImage(device, this->outImgs_1.at(i)); + Utils::clearImage(device, this->outImgs_2.at(i)); + } } void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { diff --git a/src/shaderchains/beta.cpp b/src/shaderchains/beta.cpp index dbbfab6..25c0176 100644 --- a/src/shaderchains/beta.cpp +++ b/src/shaderchains/beta.cpp @@ -37,7 +37,7 @@ Beta::Beta(const Device& device, const Core::DescriptorPool& pool, this->pipelines.at(i) = Core::Pipeline(device, this->shaderModules.at(i)); if (i == 0) continue; // first shader has special logic - this->descriptorSets.at(i+1) = Core::DescriptorSet(device, pool, + this->descriptorSets.at(i - 1) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); } for (size_t i = 0; i < 3; i++) diff --git a/src/shaderchains/gamma.cpp b/src/shaderchains/gamma.cpp index 42e20f9..8014f4b 100644 --- a/src/shaderchains/gamma.cpp +++ b/src/shaderchains/gamma.cpp @@ -4,14 +4,16 @@ using namespace LSFG::Shaderchains; Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, - std::array temporalImgs, - std::array inImgs1, + std::array inImgs1_0, + std::array inImgs1_1, + std::array inImgs1_2, Core::Image inImg2, std::optional optImg1, // NOLINT std::optional optImg2, VkExtent2D outExtent) - : temporalImgs(std::move(temporalImgs)), - inImgs1(std::move(inImgs1)), + : inImgs1_0(std::move(inImgs1_0)), + inImgs1_1(std::move(inImgs1_1)), + inImgs1_2(std::move(inImgs1_2)), inImg2(std::move(inImg2)), optImg2(std::move(optImg2)) { this->shaderModules = {{ @@ -46,15 +48,19 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, for (size_t i = 0; i < 6; i++) { this->pipelines.at(i) = Core::Pipeline(device, this->shaderModules.at(i)); - this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, + if (i == 0) continue; // first shader has special logic + this->descriptorSets.at(i - 1) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); } + for (size_t i = 0; i < 3; i++) + this->specialDescriptorSets.at(i) = Core::DescriptorSet(device, pool, + this->shaderModules.at(0)); Globals::FgBuffer data = Globals::fgBuffer; data.firstIter = !optImg1.has_value(); this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - const auto extent = this->temporalImgs.at(0).getExtent(); + const auto extent = this->inImgs1_0.at(0).getExtent(); this->optImg1 = optImg1.value_or(Core::Image(device, extent, VK_FORMAT_R8G8B8A8_UNORM, @@ -91,36 +97,47 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); + for (size_t fc = 0; fc < 3; fc++) { + auto& nextImgs1 = this->inImgs1_0; + auto& prevImgs1 = this->inImgs1_2; + if (fc == 1) { + nextImgs1 = this->inImgs1_1; + prevImgs1 = this->inImgs1_0; + } else if (fc == 2) { + nextImgs1 = this->inImgs1_2; + prevImgs1 = this->inImgs1_1; + } + this->specialDescriptorSets.at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, prevImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, nextImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) + .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)) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + } this->descriptorSets.at(0).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->temporalImgs) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) - .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)) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); - this->descriptorSets.at(1).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .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(); - this->descriptorSets.at(2).update(device) + this->descriptorSets.at(1).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) .build(); - this->descriptorSets.at(3).update(device) + this->descriptorSets.at(2).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) .build(); - this->descriptorSets.at(4).update(device) + this->descriptorSets.at(3).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) @@ -129,7 +146,7 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1) .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); - this->descriptorSets.at(5).update(device) + this->descriptorSets.at(4).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) @@ -144,16 +161,25 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, Utils::clearImage(device, this->optImg1); } -void Gamma::Dispatch(const Core::CommandBuffer& buf) { +void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { const auto extent = this->tempImgs1.at(0).getExtent(); // first pass uint32_t threadsX = (extent.width + 7) >> 3; uint32_t threadsY = (extent.height + 7) >> 3; + auto& nextImgs1 = this->inImgs1_0; + auto& prevImgs1 = this->inImgs1_2; + if ((fc % 3) == 1) { + nextImgs1 = this->inImgs1_1; + prevImgs1 = this->inImgs1_0; + } else if ((fc % 3) == 2) { + nextImgs1 = this->inImgs1_2; + prevImgs1 = this->inImgs1_1; + } Utils::BarrierBuilder(buf) - .addW2R(this->temporalImgs) - .addW2R(this->inImgs1) + .addW2R(prevImgs1) + .addW2R(nextImgs1) .addW2R(this->optImg1) .addW2R(this->optImg2) .addR2W(this->tempImgs1.at(0)) @@ -162,7 +188,7 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipelines.at(0).bind(buf); - this->descriptorSets.at(0).bind(buf, this->pipelines.at(0)); + this->specialDescriptorSets.at(fc % 3).bind(buf, this->pipelines.at(0)); buf.dispatch(threadsX, threadsY, 1); // second pass diff --git a/src/shaderchains/magic.cpp b/src/shaderchains/magic.cpp index 8d8c9b4..e907518 100644 --- a/src/shaderchains/magic.cpp +++ b/src/shaderchains/magic.cpp @@ -4,13 +4,15 @@ using namespace LSFG::Shaderchains; Magic::Magic(const Device& device, const Core::DescriptorPool& pool, - std::array temporalImgs, - std::array inImgs1, + std::array inImgs1_0, + std::array inImgs1_1, + std::array inImgs1_2, Core::Image inImg2, Core::Image inImg3, std::optional optImg) - : temporalImgs(std::move(temporalImgs)), - inImgs1(std::move(inImgs1)), + : inImgs1_0(std::move(inImgs1_0)), + inImgs1_1(std::move(inImgs1_1)), + inImgs1_2(std::move(inImgs1_2)), inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), optImg(std::move(optImg)) { this->shaderModule = Core::ShaderModule(device, "rsc/shaders/magic.spv", @@ -19,13 +21,14 @@ Magic::Magic(const Device& device, const Core::DescriptorPool& pool, { 3+3+2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); this->pipeline = Core::Pipeline(device, this->shaderModule); - this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); + for (size_t i = 0; i < 3; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModule); Globals::FgBuffer data = Globals::fgBuffer; data.firstIterS = !this->optImg.has_value(); this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - auto extent = this->temporalImgs.at(0).getExtent(); + auto extent = this->inImgs1_0.at(0).getExtent(); for (size_t i = 0; i < 2; i++) this->outImgs1.at(i) = Core::Image(device, @@ -46,31 +49,51 @@ Magic::Magic(const Device& device, const Core::DescriptorPool& pool, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - this->descriptorSet.update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->temporalImgs) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs3) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t fc = 0; fc < 3; fc++) { + auto& nextImgs1 = this->inImgs1_0; + auto& prevImgs1 = this->inImgs1_2; + if (fc == 1) { + nextImgs1 = this->inImgs1_1; + prevImgs1 = this->inImgs1_0; + } else if (fc == 2) { + nextImgs1 = this->inImgs1_2; + prevImgs1 = this->inImgs1_1; + } + this->descriptorSets.at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, prevImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, nextImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .build(); + } } -void Magic::Dispatch(const Core::CommandBuffer& buf) { - auto extent = this->temporalImgs.at(0).getExtent(); +void Magic::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { + auto extent = this->inImgs1_0.at(0).getExtent(); // first pass const uint32_t threadsX = (extent.width + 7) >> 3; const uint32_t threadsY = (extent.height + 7) >> 3; + auto& nextImgs1 = this->inImgs1_0; + auto& prevImgs1 = this->inImgs1_2; + if ((fc % 3) == 1) { + nextImgs1 = this->inImgs1_1; + prevImgs1 = this->inImgs1_0; + } else if ((fc % 3) == 2) { + nextImgs1 = this->inImgs1_2; + prevImgs1 = this->inImgs1_1; + } Utils::BarrierBuilder(buf) - .addW2R(this->temporalImgs) - .addW2R(this->inImgs1) + .addW2R(prevImgs1) + .addW2R(nextImgs1) .addW2R(this->inImg2) .addW2R(this->inImg3) .addW2R(this->optImg) @@ -80,6 +103,6 @@ void Magic::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipeline.bind(buf); - this->descriptorSet.bind(buf, this->pipeline); + this->descriptorSets.at(fc).bind(buf, this->pipeline); buf.dispatch(threadsX, threadsY, 1); } From 0bdbc4b25cc39fde3694b57b5283542da7b3d217 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 05:25:03 +0200 Subject: [PATCH 037/253] bugfixes again --- src/lsfg.cpp | 31 +++++- src/main.cpp | 195 +++---------------------------------- src/shaderchains/alpha.cpp | 25 ++--- src/shaderchains/beta.cpp | 26 ++--- src/shaderchains/gamma.cpp | 42 ++++---- src/shaderchains/magic.cpp | 32 +++--- src/shaderchains/merge.cpp | 2 +- 7 files changed, 106 insertions(+), 247 deletions(-) diff --git a/src/lsfg.cpp b/src/lsfg.cpp index b63c8f4..1bf488e 100644 --- a/src/lsfg.cpp +++ b/src/lsfg.cpp @@ -1,12 +1,26 @@ #include "lsfg.hpp" #include "core/commandbuffer.hpp" +#include "core/fence.hpp" +#include "core/image.hpp" +#include #include using namespace LSFG; Generator::Generator(const Context& context) { - // TODO: temporal frames + // TEST: create temporal images + this->inImg_0 = Core::Image(context.device, + { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ); + this->inImg_1 = Core::Image(context.device, + { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT + ); + // TEST END // create shader chains this->downsampleChain = Shaderchains::Downsample(context.device, context.descPool, @@ -64,13 +78,13 @@ Generator::Generator(const Context& context) { this->extractChains.at(i - 4) = Shaderchains::Extract(context.device, context.descPool, this->zetaChains.at(i - 4).getOutImage(), this->epsilonChains.at(i - 4).getOutImage(), - this->extractChains.at(i - 5).getOutImage().getExtent() + this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() ); } } this->mergeChain = Shaderchains::Merge(context.device, context.descPool, - this->inImg_0, this->inImg_1, + this->inImg_0, this->zetaChains.at(2).getOutImage(), this->epsilonChains.at(2).getOutImage(), this->deltaChains.at(2).getOutImage() @@ -78,6 +92,10 @@ Generator::Generator(const Context& context) { } void Generator::present(const Context& context) { + // TEST: upload input images + // TODO: implement test, lol + // TEST END + Core::CommandBuffer cmdBuffer(context.device, context.cmdPool); cmdBuffer.begin(); @@ -99,7 +117,12 @@ void Generator::present(const Context& context) { cmdBuffer.end(); - // TODO: submit logic + // TEST: temporary fence submit logic + Core::Fence fence(context.device); + cmdBuffer.submit(context.device.getComputeQueue(), fence); + assert(fence.wait(context.device)); + // TEST END + fc++; } diff --git a/src/main.cpp b/src/main.cpp index de8e249..83b0e26 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,29 +1,10 @@ -#include "core/commandbuffer.hpp" -#include "core/commandpool.hpp" -#include "core/descriptorpool.hpp" -#include "core/fence.hpp" -#include "core/image.hpp" -#include "device.hpp" -#include "instance.hpp" -#include "shaderchains/alpha.hpp" -#include "shaderchains/beta.hpp" -#include "shaderchains/delta.hpp" -#include "shaderchains/downsample.hpp" -#include "shaderchains/epsilon.hpp" -#include "shaderchains/extract.hpp" -#include "shaderchains/gamma.hpp" -#include "shaderchains/magic.hpp" -#include "shaderchains/merge.hpp" -#include "shaderchains/zeta.hpp" -#include "utils.hpp" +#include "lsfg.hpp" -#include #include #include #include #include -#include using namespace LSFG; @@ -42,172 +23,24 @@ int main() { usleep(1000 * 100); // give renderdoc time to load } - // initialize application - // const Instance instance; - // const Device device(instance); - // const Core::DescriptorPool descriptorPool(device); - // const Core::CommandPool commandPool(device); + // initialize test application + LSFG::Context context; + auto gen = LSFG::Generator(context); - // Globals::initializeGlobals(device); + for (int i = 0; i < 3; i++) { + if (rdoc) + rdoc->StartFrameCapture(nullptr, nullptr); - // create downsample shader chain - // Core::Image inputImage( - // device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, - // VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - // VK_IMAGE_ASPECT_COLOR_BIT - // ); - // Utils::uploadImage(device, commandPool, inputImage, "rsc/images/source.dds"); + gen.present(context); - // Shaderchains::Downsample downsample(device, descriptorPool, inputImage); + if (rdoc) + rdoc->EndFrameCapture(nullptr, nullptr); - // create alpha shader chains - // std::vector alphas; - // alphas.reserve(7); - // for (size_t i = 0; i < 7; ++i) - // alphas.emplace_back(device, descriptorPool, downsample.getOutImages().at(i)); + // sleep 8 ms + usleep(8000); + } - // // create beta shader chain - // std::array betaTemporalImages; - // auto betaInExtent = alphas.at(0).getOutImages().at(0).getExtent(); - // for (size_t i = 0; i < 8; ++i) { - // betaTemporalImages.at(i) = Core::Image( - // device, betaInExtent, VK_FORMAT_R8G8B8A8_UNORM, - // VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | - // VK_IMAGE_USAGE_TRANSFER_DST_BIT, - // VK_IMAGE_ASPECT_COLOR_BIT - // ); - // Utils::uploadImage(device, commandPool, betaTemporalImages.at(i), - // std::format("rsc/images/temporal_beta/{}.dds", i)); - // } - - // Shaderchains::Beta beta(device, descriptorPool, - // betaTemporalImages, - // alphas.at(0).getOutImages() - // ); - - // create gamma to zeta shader chains - // std::vector gammas; - // std::vector magics; - // std::vector deltas; - // std::vector epsilons; - // std::vector zetas; - // std::vector extracts; - // std::array, 7> otherTemporalImages; - // for (size_t i = 0; i < 7; i++) { - // auto otherInExtent = alphas.at(6 - i).getOutImages().at(0).getExtent(); - // for (size_t j = 0; j < 4; j++) { - // otherTemporalImages.at(i).at(j) = Core::Image( - // device, otherInExtent, VK_FORMAT_R8G8B8A8_UNORM, - // VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | - // VK_IMAGE_USAGE_TRANSFER_DST_BIT, - // VK_IMAGE_ASPECT_COLOR_BIT - // ); - // Utils::uploadImage(device, commandPool, otherTemporalImages.at(i).at(j), - // std::format("rsc/images/temporal_other/{}.{}.dds", i, j)); - // } - // if (i < 4) { - // auto gammaOutExtent = alphas.at(6 - i - 1).getOutImages().at(0).getExtent(); - // gammas.emplace_back(device, descriptorPool, - // otherTemporalImages.at(i), - // alphas.at(6 - i).getOutImages(), - // beta.getOutImages().at(std::min(static_cast(5), 6 - i)), // smallest twice - // i == 0 ? std::nullopt : std::optional{gammas.at(i - 1).getOutImage2()}, - // i == 0 ? std::nullopt : std::optional{gammas.at(i - 1).getOutImage1()}, - // gammaOutExtent); - // } else { - // magics.emplace_back(device, descriptorPool, - // otherTemporalImages.at(i), - // alphas.at(6 - i).getOutImages(), - // i == 4 ? gammas.at(i - 1).getOutImage2() : extracts.at(i - 5).getOutImage(), - // i == 4 ? gammas.at(i - 1).getOutImage1() : zetas.at(i - 5).getOutImage(), - // i == 4 ? std::nullopt : std::optional{epsilons.at(i - 5).getOutImage()} - // ); - // deltas.emplace_back(device, descriptorPool, - // magics.at(i - 4).getOutImages1(), - // i == 4 ? std::nullopt : std::optional{deltas.at(i - 5).getOutImage()} - // ); - // epsilons.emplace_back(device, descriptorPool, - // magics.at(i - 4).getOutImages2(), - // beta.getOutImages().at(6 - i), - // i == 4 ? std::nullopt : std::optional{epsilons.at(i - 5).getOutImage()} - // ); - // zetas.emplace_back(device, descriptorPool, - // magics.at(i - 4).getOutImages3(), - // i == 4 ? gammas.at(i - 1).getOutImage1() : zetas.at(i - 5).getOutImage(), - // beta.getOutImages().at(6 - i) - // ); - // if (i < 6) { - // auto extractOutExtent = alphas.at(6 - i - 1).getOutImages().at(0).getExtent(); - // extracts.emplace_back(device, descriptorPool, - // zetas.at(i - 4).getOutImage(), - // epsilons.at(i - 4).getOutImage(), - // extractOutExtent); - // } - // } - // } - - // // create merge shader chain - // Core::Image inputImagePrev( - // device, { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, - // VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - // VK_IMAGE_ASPECT_COLOR_BIT - // ); - // Utils::uploadImage(device, commandPool, inputImagePrev, "rsc/images/source_prev.dds"); - // Shaderchains::Merge merge(device, descriptorPool, - // inputImagePrev, - // inputImage, - // zetas.at(2).getOutImage(), - // epsilons.at(2).getOutImage(), - // deltas.at(2).getOutImage() - // ); - - // for (int i = 0; i < 1000; i++) { - // const Core::Fence fence(device); - - // // start the rendering pipeline - // if (rdoc) - // rdoc->StartFrameCapture(nullptr, nullptr); - - // Core::CommandBuffer commandBuffer(device, commandPool); - // commandBuffer.begin(); - - // downsample.Dispatch(commandBuffer); - // for (size_t i = 0; i < 7; i++) - // alphas.at(6 - i).Dispatch(commandBuffer); - // beta.Dispatch(commandBuffer); - // for (size_t i = 0; i < 4; i++) - // gammas.at(i).Dispatch(commandBuffer); - // for (size_t i = 0; i < 3; i++) { - // magics.at(i).Dispatch(commandBuffer); - // deltas.at(i).Dispatch(commandBuffer); - // epsilons.at(i).Dispatch(commandBuffer); - // zetas.at(i).Dispatch(commandBuffer); - // if (i < 2) - // extracts.at(i).Dispatch(commandBuffer); - // } - // merge.Dispatch(commandBuffer); - - // // finish the rendering pipeline - // commandBuffer.end(); - - // commandBuffer.submit(device.getComputeQueue(), fence); - // if (!fence.wait(device)) { - // Globals::uninitializeGlobals(); - - // std::cerr << "Application failed due to timeout" << '\n'; - // return 1; - // } - - // if (rdoc) - // rdoc->EndFrameCapture(nullptr, nullptr); - - // // sleep 8 ms - // usleep(8000); - // } - - // usleep(1000 * 100); // give renderdoc time to capture - - // Globals::uninitializeGlobals(); + usleep(1000 * 100); std::cerr << "Application finished" << '\n'; return 0; diff --git a/src/shaderchains/alpha.cpp b/src/shaderchains/alpha.cpp index b19ab2b..ba7f4c0 100644 --- a/src/shaderchains/alpha.cpp +++ b/src/shaderchains/alpha.cpp @@ -67,17 +67,20 @@ Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, this->outImgs_0.at(i) = Core::Image(device, quarterExtent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT + | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); this->outImgs_1.at(i) = Core::Image(device, quarterExtent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT + | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); this->outImgs_2.at(i) = Core::Image(device, quarterExtent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT + | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_ASPECT_COLOR_BIT); } @@ -97,13 +100,13 @@ Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs3) .build(); for (size_t fc = 0; fc < 3; fc++) { - auto& outImgs = this->outImgs_0; - if (fc == 1) outImgs = this->outImgs_1; - else if (fc == 2) outImgs = this->outImgs_2; + auto* outImgs = &this->outImgs_0; + if (fc == 1) outImgs = &this->outImgs_1; + else if (fc == 2) outImgs = &this->outImgs_2; this->specialDescriptorSets.at(fc).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outImgs) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, *outImgs) .build(); } @@ -156,12 +159,12 @@ void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { buf.dispatch(threadsX, threadsY, 1); // fourth pass - auto& outImgs = this->outImgs_0; - if ((fc % 3) == 1) outImgs = this->outImgs_1; - else if ((fc % 3) == 2) outImgs = this->outImgs_2; + auto* outImgs = &this->outImgs_0; + if ((fc % 3) == 1) outImgs = &this->outImgs_1; + else if ((fc % 3) == 2) outImgs = &this->outImgs_2; Utils::BarrierBuilder(buf) .addW2R(this->tempImgs3) - .addR2W(outImgs) + .addR2W(*outImgs) .build(); this->pipelines.at(3).bind(buf); diff --git a/src/shaderchains/beta.cpp b/src/shaderchains/beta.cpp index 25c0176..698aa34 100644 --- a/src/shaderchains/beta.cpp +++ b/src/shaderchains/beta.cpp @@ -42,7 +42,7 @@ Beta::Beta(const Device& device, const Core::DescriptorPool& pool, } for (size_t i = 0; i < 3; i++) this->specialDescriptorSets.at(i) = Core::DescriptorSet(device, pool, - this->shaderModules.at(4)); + this->shaderModules.at(0)); this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); const auto extent = this->inImgs_0.at(0).getExtent(); @@ -69,23 +69,23 @@ Beta::Beta(const Device& device, const Core::DescriptorPool& pool, } for (size_t fc = 0; fc < 3; fc++) { - auto& nextImgs = this->inImgs_0; - auto& prevImgs = this->inImgs_2; - auto& pprevImgs = this->inImgs_1; + auto* nextImgs = &this->inImgs_0; + auto* prevImgs = &this->inImgs_2; + auto* pprevImgs = &this->inImgs_1; if (fc == 1) { - nextImgs = this->inImgs_1; - prevImgs = this->inImgs_0; - pprevImgs = this->inImgs_2; + nextImgs = &this->inImgs_1; + prevImgs = &this->inImgs_0; + pprevImgs = &this->inImgs_2; } else if (fc == 2) { - nextImgs = this->inImgs_2; - prevImgs = this->inImgs_1; - pprevImgs = this->inImgs_0; + nextImgs = &this->inImgs_2; + prevImgs = &this->inImgs_1; + pprevImgs = &this->inImgs_0; } this->specialDescriptorSets.at(fc).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, pprevImgs) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, prevImgs) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, nextImgs) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *pprevImgs) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *nextImgs) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) .build(); } diff --git a/src/shaderchains/gamma.cpp b/src/shaderchains/gamma.cpp index 8014f4b..cd1b33b 100644 --- a/src/shaderchains/gamma.cpp +++ b/src/shaderchains/gamma.cpp @@ -98,20 +98,20 @@ Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, VK_IMAGE_ASPECT_COLOR_BIT); for (size_t fc = 0; fc < 3; fc++) { - auto& nextImgs1 = this->inImgs1_0; - auto& prevImgs1 = this->inImgs1_2; + auto* nextImgs1 = &this->inImgs1_0; + auto* prevImgs1 = &this->inImgs1_2; if (fc == 1) { - nextImgs1 = this->inImgs1_1; - prevImgs1 = this->inImgs1_0; + nextImgs1 = &this->inImgs1_1; + prevImgs1 = &this->inImgs1_0; } else if (fc == 2) { - nextImgs1 = this->inImgs1_2; - prevImgs1 = this->inImgs1_1; + nextImgs1 = &this->inImgs1_2; + prevImgs1 = &this->inImgs1_1; } this->specialDescriptorSets.at(fc).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, prevImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, nextImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *nextImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) @@ -168,18 +168,18 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { uint32_t threadsX = (extent.width + 7) >> 3; uint32_t threadsY = (extent.height + 7) >> 3; - auto& nextImgs1 = this->inImgs1_0; - auto& prevImgs1 = this->inImgs1_2; + auto* nextImgs1 = &this->inImgs1_0; + auto* prevImgs1 = &this->inImgs1_2; if ((fc % 3) == 1) { - nextImgs1 = this->inImgs1_1; - prevImgs1 = this->inImgs1_0; + nextImgs1 = &this->inImgs1_1; + prevImgs1 = &this->inImgs1_0; } else if ((fc % 3) == 2) { - nextImgs1 = this->inImgs1_2; - prevImgs1 = this->inImgs1_1; + nextImgs1 = &this->inImgs1_2; + prevImgs1 = &this->inImgs1_1; } Utils::BarrierBuilder(buf) - .addW2R(prevImgs1) - .addW2R(nextImgs1) + .addW2R(*prevImgs1) + .addW2R(*nextImgs1) .addW2R(this->optImg1) .addW2R(this->optImg2) .addR2W(this->tempImgs1.at(0)) @@ -200,7 +200,7 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipelines.at(1).bind(buf); - this->descriptorSets.at(1).bind(buf, this->pipelines.at(1)); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(1)); buf.dispatch(threadsX, threadsY, 1); // third pass @@ -210,7 +210,7 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipelines.at(2).bind(buf); - this->descriptorSets.at(2).bind(buf, this->pipelines.at(2)); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(2)); buf.dispatch(threadsX, threadsY, 1); // fourth pass @@ -220,7 +220,7 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipelines.at(3).bind(buf); - this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(3)); buf.dispatch(threadsX, threadsY, 1); // fifth pass @@ -232,7 +232,7 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipelines.at(4).bind(buf); - this->descriptorSets.at(4).bind(buf, this->pipelines.at(4)); + this->descriptorSets.at(3).bind(buf, this->pipelines.at(4)); buf.dispatch(threadsX, threadsY, 1); // sixth pass @@ -246,6 +246,6 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipelines.at(5).bind(buf); - this->descriptorSets.at(5).bind(buf, this->pipelines.at(5)); + this->descriptorSets.at(4).bind(buf, this->pipelines.at(5)); buf.dispatch(threadsX, threadsY, 1); } diff --git a/src/shaderchains/magic.cpp b/src/shaderchains/magic.cpp index e907518..3ebd895 100644 --- a/src/shaderchains/magic.cpp +++ b/src/shaderchains/magic.cpp @@ -50,20 +50,20 @@ Magic::Magic(const Device& device, const Core::DescriptorPool& pool, VK_IMAGE_ASPECT_COLOR_BIT); for (size_t fc = 0; fc < 3; fc++) { - auto& nextImgs1 = this->inImgs1_0; - auto& prevImgs1 = this->inImgs1_2; + auto* nextImgs1 = &this->inImgs1_0; + auto* prevImgs1 = &this->inImgs1_2; if (fc == 1) { - nextImgs1 = this->inImgs1_1; - prevImgs1 = this->inImgs1_0; + nextImgs1 = &this->inImgs1_1; + prevImgs1 = &this->inImgs1_0; } else if (fc == 2) { - nextImgs1 = this->inImgs1_2; - prevImgs1 = this->inImgs1_1; + nextImgs1 = &this->inImgs1_2; + prevImgs1 = &this->inImgs1_1; } this->descriptorSets.at(fc).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, prevImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, nextImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *nextImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) @@ -82,18 +82,18 @@ void Magic::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { const uint32_t threadsX = (extent.width + 7) >> 3; const uint32_t threadsY = (extent.height + 7) >> 3; - auto& nextImgs1 = this->inImgs1_0; - auto& prevImgs1 = this->inImgs1_2; + auto* nextImgs1 = &this->inImgs1_0; + auto* prevImgs1 = &this->inImgs1_2; if ((fc % 3) == 1) { - nextImgs1 = this->inImgs1_1; - prevImgs1 = this->inImgs1_0; + nextImgs1 = &this->inImgs1_1; + prevImgs1 = &this->inImgs1_0; } else if ((fc % 3) == 2) { - nextImgs1 = this->inImgs1_2; - prevImgs1 = this->inImgs1_1; + nextImgs1 = &this->inImgs1_2; + prevImgs1 = &this->inImgs1_1; } Utils::BarrierBuilder(buf) - .addW2R(prevImgs1) - .addW2R(nextImgs1) + .addW2R(*prevImgs1) + .addW2R(*nextImgs1) .addW2R(this->inImg2) .addW2R(this->inImg3) .addW2R(this->optImg) diff --git a/src/shaderchains/merge.cpp b/src/shaderchains/merge.cpp index 82f09a7..25e8e45 100644 --- a/src/shaderchains/merge.cpp +++ b/src/shaderchains/merge.cpp @@ -64,6 +64,6 @@ void Merge::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipeline.bind(buf); - this->descriptorSets.at(fc).bind(buf, this->pipeline); + this->descriptorSets.at(fc % 2).bind(buf, this->pipeline); buf.dispatch(threadsX, threadsY, 1); } From b7ca80adb7345ce9a5b66ea86755f87a06a15bf6 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 07:12:45 +0200 Subject: [PATCH 038/253] bugfixes final --- src/core/sampler.cpp | 1 + src/lsfg.cpp | 19 +++++-------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/core/sampler.cpp b/src/core/sampler.cpp index 729fac5..768d0c8 100644 --- a/src/core/sampler.cpp +++ b/src/core/sampler.cpp @@ -13,6 +13,7 @@ Sampler::Sampler(const Device& device, VkSamplerAddressMode mode) { .addressModeU = mode, .addressModeV = mode, .addressModeW = mode, + .compareOp = mode == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ? VK_COMPARE_OP_ALWAYS : VK_COMPARE_OP_NEVER, .maxLod = 15.99609F }; VkSampler samplerHandle{}; diff --git a/src/lsfg.cpp b/src/lsfg.cpp index 1bf488e..90b75c4 100644 --- a/src/lsfg.cpp +++ b/src/lsfg.cpp @@ -2,6 +2,7 @@ #include "core/commandbuffer.hpp" #include "core/fence.hpp" #include "core/image.hpp" +#include "utils.hpp" #include #include @@ -30,14 +31,14 @@ Generator::Generator(const Context& context) { this->downsampleChain.getOutImages().at(i)); this->betaChain = Shaderchains::Beta(context.device, context.descPool, this->alphaChains.at(0).getOutImages0(), - this->alphaChains.at(0).getOutImages2(), - this->alphaChains.at(0).getOutImages1()); + this->alphaChains.at(0).getOutImages1(), + this->alphaChains.at(0).getOutImages2()); for (size_t i = 0; i < 7; i++) { if (i < 4) { this->gammaChains.at(i) = Shaderchains::Gamma(context.device, context.descPool, this->alphaChains.at(6 - i).getOutImages0(), - this->alphaChains.at(6 - i).getOutImages2(), this->alphaChains.at(6 - i).getOutImages1(), + this->alphaChains.at(6 - i).getOutImages2(), this->betaChain.getOutImages().at(std::min(5UL, 6 - i)), i == 0 ? std::nullopt : std::optional{this->gammaChains.at(i - 1).getOutImage2()}, @@ -48,8 +49,8 @@ Generator::Generator(const Context& context) { } else { this->magicChains.at(i - 4) = Shaderchains::Magic(context.device, context.descPool, this->alphaChains.at(6 - i).getOutImages0(), - this->alphaChains.at(6 - i).getOutImages2(), this->alphaChains.at(6 - i).getOutImages1(), + this->alphaChains.at(6 - i).getOutImages2(), i == 4 ? this->gammaChains.at(i - 1).getOutImage2() : this->extractChains.at(i - 5).getOutImage(), i == 4 ? this->gammaChains.at(i - 1).getOutImage1() @@ -92,10 +93,6 @@ Generator::Generator(const Context& context) { } void Generator::present(const Context& context) { - // TEST: upload input images - // TODO: implement test, lol - // TEST END - Core::CommandBuffer cmdBuffer(context.device, context.cmdPool); cmdBuffer.begin(); @@ -117,12 +114,6 @@ void Generator::present(const Context& context) { cmdBuffer.end(); - // TEST: temporary fence submit logic - Core::Fence fence(context.device); - cmdBuffer.submit(context.device.getComputeQueue(), fence); - assert(fence.wait(context.device)); - // TEST END - fc++; } From 08646483a8e9341f32a0643df2b82a652670dd03 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 08:21:41 +0200 Subject: [PATCH 039/253] make it a library --- CMakeLists.txt | 5 +- include/context.hpp | 76 +++++++++++++ include/core/buffer.hpp | 8 +- include/core/commandbuffer.hpp | 4 +- include/core/commandpool.hpp | 4 +- include/core/descriptorpool.hpp | 4 +- include/core/descriptorset.hpp | 10 +- include/{ => core}/device.hpp | 8 +- include/core/fence.hpp | 8 +- include/core/image.hpp | 4 +- include/{ => core}/instance.hpp | 2 +- include/core/pipeline.hpp | 4 +- include/core/sampler.hpp | 4 +- include/core/semaphore.hpp | 8 +- include/core/shadermodule.hpp | 4 +- include/lsfg.hpp | 128 --------------------- include/shaderchains/alpha.hpp | 4 +- include/shaderchains/beta.hpp | 4 +- include/shaderchains/delta.hpp | 4 +- include/shaderchains/downsample.hpp | 4 +- include/shaderchains/epsilon.hpp | 4 +- include/shaderchains/extract.hpp | 4 +- include/shaderchains/gamma.hpp | 4 +- include/shaderchains/magic.hpp | 4 +- include/shaderchains/merge.hpp | 4 +- include/shaderchains/zeta.hpp | 4 +- include/utils.hpp | 9 +- public/lsfg.hpp | 79 +++++++++++++ src/context.cpp | 111 +++++++++++++++++++ src/core/buffer.cpp | 2 +- src/core/commandbuffer.cpp | 2 +- src/core/commandpool.cpp | 2 +- src/core/descriptorpool.cpp | 2 +- src/core/descriptorset.cpp | 4 +- src/{ => core}/device.cpp | 4 +- src/core/fence.cpp | 6 +- src/core/image.cpp | 2 +- src/{ => core}/instance.cpp | 4 +- src/core/pipeline.cpp | 2 +- src/core/sampler.cpp | 2 +- src/core/semaphore.cpp | 6 +- src/core/shadermodule.cpp | 2 +- src/lsfg.cpp | 166 +++++++++------------------- src/main.cpp | 47 -------- src/shaderchains/alpha.cpp | 2 +- src/shaderchains/beta.cpp | 2 +- src/shaderchains/delta.cpp | 2 +- src/shaderchains/downsample.cpp | 2 +- src/shaderchains/epsilon.cpp | 2 +- src/shaderchains/extract.cpp | 2 +- src/shaderchains/gamma.cpp | 2 +- src/shaderchains/magic.cpp | 2 +- src/shaderchains/merge.cpp | 2 +- src/shaderchains/zeta.cpp | 2 +- src/utils.cpp | 6 +- 55 files changed, 417 insertions(+), 382 deletions(-) create mode 100644 include/context.hpp rename include/{ => core}/device.hpp (89%) rename include/{ => core}/instance.hpp (97%) delete mode 100644 include/lsfg.hpp create mode 100644 public/lsfg.hpp create mode 100644 src/context.cpp rename src/{ => core}/device.cpp (98%) rename src/{ => core}/instance.cpp (96%) delete mode 100644 src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f01bed..5aa0e7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,10 +22,11 @@ file(GLOB SOURCES "src/*.cpp" ) -add_executable(lsfg-vk-base ${SOURCES}) +add_library(lsfg-vk-base SHARED ${SOURCES}) target_include_directories(lsfg-vk-base - PUBLIC include) + PRIVATE include + PUBLIC public) target_link_libraries(lsfg-vk-base PUBLIC vulkan) target_compile_options(lsfg-vk-base PRIVATE diff --git a/include/context.hpp b/include/context.hpp new file mode 100644 index 0000000..15768f3 --- /dev/null +++ b/include/context.hpp @@ -0,0 +1,76 @@ +#ifndef CONTEXT_HPP +#define CONTEXT_HPP + +#include "core/commandpool.hpp" +#include "core/descriptorpool.hpp" +#include "core/image.hpp" +#include "shaderchains/alpha.hpp" +#include "shaderchains/beta.hpp" +#include "shaderchains/delta.hpp" +#include "shaderchains/downsample.hpp" +#include "shaderchains/epsilon.hpp" +#include "shaderchains/extract.hpp" +#include "shaderchains/gamma.hpp" +#include "shaderchains/magic.hpp" +#include "shaderchains/merge.hpp" +#include "shaderchains/zeta.hpp" + +namespace LSFG { + + /// + /// LSFG context. + /// + class Context { + public: + /// + /// Create a generator instance. + /// + /// @param device The Vulkan device to use. + /// @param width Width of the input images. + /// @param height Height of the input images. + /// @param in0 File descriptor for the first input image. + /// @param in1 File descriptor for the second input image. + /// + /// @throws LSFG::vulkan_error if the generator fails to initialize. + /// + Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1); + + /// + /// Schedule the next generation. + /// + /// @param device The Vulkan device to use. + /// @param inSem Semaphore to wait on before starting the generation. + /// @param outSem Semaphore to signal when the generation is complete. + /// + /// @throws LSFG::vulkan_error if the generator fails to present. + /// + void present(const Core::Device& device, int inSem, int outSem); + + // Trivially copyable, moveable and destructible + Context(const Context&) = default; + Context(Context&&) = default; + Context& operator=(const Context&) = default; + Context& operator=(Context&&) = default; + ~Context() = default; + private: + Core::DescriptorPool descPool; + Core::CommandPool cmdPool; + + Core::Image inImg_0, inImg_1; // inImg_0 is next (inImg_1 prev) when fc % 2 == 0 + uint64_t fc{0}; + + Shaderchains::Downsample downsampleChain; + std::array alphaChains; + Shaderchains::Beta betaChain; + std::array gammaChains; + std::array magicChains; + std::array deltaChains; + std::array epsilonChains; + std::array zetaChains; + std::array extractChains; + Shaderchains::Merge mergeChain; + }; + +} + +#endif // CONTEXT_HPP diff --git a/include/core/buffer.hpp b/include/core/buffer.hpp index 2e283f1..a2c369e 100644 --- a/include/core/buffer.hpp +++ b/include/core/buffer.hpp @@ -1,7 +1,7 @@ #ifndef BUFFER_HPP #define BUFFER_HPP -#include "device.hpp" +#include "core/device.hpp" #include @@ -28,7 +28,7 @@ namespace LSFG::Core { /// @throws LSFG::vulkan_error if object creation fails. /// template - Buffer(const Device& device, const T& data, VkBufferUsageFlags usage) + Buffer(const Core::Device& device, const T& data, VkBufferUsageFlags usage) : size(sizeof(T)) { construct(device, reinterpret_cast(&data), usage); } @@ -43,7 +43,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - Buffer(const Device& device, const void* data, size_t size, VkBufferUsageFlags usage) + Buffer(const Core::Device& device, const void* data, size_t size, VkBufferUsageFlags usage) : size(size) { construct(device, data, usage); } @@ -60,7 +60,7 @@ namespace LSFG::Core { Buffer& operator=(Buffer&&) noexcept = default; ~Buffer() = default; private: - void construct(const Device& device, const void* data, VkBufferUsageFlags usage); + void construct(const Core::Device& device, const void* data, VkBufferUsageFlags usage); std::shared_ptr buffer; std::shared_ptr memory; diff --git a/include/core/commandbuffer.hpp b/include/core/commandbuffer.hpp index aaed19e..d9395db 100644 --- a/include/core/commandbuffer.hpp +++ b/include/core/commandbuffer.hpp @@ -4,7 +4,7 @@ #include "core/commandpool.hpp" #include "core/fence.hpp" #include "core/semaphore.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -45,7 +45,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - CommandBuffer(const Device& device, const CommandPool& pool); + CommandBuffer(const Core::Device& device, const CommandPool& pool); /// /// Begin recording commands in the command buffer. diff --git a/include/core/commandpool.hpp b/include/core/commandpool.hpp index d444b4a..9946176 100644 --- a/include/core/commandpool.hpp +++ b/include/core/commandpool.hpp @@ -1,7 +1,7 @@ #ifndef COMMANDPOOL_HPP #define COMMANDPOOL_HPP -#include "device.hpp" +#include "core/device.hpp" #include @@ -25,7 +25,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - CommandPool(const Device& device); + CommandPool(const Core::Device& device); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->commandPool; } diff --git a/include/core/descriptorpool.hpp b/include/core/descriptorpool.hpp index 5ffac10..76ac03c 100644 --- a/include/core/descriptorpool.hpp +++ b/include/core/descriptorpool.hpp @@ -1,7 +1,7 @@ #ifndef DESCRIPTORPOOL_HPP #define DESCRIPTORPOOL_HPP -#include "device.hpp" +#include "core/device.hpp" #include @@ -25,7 +25,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - DescriptorPool(const Device& device); + DescriptorPool(const Core::Device& device); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->descriptorPool; } diff --git a/include/core/descriptorset.hpp b/include/core/descriptorset.hpp index 79b2b91..cdd15ad 100644 --- a/include/core/descriptorset.hpp +++ b/include/core/descriptorset.hpp @@ -8,7 +8,7 @@ #include "core/pipeline.hpp" #include "core/sampler.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -36,7 +36,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - DescriptorSet(const Device& device, + DescriptorSet(const Core::Device& device, const DescriptorPool& pool, const ShaderModule& shaderModule); /// @@ -44,7 +44,7 @@ namespace LSFG::Core { /// /// @param device Vulkan device /// - [[nodiscard]] DescriptorSetUpdateBuilder update(const Device& device) const; + [[nodiscard]] DescriptorSetUpdateBuilder update(const Core::Device& device) const; /// /// Bind a descriptor set to a command buffer. @@ -110,9 +110,9 @@ namespace LSFG::Core { void build(); private: const DescriptorSet* descriptorSet; - const Device* device; + const Core::Device* device; - DescriptorSetUpdateBuilder(const DescriptorSet& descriptorSet, const Device& device) + DescriptorSetUpdateBuilder(const DescriptorSet& descriptorSet, const Core::Device& device) : descriptorSet(&descriptorSet), device(&device) {} std::vector entries; diff --git a/include/device.hpp b/include/core/device.hpp similarity index 89% rename from include/device.hpp rename to include/core/device.hpp index 25d2702..2916e86 100644 --- a/include/device.hpp +++ b/include/core/device.hpp @@ -1,14 +1,14 @@ #ifndef DEVICE_HPP #define DEVICE_HPP -#include "instance.hpp" +#include "core/instance.hpp" #include #include #include -namespace LSFG { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan device. @@ -36,8 +36,8 @@ namespace LSFG { [[nodiscard]] VkQueue getComputeQueue() const { return this->computeQueue; } // Trivially copyable, moveable and destructible - Device(const Device&) noexcept = default; - Device& operator=(const Device&) noexcept = default; + Device(const Core::Device&) noexcept = default; + Device& operator=(const Core::Device&) noexcept = default; Device(Device&&) noexcept = default; Device& operator=(Device&&) noexcept = default; ~Device() = default; diff --git a/include/core/fence.hpp b/include/core/fence.hpp index 3d7df5f..6a50eb1 100644 --- a/include/core/fence.hpp +++ b/include/core/fence.hpp @@ -1,7 +1,7 @@ #ifndef FENCE_HPP #define FENCE_HPP -#include "device.hpp" +#include "core/device.hpp" #include @@ -25,7 +25,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - Fence(const Device& device); + Fence(const Core::Device& device); /// /// Reset the fence to an unsignaled state. @@ -34,7 +34,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if resetting fails. /// - void reset(const Device& device) const; + void reset(const Core::Device& device) const; /// /// Wait for the fence @@ -45,7 +45,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if waiting fails. /// - [[nodiscard]] bool wait(const Device& device, uint64_t timeout = UINT64_MAX) const; + [[nodiscard]] bool wait(const Core::Device& device, uint64_t timeout = UINT64_MAX) const; /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->fence; } diff --git a/include/core/image.hpp b/include/core/image.hpp index 2b016dc..64dd03c 100644 --- a/include/core/image.hpp +++ b/include/core/image.hpp @@ -1,7 +1,7 @@ #ifndef IMAGE_HPP #define IMAGE_HPP -#include "device.hpp" +#include "core/device.hpp" #include @@ -29,7 +29,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - Image(const Device& device, VkExtent2D extent, VkFormat format, + Image(const Core::Device& device, VkExtent2D extent, VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspectFlags); /// Get the Vulkan handle. diff --git a/include/instance.hpp b/include/core/instance.hpp similarity index 97% rename from include/instance.hpp rename to include/core/instance.hpp index c957f72..3fb7d26 100644 --- a/include/instance.hpp +++ b/include/core/instance.hpp @@ -5,7 +5,7 @@ #include -namespace LSFG { +namespace LSFG::Core { /// /// C++ wrapper class for a Vulkan instance. diff --git a/include/core/pipeline.hpp b/include/core/pipeline.hpp index 812e76a..6e2f981 100644 --- a/include/core/pipeline.hpp +++ b/include/core/pipeline.hpp @@ -3,7 +3,7 @@ #include "core/commandbuffer.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -28,7 +28,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - Pipeline(const Device& device, const ShaderModule& shader); + Pipeline(const Core::Device& device, const ShaderModule& shader); /// /// Bind the pipeline to a command buffer. diff --git a/include/core/sampler.hpp b/include/core/sampler.hpp index 325d783..7890606 100644 --- a/include/core/sampler.hpp +++ b/include/core/sampler.hpp @@ -1,7 +1,7 @@ #ifndef SAMPLER_HPP #define SAMPLER_HPP -#include "device.hpp" +#include "core/device.hpp" #include @@ -26,7 +26,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - Sampler(const Device& device, VkSamplerAddressMode mode); + Sampler(const Core::Device& device, VkSamplerAddressMode mode); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->sampler; } diff --git a/include/core/semaphore.hpp b/include/core/semaphore.hpp index cd2ae53..b91fdb4 100644 --- a/include/core/semaphore.hpp +++ b/include/core/semaphore.hpp @@ -1,7 +1,7 @@ #ifndef SEMAPHORE_HPP #define SEMAPHORE_HPP -#include "device.hpp" +#include "core/device.hpp" #include @@ -27,7 +27,7 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - Semaphore(const Device& device, std::optional initial = std::nullopt); + Semaphore(const Core::Device& device, std::optional initial = std::nullopt); /// /// Signal the semaphore to a specific value. @@ -38,7 +38,7 @@ namespace LSFG::Core { /// @throws std::logic_error if the semaphore is not a timeline semaphore. /// @throws LSFG::vulkan_error if signaling fails. /// - void signal(const Device& device, uint64_t value) const; + void signal(const Core::Device& device, uint64_t value) const; /// /// Wait for the semaphore to reach a specific value. @@ -51,7 +51,7 @@ namespace LSFG::Core { /// @throws std::logic_error if the semaphore is not a timeline semaphore. /// @throws LSFG::vulkan_error if waiting fails. /// - [[nodiscard]] bool wait(const Device& device, uint64_t value, uint64_t timeout = UINT64_MAX) const; + [[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; } diff --git a/include/core/shadermodule.hpp b/include/core/shadermodule.hpp index 4a06541..81d8c10 100644 --- a/include/core/shadermodule.hpp +++ b/include/core/shadermodule.hpp @@ -1,7 +1,7 @@ #ifndef SHADERMODULE_HPP #define SHADERMODULE_HPP -#include "device.hpp" +#include "core/device.hpp" #include @@ -31,7 +31,7 @@ namespace LSFG::Core { /// @throws std::system_error if the shader file cannot be opened or read. /// @throws LSFG::vulkan_error if object creation fails. /// - ShaderModule(const Device& device, const std::string& path, + ShaderModule(const Core::Device& device, const std::string& path, const std::vector>& descriptorTypes); /// Get the Vulkan handle. diff --git a/include/lsfg.hpp b/include/lsfg.hpp deleted file mode 100644 index ea3459b..0000000 --- a/include/lsfg.hpp +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef LSFG_HPP -#define LSFG_HPP - -#include "core/commandpool.hpp" -#include "core/descriptorpool.hpp" -#include "core/image.hpp" -#include "device.hpp" -#include "instance.hpp" -#include "shaderchains/alpha.hpp" -#include "shaderchains/beta.hpp" -#include "shaderchains/delta.hpp" -#include "shaderchains/downsample.hpp" -#include "shaderchains/epsilon.hpp" -#include "shaderchains/extract.hpp" -#include "shaderchains/gamma.hpp" -#include "shaderchains/magic.hpp" -#include "shaderchains/merge.hpp" -#include "shaderchains/zeta.hpp" -#include "utils.hpp" - -namespace LSFG { - - class Generator; - - /// LSFG context. - class Context { - friend class Generator; // FIXME: getters, I'm lazy - public: - /// - /// Initialize the LSFG Vulkan instance. - /// - /// @throws LSFG::vulkan_error if the Vulkan objects cannot be created. - /// - Context() { Globals::initializeGlobals(device); } // FIXME: no need for globals - - /// - /// Create a generator instance. - /// - /// @throws LSFG::vulkan_error if the generator cannot be created. - /// - const Generator& create(); - - /// - /// Present a generator instance. - /// - /// @throws LSFG::vulkan_error if the generator fails to present. - /// - void present(const Generator& gen); - - /// Trivial copyable, moveable and destructible - Context(const Context&) = default; - Context& operator=(const Context&) = default; - Context(Context&&) = default; - Context& operator=(Context&&) = default; - ~Context() { Globals::uninitializeGlobals(); } - private: - Instance instance; - Device device{instance}; - Core::DescriptorPool descPool{device}; - Core::CommandPool cmdPool{device}; - }; - - /// Per-swapchain instance of LSFG. - class Generator { - public: - /// - /// Create a generator instance. - /// - /// @param context The LSFG context to use. - /// - Generator(const Context& context); - - /// - /// Present. - /// - /// @throws LSFG::vulkan_error if the generator fails to present. - /// - void present(const Context& context); - - // Trivially copyable, moveable and destructible - Generator(const Generator&) = default; - Generator(Generator&&) = default; - Generator& operator=(const Generator&) = default; - Generator& operator=(Generator&&) = default; - ~Generator() = default; - private: - Core::Image inImg_0, inImg_1; // inImg_0 is next (inImg_1 prev) when fc % 2 == 0 - uint64_t fc{0}; - - Shaderchains::Downsample downsampleChain; // FIXME: get rid of default constructors (+ core) - std::array alphaChains; - Shaderchains::Beta betaChain; - std::array gammaChains; - std::array magicChains; - std::array deltaChains; - std::array epsilonChains; - std::array zetaChains; - std::array extractChains; - Shaderchains::Merge mergeChain; - }; - - /// 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; - }; - -} - -#endif // LSFG_HPP diff --git a/include/shaderchains/alpha.hpp b/include/shaderchains/alpha.hpp index fb97e3f..a38e40a 100644 --- a/include/shaderchains/alpha.hpp +++ b/include/shaderchains/alpha.hpp @@ -7,7 +7,7 @@ #include "core/image.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -31,7 +31,7 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Alpha(const Device& device, const Core::DescriptorPool& pool, + Alpha(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg); /// diff --git a/include/shaderchains/beta.hpp b/include/shaderchains/beta.hpp index 3420ae4..1effbc5 100644 --- a/include/shaderchains/beta.hpp +++ b/include/shaderchains/beta.hpp @@ -8,7 +8,7 @@ #include "core/image.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -35,7 +35,7 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Beta(const Device& device, const Core::DescriptorPool& pool, + Beta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs_0, std::array inImgs_1, std::array inImgs_2); diff --git a/include/shaderchains/delta.hpp b/include/shaderchains/delta.hpp index cd67869..33f8fb9 100644 --- a/include/shaderchains/delta.hpp +++ b/include/shaderchains/delta.hpp @@ -8,7 +8,7 @@ #include "core/image.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -34,7 +34,7 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Delta(const Device& device, const Core::DescriptorPool& pool, + Delta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs, std::optional optImg); diff --git a/include/shaderchains/downsample.hpp b/include/shaderchains/downsample.hpp index fd7e280..78a9f78 100644 --- a/include/shaderchains/downsample.hpp +++ b/include/shaderchains/downsample.hpp @@ -8,7 +8,7 @@ #include "core/image.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -33,7 +33,7 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Downsample(const Device& device, const Core::DescriptorPool& pool, + Downsample(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg_0, Core::Image inImg_1); /// diff --git a/include/shaderchains/epsilon.hpp b/include/shaderchains/epsilon.hpp index 97f2d27..28e09d2 100644 --- a/include/shaderchains/epsilon.hpp +++ b/include/shaderchains/epsilon.hpp @@ -8,7 +8,7 @@ #include "core/image.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -35,7 +35,7 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Epsilon(const Device& device, const Core::DescriptorPool& pool, + Epsilon(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, std::optional optImg); diff --git a/include/shaderchains/extract.hpp b/include/shaderchains/extract.hpp index abf3fcc..a56a401 100644 --- a/include/shaderchains/extract.hpp +++ b/include/shaderchains/extract.hpp @@ -8,7 +8,7 @@ #include "core/image.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" namespace LSFG::Shaderchains { @@ -33,7 +33,7 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Extract(const Device& device, const Core::DescriptorPool& pool, + Extract(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg1, Core::Image inImg2, VkExtent2D outExtent); diff --git a/include/shaderchains/gamma.hpp b/include/shaderchains/gamma.hpp index a2767d8..9aca275 100644 --- a/include/shaderchains/gamma.hpp +++ b/include/shaderchains/gamma.hpp @@ -8,7 +8,7 @@ #include "core/image.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -41,7 +41,7 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Gamma(const Device& device, const Core::DescriptorPool& pool, + Gamma(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1_0, std::array inImgs1_1, std::array inImgs1_2, diff --git a/include/shaderchains/magic.hpp b/include/shaderchains/magic.hpp index f1b287a..7a0b33f 100644 --- a/include/shaderchains/magic.hpp +++ b/include/shaderchains/magic.hpp @@ -8,7 +8,7 @@ #include "core/image.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -38,7 +38,7 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Magic(const Device& device, const Core::DescriptorPool& pool, + Magic(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1_0, std::array inImgs1_1, std::array inImgs1_2, diff --git a/include/shaderchains/merge.hpp b/include/shaderchains/merge.hpp index 679522d..3af00cf 100644 --- a/include/shaderchains/merge.hpp +++ b/include/shaderchains/merge.hpp @@ -8,7 +8,7 @@ #include "core/image.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -37,7 +37,7 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Merge(const Device& device, const Core::DescriptorPool& pool, + Merge(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg1, Core::Image inImg2, Core::Image inImg3, diff --git a/include/shaderchains/zeta.hpp b/include/shaderchains/zeta.hpp index 56eeef4..372c0ae 100644 --- a/include/shaderchains/zeta.hpp +++ b/include/shaderchains/zeta.hpp @@ -8,7 +8,7 @@ #include "core/image.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "device.hpp" +#include "core/device.hpp" #include @@ -35,7 +35,7 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Zeta(const Device& device, const Core::DescriptorPool& pool, + Zeta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, Core::Image inImg3); diff --git a/include/utils.hpp b/include/utils.hpp index e6f7e64..15c6ce1 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -4,9 +4,8 @@ #include "core/commandbuffer.hpp" #include "core/image.hpp" #include "core/sampler.hpp" -#include "device.hpp" +#include "core/device.hpp" -#include #include #include @@ -68,7 +67,7 @@ namespace LSFG::Utils { /// @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 Device& device, + void uploadImage(const Core::Device& device, const Core::CommandPool& commandPool, Core::Image& image, const std::string& path); @@ -81,7 +80,7 @@ namespace LSFG::Utils { /// /// @throws LSFG::vulkan_error If the Vulkan image cannot be cleared. /// - void clearImage(const Device& device, Core::Image& image, bool white = false); + void clearImage(const Core::Device& device, Core::Image& image, bool white = false); } @@ -111,7 +110,7 @@ namespace LSFG::Globals { static_assert(sizeof(FgBuffer) == 48, "FgBuffer must be 48 bytes in size."); /// Initialize global resources. - void initializeGlobals(const Device& device); + void initializeGlobals(const Core::Device& device); /// Uninitialize global resources. void uninitializeGlobals() noexcept; diff --git a/public/lsfg.hpp b/public/lsfg.hpp new file mode 100644 index 0000000..d9a25bf --- /dev/null +++ b/public/lsfg.hpp @@ -0,0 +1,79 @@ +#ifndef PUBLIC_LSFG_HPP +#define PUBLIC_LSFG_HPP + +#include + +#include + +namespace LSFG { + + /// + /// Initialize the LSFG library. + /// + /// @throws LSFG::vulkan_error if Vulkan objects fail to initialize. + /// + void initialize(); + + /// + /// Create a new LSFG context on a swapchain. + /// + /// @param width Width of the input images. + /// @param height Height of the input images. + /// @param in0 File descriptor for the first input image. + /// @param in1 File descriptor for the second input image. + /// @return A unique identifier for the created context. + /// + /// @throws LSFG::vulkan_error if the context cannot be created. + /// + int32_t createContext(uint32_t width, uint32_t height, int in0, int in1); + + /// + /// Present a context. + /// + /// @param id Unique identifier of the context to present. + /// @param inSem Semaphore to wait on before starting the generation. + /// @param outSem Semaphore to signal when the generation is complete. + /// + /// @throws LSFG::vulkan_error if the context cannot be presented. + /// + void presentContext(int32_t id, int inSem, 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(); + + /// 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; + }; + +} + +#endif // PUBLIC_LSFG_HPP diff --git a/src/context.cpp b/src/context.cpp new file mode 100644 index 0000000..0afd34c --- /dev/null +++ b/src/context.cpp @@ -0,0 +1,111 @@ +#include "context.hpp" +#include "lsfg.hpp" + +#include +#include + +using namespace LSFG; + +Context::Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1) { + // create pools + this->descPool = Core::DescriptorPool(device); + this->cmdPool = Core::CommandPool(device); + + // create shader chains + this->downsampleChain = Shaderchains::Downsample(device, this->descPool, + this->inImg_0, this->inImg_1); + for (size_t i = 0; i < 7; i++) + this->alphaChains.at(i) = Shaderchains::Alpha(device, this->descPool, + this->downsampleChain.getOutImages().at(i)); + this->betaChain = Shaderchains::Beta(device, this->descPool, + this->alphaChains.at(0).getOutImages0(), + this->alphaChains.at(0).getOutImages1(), + this->alphaChains.at(0).getOutImages2()); + for (size_t i = 0; i < 7; i++) { + if (i < 4) { + this->gammaChains.at(i) = Shaderchains::Gamma(device, this->descPool, + this->alphaChains.at(6 - i).getOutImages0(), + this->alphaChains.at(6 - i).getOutImages1(), + this->alphaChains.at(6 - i).getOutImages2(), + this->betaChain.getOutImages().at(std::min(5UL, 6 - i)), + i == 0 ? std::nullopt + : std::optional{this->gammaChains.at(i - 1).getOutImage2()}, + i == 0 ? std::nullopt + : std::optional{this->gammaChains.at(i - 1).getOutImage1()}, + this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() + ); + } else { + this->magicChains.at(i - 4) = Shaderchains::Magic(device, this->descPool, + this->alphaChains.at(6 - i).getOutImages0(), + this->alphaChains.at(6 - i).getOutImages1(), + this->alphaChains.at(6 - i).getOutImages2(), + i == 4 ? this->gammaChains.at(i - 1).getOutImage2() + : this->extractChains.at(i - 5).getOutImage(), + i == 4 ? this->gammaChains.at(i - 1).getOutImage1() + : this->zetaChains.at(i - 5).getOutImage(), + i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()} + ); + this->deltaChains.at(i - 4) = Shaderchains::Delta(device, this->descPool, + this->magicChains.at(i - 4).getOutImages1(), + i == 4 ? std::nullopt + : std::optional{this->deltaChains.at(i - 5).getOutImage()} + ); + this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(device, this->descPool, + this->magicChains.at(i - 4).getOutImages2(), + this->betaChain.getOutImages().at(6 - i), + i == 4 ? std::nullopt + : std::optional{this->epsilonChains.at(i - 5).getOutImage()} + ); + this->zetaChains.at(i - 4) = Shaderchains::Zeta(device, this->descPool, + this->magicChains.at(i - 4).getOutImages3(), + i == 4 ? this->gammaChains.at(i - 1).getOutImage1() + : this->zetaChains.at(i - 5).getOutImage(), + this->betaChain.getOutImages().at(6 - i) + ); + if (i >= 6) + continue; // no extract for i >= 6 + this->extractChains.at(i - 4) = Shaderchains::Extract(device, this->descPool, + this->zetaChains.at(i - 4).getOutImage(), + this->epsilonChains.at(i - 4).getOutImage(), + this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() + ); + } + } + this->mergeChain = Shaderchains::Merge(device, this->descPool, + this->inImg_1, + this->inImg_0, + this->zetaChains.at(2).getOutImage(), + this->epsilonChains.at(2).getOutImage(), + this->deltaChains.at(2).getOutImage() + ); +} + +void Context::present(const Core::Device& device, int inSem, int outSem) { + Core::CommandBuffer cmdBuffer(device, this->cmdPool); + cmdBuffer.begin(); + + this->downsampleChain.Dispatch(cmdBuffer, fc); + for (size_t i = 0; i < 7; i++) + this->alphaChains.at(6 - i).Dispatch(cmdBuffer, fc); + this->betaChain.Dispatch(cmdBuffer, fc); + for (size_t i = 0; i < 4; i++) + this->gammaChains.at(i).Dispatch(cmdBuffer, fc); + for (size_t i = 0; i < 3; i++) { + this->magicChains.at(i).Dispatch(cmdBuffer, fc); + this->deltaChains.at(i).Dispatch(cmdBuffer); + this->epsilonChains.at(i).Dispatch(cmdBuffer); + this->zetaChains.at(i).Dispatch(cmdBuffer); + if (i < 2) + this->extractChains.at(i).Dispatch(cmdBuffer); + } + this->mergeChain.Dispatch(cmdBuffer, fc); + + cmdBuffer.end(); + + fc++; +} + +vulkan_error::vulkan_error(VkResult result, const std::string& message) + : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} + +vulkan_error::~vulkan_error() noexcept = default; diff --git a/src/core/buffer.cpp b/src/core/buffer.cpp index b446cd4..6a5e225 100644 --- a/src/core/buffer.cpp +++ b/src/core/buffer.cpp @@ -6,7 +6,7 @@ using namespace LSFG::Core; -void Buffer::construct(const Device& device, const void* data, VkBufferUsageFlags usage) { +void Buffer::construct(const Core::Device& device, const void* data, VkBufferUsageFlags usage) { // create buffer const VkBufferCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, diff --git a/src/core/commandbuffer.cpp b/src/core/commandbuffer.cpp index 6c7d99d..c556127 100644 --- a/src/core/commandbuffer.cpp +++ b/src/core/commandbuffer.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Core; -CommandBuffer::CommandBuffer(const Device& device, const CommandPool& pool) { +CommandBuffer::CommandBuffer(const Core::Device& device, const CommandPool& pool) { // create command buffer const VkCommandBufferAllocateInfo desc{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, diff --git a/src/core/commandpool.cpp b/src/core/commandpool.cpp index efd2098..f1a6030 100644 --- a/src/core/commandpool.cpp +++ b/src/core/commandpool.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Core; -CommandPool::CommandPool(const Device& device) { +CommandPool::CommandPool(const Core::Device& device) { // create command pool const VkCommandPoolCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, diff --git a/src/core/descriptorpool.cpp b/src/core/descriptorpool.cpp index 1761549..75eb627 100644 --- a/src/core/descriptorpool.cpp +++ b/src/core/descriptorpool.cpp @@ -5,7 +5,7 @@ using namespace LSFG::Core; -DescriptorPool::DescriptorPool(const Device& device) { +DescriptorPool::DescriptorPool(const Core::Device& device) { // create descriptor pool const std::array pools{{ // arbitrary limits { .type = VK_DESCRIPTOR_TYPE_SAMPLER, .descriptorCount = 4096 }, diff --git a/src/core/descriptorset.cpp b/src/core/descriptorset.cpp index cb1df1d..c39454e 100644 --- a/src/core/descriptorset.cpp +++ b/src/core/descriptorset.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Core; -DescriptorSet::DescriptorSet(const Device& device, +DescriptorSet::DescriptorSet(const Core::Device& device, const DescriptorPool& pool, const ShaderModule& shaderModule) { // create descriptor set VkDescriptorSetLayout layout = shaderModule.getLayout(); @@ -27,7 +27,7 @@ DescriptorSet::DescriptorSet(const Device& device, ); } -DescriptorSetUpdateBuilder DescriptorSet::update(const Device& device) const { +DescriptorSetUpdateBuilder DescriptorSet::update(const Core::Device& device) const { return { *this, device }; } diff --git a/src/device.cpp b/src/core/device.cpp similarity index 98% rename from src/device.cpp rename to src/core/device.cpp index bbcd4ff..ef7788b 100644 --- a/src/device.cpp +++ b/src/core/device.cpp @@ -1,10 +1,10 @@ -#include "device.hpp" +#include "core/device.hpp" #include "lsfg.hpp" #include #include -using namespace LSFG; +using namespace LSFG::Core; const std::vector requiredExtensions = { "VK_KHR_external_memory_fd", diff --git a/src/core/fence.cpp b/src/core/fence.cpp index f7a12c5..b543df0 100644 --- a/src/core/fence.cpp +++ b/src/core/fence.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Core; -Fence::Fence(const Device& device) { +Fence::Fence(const Core::Device& device) { // create fence const VkFenceCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO @@ -22,14 +22,14 @@ Fence::Fence(const Device& device) { ); } -void Fence::reset(const Device& device) const { +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 Device& device, uint64_t timeout) const { +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) diff --git a/src/core/image.cpp b/src/core/image.cpp index a24ff85..c190bb1 100644 --- a/src/core/image.cpp +++ b/src/core/image.cpp @@ -5,7 +5,7 @@ using namespace LSFG::Core; -Image::Image(const Device& device, VkExtent2D extent, VkFormat format, +Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspectFlags) : extent(extent), format(format), aspectFlags(aspectFlags) { // create image diff --git a/src/instance.cpp b/src/core/instance.cpp similarity index 96% rename from src/instance.cpp rename to src/core/instance.cpp index a97de0f..83a7a24 100644 --- a/src/instance.cpp +++ b/src/core/instance.cpp @@ -1,9 +1,9 @@ -#include "instance.hpp" +#include "core/instance.hpp" #include "lsfg.hpp" #include -using namespace LSFG; +using namespace LSFG::Core; const std::vector requiredExtensions = { diff --git a/src/core/pipeline.cpp b/src/core/pipeline.cpp index 4fc8fa4..9b2986f 100644 --- a/src/core/pipeline.cpp +++ b/src/core/pipeline.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Core; -Pipeline::Pipeline(const Device& device, const ShaderModule& shader) { +Pipeline::Pipeline(const Core::Device& device, const ShaderModule& shader) { // create pipeline layout VkDescriptorSetLayout shaderLayout = shader.getLayout(); const VkPipelineLayoutCreateInfo layoutDesc{ diff --git a/src/core/sampler.cpp b/src/core/sampler.cpp index 768d0c8..6965145 100644 --- a/src/core/sampler.cpp +++ b/src/core/sampler.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Core; -Sampler::Sampler(const Device& device, VkSamplerAddressMode mode) { +Sampler::Sampler(const Core::Device& device, VkSamplerAddressMode mode) { // create sampler const VkSamplerCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, diff --git a/src/core/semaphore.cpp b/src/core/semaphore.cpp index 20599cf..d2aee2a 100644 --- a/src/core/semaphore.cpp +++ b/src/core/semaphore.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Core; -Semaphore::Semaphore(const Device& device, std::optional initial) { +Semaphore::Semaphore(const Core::Device& device, std::optional initial) { // create semaphore const VkSemaphoreTypeCreateInfo typeInfo{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, @@ -29,7 +29,7 @@ Semaphore::Semaphore(const Device& device, std::optional initial) { ); } -void Semaphore::signal(const Device& device, uint64_t value) const { +void Semaphore::signal(const Core::Device& device, uint64_t value) const { if (!this->isTimeline) throw std::logic_error("Invalid timeline semaphore"); @@ -43,7 +43,7 @@ void Semaphore::signal(const Device& device, uint64_t value) const { throw LSFG::vulkan_error(res, "Unable to signal semaphore"); } -bool Semaphore::wait(const Device& device, uint64_t value, uint64_t timeout) const { +bool Semaphore::wait(const Core::Device& device, uint64_t value, uint64_t timeout) const { if (!this->isTimeline) throw std::logic_error("Invalid timeline semaphore"); diff --git a/src/core/shadermodule.cpp b/src/core/shadermodule.cpp index acf0f23..3c8e98f 100644 --- a/src/core/shadermodule.cpp +++ b/src/core/shadermodule.cpp @@ -5,7 +5,7 @@ using namespace LSFG::Core; -ShaderModule::ShaderModule(const Device& device, const std::string& path, +ShaderModule::ShaderModule(const Core::Device& device, const std::string& path, const std::vector>& descriptorTypes) { // read shader bytecode std::ifstream file(path, std::ios::ate | std::ios::binary); diff --git a/src/lsfg.cpp b/src/lsfg.cpp index 90b75c4..d434c24 100644 --- a/src/lsfg.cpp +++ b/src/lsfg.cpp @@ -1,123 +1,67 @@ #include "lsfg.hpp" -#include "core/commandbuffer.hpp" -#include "core/fence.hpp" -#include "core/image.hpp" -#include "utils.hpp" +#include "core/device.hpp" +#include "core/instance.hpp" +#include "context.hpp" -#include -#include +#include +#include +#include +#include using namespace LSFG; -Generator::Generator(const Context& context) { - // TEST: create temporal images - this->inImg_0 = Core::Image(context.device, - { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ); - this->inImg_1 = Core::Image(context.device, - { 2560, 1411 }, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT - ); - // TEST END - - // create shader chains - this->downsampleChain = Shaderchains::Downsample(context.device, context.descPool, - this->inImg_0, this->inImg_1); - for (size_t i = 0; i < 7; i++) - this->alphaChains.at(i) = Shaderchains::Alpha(context.device, context.descPool, - this->downsampleChain.getOutImages().at(i)); - this->betaChain = Shaderchains::Beta(context.device, context.descPool, - this->alphaChains.at(0).getOutImages0(), - this->alphaChains.at(0).getOutImages1(), - this->alphaChains.at(0).getOutImages2()); - for (size_t i = 0; i < 7; i++) { - if (i < 4) { - this->gammaChains.at(i) = Shaderchains::Gamma(context.device, context.descPool, - this->alphaChains.at(6 - i).getOutImages0(), - this->alphaChains.at(6 - i).getOutImages1(), - this->alphaChains.at(6 - i).getOutImages2(), - this->betaChain.getOutImages().at(std::min(5UL, 6 - i)), - i == 0 ? std::nullopt - : std::optional{this->gammaChains.at(i - 1).getOutImage2()}, - i == 0 ? std::nullopt - : std::optional{this->gammaChains.at(i - 1).getOutImage1()}, - this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() - ); - } else { - this->magicChains.at(i - 4) = Shaderchains::Magic(context.device, context.descPool, - this->alphaChains.at(6 - i).getOutImages0(), - this->alphaChains.at(6 - i).getOutImages1(), - this->alphaChains.at(6 - i).getOutImages2(), - i == 4 ? this->gammaChains.at(i - 1).getOutImage2() - : this->extractChains.at(i - 5).getOutImage(), - i == 4 ? this->gammaChains.at(i - 1).getOutImage1() - : this->zetaChains.at(i - 5).getOutImage(), - i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()} - ); - this->deltaChains.at(i - 4) = Shaderchains::Delta(context.device, context.descPool, - this->magicChains.at(i - 4).getOutImages1(), - i == 4 ? std::nullopt - : std::optional{this->deltaChains.at(i - 5).getOutImage()} - ); - this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(context.device, context.descPool, - this->magicChains.at(i - 4).getOutImages2(), - this->betaChain.getOutImages().at(6 - i), - i == 4 ? std::nullopt - : std::optional{this->epsilonChains.at(i - 5).getOutImage()} - ); - this->zetaChains.at(i - 4) = Shaderchains::Zeta(context.device, context.descPool, - this->magicChains.at(i - 4).getOutImages3(), - i == 4 ? this->gammaChains.at(i - 1).getOutImage1() - : this->zetaChains.at(i - 5).getOutImage(), - this->betaChain.getOutImages().at(6 - i) - ); - if (i >= 6) - continue; // no extract for i >= 6 - this->extractChains.at(i - 4) = Shaderchains::Extract(context.device, context.descPool, - this->zetaChains.at(i - 4).getOutImage(), - this->epsilonChains.at(i - 4).getOutImage(), - this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() - ); - } - } - this->mergeChain = Shaderchains::Merge(context.device, context.descPool, - this->inImg_1, - this->inImg_0, - this->zetaChains.at(2).getOutImage(), - this->epsilonChains.at(2).getOutImage(), - this->deltaChains.at(2).getOutImage() - ); +namespace { + std::optional instance; + std::optional device; + std::unordered_map contexts; } -void Generator::present(const Context& context) { - Core::CommandBuffer cmdBuffer(context.device, context.cmdPool); - cmdBuffer.begin(); +void LSFG::initialize() { + if (instance.has_value() || device.has_value()) + return; - this->downsampleChain.Dispatch(cmdBuffer, fc); - for (size_t i = 0; i < 7; i++) - this->alphaChains.at(6 - i).Dispatch(cmdBuffer, fc); - this->betaChain.Dispatch(cmdBuffer, fc); - for (size_t i = 0; i < 4; i++) - this->gammaChains.at(i).Dispatch(cmdBuffer, fc); - for (size_t i = 0; i < 3; i++) { - this->magicChains.at(i).Dispatch(cmdBuffer, fc); - this->deltaChains.at(i).Dispatch(cmdBuffer); - this->epsilonChains.at(i).Dispatch(cmdBuffer); - this->zetaChains.at(i).Dispatch(cmdBuffer); - if (i < 2) - this->extractChains.at(i).Dispatch(cmdBuffer); - } - this->mergeChain.Dispatch(cmdBuffer, fc); + instance.emplace(); + device.emplace(*instance); - cmdBuffer.end(); - - fc++; + std::srand(static_cast(std::time(nullptr))); } -vulkan_error::vulkan_error(VkResult result, const std::string& message) - : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} +int32_t LSFG::createContext(uint32_t width, uint32_t height, int in0, int in1) { + if (!instance.has_value() || !device.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); -vulkan_error::~vulkan_error() noexcept = default; + auto id = std::rand(); + contexts.emplace(id, Context(*device, width, height, in0, in1)); + return id; +} + +void LSFG::presentContext(int32_t id, int inSem, 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_DEVICE_LOST, "No such context"); + + Context& context = it->second; + context.present(*device, inSem, outSem); +} + +void LSFG::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"); + + contexts.erase(it); +} + +void LSFG::finalize() { + if (!instance.has_value() && !device.has_value()) + return; + + instance.reset(); + device.reset(); +} diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 83b0e26..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "lsfg.hpp" - -#include - -#include -#include -#include - -using namespace LSFG; - -int main() { - // attempt to load renderdoc - RENDERDOC_API_1_6_0* rdoc = nullptr; - if (void* mod_renderdoc = dlopen("/usr/lib/librenderdoc.so", RTLD_NOLOAD | RTLD_NOW)) { - std::cerr << "Found RenderDoc library, setting up frame capture." << '\n'; - - auto GetAPI = reinterpret_cast(dlsym(mod_renderdoc, "RENDERDOC_GetAPI")); - const int ret = GetAPI(eRENDERDOC_API_Version_1_6_0, reinterpret_cast(&rdoc)); - if (ret == 0) { - std::cerr << "Unable to initialize RenderDoc API. Is your RenderDoc up to date?" << '\n'; - rdoc = nullptr; - } - usleep(1000 * 100); // give renderdoc time to load - } - - // initialize test application - LSFG::Context context; - auto gen = LSFG::Generator(context); - - for (int i = 0; i < 3; i++) { - if (rdoc) - rdoc->StartFrameCapture(nullptr, nullptr); - - gen.present(context); - - if (rdoc) - rdoc->EndFrameCapture(nullptr, nullptr); - - // sleep 8 ms - usleep(8000); - } - - usleep(1000 * 100); - - std::cerr << "Application finished" << '\n'; - return 0; -} diff --git a/src/shaderchains/alpha.cpp b/src/shaderchains/alpha.cpp index ba7f4c0..52c55aa 100644 --- a/src/shaderchains/alpha.cpp +++ b/src/shaderchains/alpha.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Shaderchains; -Alpha::Alpha(const Device& device, const Core::DescriptorPool& pool, +Alpha::Alpha(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg) : inImg(std::move(inImg)) { this->shaderModules = {{ diff --git a/src/shaderchains/beta.cpp b/src/shaderchains/beta.cpp index 698aa34..c3d3a2e 100644 --- a/src/shaderchains/beta.cpp +++ b/src/shaderchains/beta.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Shaderchains; -Beta::Beta(const Device& device, const Core::DescriptorPool& pool, +Beta::Beta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs_0, std::array inImgs_1, std::array inImgs_2) diff --git a/src/shaderchains/delta.cpp b/src/shaderchains/delta.cpp index ca25a39..295ce97 100644 --- a/src/shaderchains/delta.cpp +++ b/src/shaderchains/delta.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Shaderchains; -Delta::Delta(const Device& device, const Core::DescriptorPool& pool, +Delta::Delta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs, std::optional optImg) : inImgs(std::move(inImgs)), diff --git a/src/shaderchains/downsample.cpp b/src/shaderchains/downsample.cpp index c60e8bc..7b6ac12 100644 --- a/src/shaderchains/downsample.cpp +++ b/src/shaderchains/downsample.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Shaderchains; -Downsample::Downsample(const Device& device, const Core::DescriptorPool& pool, +Downsample::Downsample(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg_0, Core::Image inImg_1) : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) { diff --git a/src/shaderchains/epsilon.cpp b/src/shaderchains/epsilon.cpp index f199d64..8ba9474 100644 --- a/src/shaderchains/epsilon.cpp +++ b/src/shaderchains/epsilon.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Shaderchains; -Epsilon::Epsilon(const Device& device, const Core::DescriptorPool& pool, +Epsilon::Epsilon(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, std::optional optImg) diff --git a/src/shaderchains/extract.cpp b/src/shaderchains/extract.cpp index 4f66938..ee1fb00 100644 --- a/src/shaderchains/extract.cpp +++ b/src/shaderchains/extract.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Shaderchains; -Extract::Extract(const Device& device, const Core::DescriptorPool& pool, +Extract::Extract(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg1, Core::Image inImg2, VkExtent2D outExtent) diff --git a/src/shaderchains/gamma.cpp b/src/shaderchains/gamma.cpp index cd1b33b..ad82f10 100644 --- a/src/shaderchains/gamma.cpp +++ b/src/shaderchains/gamma.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Shaderchains; -Gamma::Gamma(const Device& device, const Core::DescriptorPool& pool, +Gamma::Gamma(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1_0, std::array inImgs1_1, std::array inImgs1_2, diff --git a/src/shaderchains/magic.cpp b/src/shaderchains/magic.cpp index 3ebd895..450bc91 100644 --- a/src/shaderchains/magic.cpp +++ b/src/shaderchains/magic.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Shaderchains; -Magic::Magic(const Device& device, const Core::DescriptorPool& pool, +Magic::Magic(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1_0, std::array inImgs1_1, std::array inImgs1_2, diff --git a/src/shaderchains/merge.cpp b/src/shaderchains/merge.cpp index 25e8e45..43bd957 100644 --- a/src/shaderchains/merge.cpp +++ b/src/shaderchains/merge.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Shaderchains; -Merge::Merge(const Device& device, const Core::DescriptorPool& pool, +Merge::Merge(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg1, Core::Image inImg2, Core::Image inImg3, diff --git a/src/shaderchains/zeta.cpp b/src/shaderchains/zeta.cpp index cb2bdd6..8b04126 100644 --- a/src/shaderchains/zeta.cpp +++ b/src/shaderchains/zeta.cpp @@ -3,7 +3,7 @@ using namespace LSFG::Shaderchains; -Zeta::Zeta(const Device& device, const Core::DescriptorPool& pool, +Zeta::Zeta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, Core::Image inImg3) diff --git a/src/utils.cpp b/src/utils.cpp index 1e13fbc..60a4274 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -58,7 +58,7 @@ void BarrierBuilder::build() const { vkCmdPipelineBarrier2(this->commandBuffer->handle(), &dependencyInfo); } -void Utils::uploadImage(const Device& device, const Core::CommandPool& commandPool, +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); @@ -130,7 +130,7 @@ void Utils::uploadImage(const Device& device, const Core::CommandPool& commandPo throw LSFG::vulkan_error(VK_TIMEOUT, "Upload operation timed out"); } -void Utils::clearImage(const Device& device, Core::Image& image, bool white) { +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); @@ -180,7 +180,7 @@ Core::Sampler Globals::samplerClampBorder; Core::Sampler Globals::samplerClampEdge; Globals::FgBuffer Globals::fgBuffer; -void Globals::initializeGlobals(const Device& device) { +void Globals::initializeGlobals(const Core::Device& device) { // initialize global samplers samplerClampBorder = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); samplerClampEdge = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); From c42dcf3ed2762707b6c87cc98b5ca037d481848a Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 08:22:54 +0200 Subject: [PATCH 040/253] one more import --- src/lsfg.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lsfg.cpp b/src/lsfg.cpp index d434c24..25c1d38 100644 --- a/src/lsfg.cpp +++ b/src/lsfg.cpp @@ -3,7 +3,6 @@ #include "core/instance.hpp" #include "context.hpp" -#include #include #include #include From 2083b6c85ec5acbd3a6156186dd02b9135eb57fa Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 08:57:44 +0200 Subject: [PATCH 041/253] import semaphore and images --- include/core/image.hpp | 15 +++++ include/core/semaphore.hpp | 10 +++ src/context.cpp | 20 ++++++ src/core/device.cpp | 1 + src/core/image.cpp | 125 ++++++++++++++++++++++++++++++++++++- src/core/semaphore.cpp | 36 +++++++++++ 6 files changed, 206 insertions(+), 1 deletion(-) diff --git a/include/core/image.hpp b/include/core/image.hpp index 64dd03c..540e043 100644 --- a/include/core/image.hpp +++ b/include/core/image.hpp @@ -32,6 +32,21 @@ namespace LSFG::Core { Image(const Core::Device& device, VkExtent2D extent, VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspectFlags); + /// + /// 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. diff --git a/include/core/semaphore.hpp b/include/core/semaphore.hpp index b91fdb4..2060c33 100644 --- a/include/core/semaphore.hpp +++ b/include/core/semaphore.hpp @@ -29,6 +29,16 @@ namespace LSFG::Core { /// Semaphore(const Core::Device& device, std::optional initial = std::nullopt); + /// + /// Import a semaphore. + /// + /// @param device Vulkan device + /// @param fd File descriptor to import the semaphore from. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Semaphore(const Core::Device& device, int fd); + /// /// Signal the semaphore to a specific value. /// diff --git a/src/context.cpp b/src/context.cpp index 0afd34c..37cb651 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -1,4 +1,5 @@ #include "context.hpp" +#include "core/semaphore.hpp" #include "lsfg.hpp" #include @@ -7,6 +8,18 @@ using namespace LSFG; Context::Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1) { + // import images + this->inImg_0 = Core::Image(device, { width, height }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, + in0); + this->inImg_1 = Core::Image(device, { width, height }, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, + in1); + // create pools this->descPool = Core::DescriptorPool(device); this->cmdPool = Core::CommandPool(device); @@ -81,6 +94,9 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in } void Context::present(const Core::Device& device, int inSem, int outSem) { + Core::Semaphore inSemaphore(device, inSem); + Core::Semaphore outSemaphore(device, outSem); + Core::CommandBuffer cmdBuffer(device, this->cmdPool); cmdBuffer.begin(); @@ -102,6 +118,10 @@ void Context::present(const Core::Device& device, int inSem, int outSem) { cmdBuffer.end(); + cmdBuffer.submit(device.getComputeQueue(), std::nullopt, + { inSemaphore }, {}, + { outSemaphore }, {}); + fc++; } diff --git a/src/core/device.cpp b/src/core/device.cpp index ef7788b..6c147bb 100644 --- a/src/core/device.cpp +++ b/src/core/device.cpp @@ -8,6 +8,7 @@ using namespace LSFG::Core; const std::vector requiredExtensions = { "VK_KHR_external_memory_fd", + "VK_KHR_external_semaphore_fd", "VK_EXT_robustness2", }; diff --git a/src/core/image.cpp b/src/core/image.cpp index c190bb1..4affb03 100644 --- a/src/core/image.cpp +++ b/src/core/image.cpp @@ -109,5 +109,128 @@ Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format, vkDestroyImageView(dev, *imgView, nullptr); } ); - +} + +// shared memory constructor + +Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int fd) + : extent(extent), format(format), aspectFlags(aspectFlags) { + // create image + const VkExternalMemoryImageCreateInfo externalInfo{ + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR + }; + const VkImageCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &externalInfo, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = { + .width = extent.width, + .height = extent.height, + .depth = 1 + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VkImage imageHandle{}; + auto res = vkCreateImage(device.handle(), &desc, nullptr, &imageHandle); + if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create Vulkan image"); + + // find memory type + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(device.getPhysicalDevice(), &memProps); + + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device.handle(), imageHandle, &memReqs); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + std::optional memType{}; + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN + (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + memType.emplace(i); + break; + } // NOLINTEND + } + if (!memType.has_value()) + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image"); +#pragma clang diagnostic pop + + // ~~allocate~~ and bind memory + const VkMemoryDedicatedAllocateInfoKHR dedicatedInfo2{ + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR, + .image = imageHandle, + }; + const VkImportMemoryFdInfoKHR importInfo{ + .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, + .pNext = &dedicatedInfo2, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR, + .fd = fd // closes the fd + }; + const VkMemoryAllocateInfo allocInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &importInfo, + .allocationSize = memReqs.size, + .memoryTypeIndex = memType.value() + }; + VkDeviceMemory memoryHandle{}; + res = vkAllocateMemory(device.handle(), &allocInfo, nullptr, &memoryHandle); + if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image"); + + res = vkBindImageMemory(device.handle(), imageHandle, memoryHandle, 0); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image"); + + // create image view + const VkImageViewCreateInfo viewDesc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = imageHandle, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY + }, + .subresourceRange = { + .aspectMask = aspectFlags, + .levelCount = 1, + .layerCount = 1 + } + }; + + VkImageView viewHandle{}; + res = vkCreateImageView(device.handle(), &viewDesc, nullptr, &viewHandle); + if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create image view"); + + // store objects in shared ptr + this->layout = std::make_shared(VK_IMAGE_LAYOUT_UNDEFINED); + this->image = std::shared_ptr( + new VkImage(imageHandle), + [dev = device.handle()](VkImage* img) { + vkDestroyImage(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device.handle()](VkDeviceMemory* mem) { + vkFreeMemory(dev, *mem, nullptr); + } + ); + this->view = std::shared_ptr( + new VkImageView(viewHandle), + [dev = device.handle()](VkImageView* imgView) { + vkDestroyImageView(dev, *imgView, nullptr); + } + ); } diff --git a/src/core/semaphore.cpp b/src/core/semaphore.cpp index d2aee2a..78b66d4 100644 --- a/src/core/semaphore.cpp +++ b/src/core/semaphore.cpp @@ -29,6 +29,42 @@ Semaphore::Semaphore(const Core::Device& device, std::optional initial ); } +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 + const VkImportSemaphoreFdInfoKHR importInfo{ + .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, + .semaphore = semaphoreHandle, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT, + .fd = fd // closes the fd + }; + res = vkImportSemaphoreFdKHR(device.handle(), &importInfo); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to import semaphore from fd"); + + // store semaphore in shared ptr + this->isTimeline = false; + this->semaphore = std::shared_ptr( + new VkSemaphore(semaphoreHandle), + [dev = device.handle()](VkSemaphore* semaphoreHandle) { + vkDestroySemaphore(dev, *semaphoreHandle, nullptr); + } + ); +} + void Semaphore::signal(const Core::Device& device, uint64_t value) const { if (!this->isTimeline) throw std::logic_error("Invalid timeline semaphore"); From d7b597ed0feaa132a4f834eaad91e80e0df84010 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 09:17:09 +0200 Subject: [PATCH 042/253] move to subproject & initial hook commit --- CMakeLists.txt | 28 +++++++------- include/log.hpp | 37 +++++++++++++++++++ lsfg-vk-gen/CMakeLists.txt | 36 ++++++++++++++++++ {include => lsfg-vk-gen/include}/context.hpp | 0 .../include}/core/buffer.hpp | 0 .../include}/core/commandbuffer.hpp | 0 .../include}/core/commandpool.hpp | 0 .../include}/core/descriptorpool.hpp | 0 .../include}/core/descriptorset.hpp | 0 .../include}/core/device.hpp | 0 .../include}/core/fence.hpp | 0 .../include}/core/image.hpp | 0 .../include}/core/instance.hpp | 0 .../include}/core/pipeline.hpp | 0 .../include}/core/sampler.hpp | 0 .../include}/core/semaphore.hpp | 0 .../include}/core/shadermodule.hpp | 0 .../include}/shaderchains/alpha.hpp | 0 .../include}/shaderchains/beta.hpp | 0 .../include}/shaderchains/delta.hpp | 0 .../include}/shaderchains/downsample.hpp | 0 .../include}/shaderchains/epsilon.hpp | 0 .../include}/shaderchains/extract.hpp | 0 .../include}/shaderchains/gamma.hpp | 0 .../include}/shaderchains/magic.hpp | 0 .../include}/shaderchains/merge.hpp | 0 .../include}/shaderchains/zeta.hpp | 0 {include => lsfg-vk-gen/include}/utils.hpp | 0 {public => lsfg-vk-gen/public}/lsfg.hpp | 0 {src => lsfg-vk-gen/src}/context.cpp | 4 +- {src => lsfg-vk-gen/src}/core/buffer.cpp | 0 .../src}/core/commandbuffer.cpp | 0 {src => lsfg-vk-gen/src}/core/commandpool.cpp | 0 .../src}/core/descriptorpool.cpp | 0 .../src}/core/descriptorset.cpp | 0 {src => lsfg-vk-gen/src}/core/device.cpp | 0 {src => lsfg-vk-gen/src}/core/fence.cpp | 0 {src => lsfg-vk-gen/src}/core/image.cpp | 0 {src => lsfg-vk-gen/src}/core/instance.cpp | 0 {src => lsfg-vk-gen/src}/core/pipeline.cpp | 0 {src => lsfg-vk-gen/src}/core/sampler.cpp | 0 {src => lsfg-vk-gen/src}/core/semaphore.cpp | 0 .../src}/core/shadermodule.cpp | 0 {src => lsfg-vk-gen/src}/lsfg.cpp | 0 .../src}/shaderchains/alpha.cpp | 0 .../src}/shaderchains/beta.cpp | 0 .../src}/shaderchains/delta.cpp | 0 .../src}/shaderchains/downsample.cpp | 0 .../src}/shaderchains/epsilon.cpp | 0 .../src}/shaderchains/extract.cpp | 0 .../src}/shaderchains/gamma.cpp | 0 .../src}/shaderchains/magic.cpp | 0 .../src}/shaderchains/merge.cpp | 0 .../src}/shaderchains/zeta.cpp | 0 {src => lsfg-vk-gen/src}/utils.cpp | 0 src/init.cpp | 15 ++++++++ 56 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 include/log.hpp create mode 100644 lsfg-vk-gen/CMakeLists.txt rename {include => lsfg-vk-gen/include}/context.hpp (100%) rename {include => lsfg-vk-gen/include}/core/buffer.hpp (100%) rename {include => lsfg-vk-gen/include}/core/commandbuffer.hpp (100%) rename {include => lsfg-vk-gen/include}/core/commandpool.hpp (100%) rename {include => lsfg-vk-gen/include}/core/descriptorpool.hpp (100%) rename {include => lsfg-vk-gen/include}/core/descriptorset.hpp (100%) rename {include => lsfg-vk-gen/include}/core/device.hpp (100%) rename {include => lsfg-vk-gen/include}/core/fence.hpp (100%) rename {include => lsfg-vk-gen/include}/core/image.hpp (100%) rename {include => lsfg-vk-gen/include}/core/instance.hpp (100%) rename {include => lsfg-vk-gen/include}/core/pipeline.hpp (100%) rename {include => lsfg-vk-gen/include}/core/sampler.hpp (100%) rename {include => lsfg-vk-gen/include}/core/semaphore.hpp (100%) rename {include => lsfg-vk-gen/include}/core/shadermodule.hpp (100%) rename {include => lsfg-vk-gen/include}/shaderchains/alpha.hpp (100%) rename {include => lsfg-vk-gen/include}/shaderchains/beta.hpp (100%) rename {include => lsfg-vk-gen/include}/shaderchains/delta.hpp (100%) rename {include => lsfg-vk-gen/include}/shaderchains/downsample.hpp (100%) rename {include => lsfg-vk-gen/include}/shaderchains/epsilon.hpp (100%) rename {include => lsfg-vk-gen/include}/shaderchains/extract.hpp (100%) rename {include => lsfg-vk-gen/include}/shaderchains/gamma.hpp (100%) rename {include => lsfg-vk-gen/include}/shaderchains/magic.hpp (100%) rename {include => lsfg-vk-gen/include}/shaderchains/merge.hpp (100%) rename {include => lsfg-vk-gen/include}/shaderchains/zeta.hpp (100%) rename {include => lsfg-vk-gen/include}/utils.hpp (100%) rename {public => lsfg-vk-gen/public}/lsfg.hpp (100%) rename {src => lsfg-vk-gen/src}/context.cpp (98%) rename {src => lsfg-vk-gen/src}/core/buffer.cpp (100%) rename {src => lsfg-vk-gen/src}/core/commandbuffer.cpp (100%) rename {src => lsfg-vk-gen/src}/core/commandpool.cpp (100%) rename {src => lsfg-vk-gen/src}/core/descriptorpool.cpp (100%) rename {src => lsfg-vk-gen/src}/core/descriptorset.cpp (100%) rename {src => lsfg-vk-gen/src}/core/device.cpp (100%) rename {src => lsfg-vk-gen/src}/core/fence.cpp (100%) rename {src => lsfg-vk-gen/src}/core/image.cpp (100%) rename {src => lsfg-vk-gen/src}/core/instance.cpp (100%) rename {src => lsfg-vk-gen/src}/core/pipeline.cpp (100%) rename {src => lsfg-vk-gen/src}/core/sampler.cpp (100%) rename {src => lsfg-vk-gen/src}/core/semaphore.cpp (100%) rename {src => lsfg-vk-gen/src}/core/shadermodule.cpp (100%) rename {src => lsfg-vk-gen/src}/lsfg.cpp (100%) rename {src => lsfg-vk-gen/src}/shaderchains/alpha.cpp (100%) rename {src => lsfg-vk-gen/src}/shaderchains/beta.cpp (100%) rename {src => lsfg-vk-gen/src}/shaderchains/delta.cpp (100%) rename {src => lsfg-vk-gen/src}/shaderchains/downsample.cpp (100%) rename {src => lsfg-vk-gen/src}/shaderchains/epsilon.cpp (100%) rename {src => lsfg-vk-gen/src}/shaderchains/extract.cpp (100%) rename {src => lsfg-vk-gen/src}/shaderchains/gamma.cpp (100%) rename {src => lsfg-vk-gen/src}/shaderchains/magic.cpp (100%) rename {src => lsfg-vk-gen/src}/shaderchains/merge.cpp (100%) rename {src => lsfg-vk-gen/src}/shaderchains/zeta.cpp (100%) rename {src => lsfg-vk-gen/src}/utils.cpp (100%) create mode 100644 src/init.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5aa0e7a..12e045f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,5 @@ cmake_minimum_required(VERSION 3.29) -project(lsfg-vk-base-base - VERSION 0.0.1 - DESCRIPTION "lsfg-vk-base: LSFG on Linux through Vulkan" - LANGUAGES CXX) - # cmake options set(CMAKE_CXX_COMPILER clang++) @@ -16,20 +11,24 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # main project +project(lsfg-vk + VERSION 0.0.1 + DESCRIPTION "lsfg-vk: LSFG on Linux through Vulkan" + LANGUAGES CXX) + +add_subdirectory(lsfg-vk-gen) + file(GLOB SOURCES - "src/core/*.cpp" - "src/shaderchains/*.cpp" "src/*.cpp" ) -add_library(lsfg-vk-base SHARED ${SOURCES}) +add_library(lsfg-vk SHARED ${SOURCES}) -target_include_directories(lsfg-vk-base - PRIVATE include - PUBLIC public) -target_link_libraries(lsfg-vk-base - PUBLIC vulkan) -target_compile_options(lsfg-vk-base PRIVATE +target_include_directories(lsfg-vk + PRIVATE include) +target_link_libraries(lsfg-vk + PRIVATE lsfg-vk-gen) +target_compile_options(lsfg-vk PRIVATE -Weverything # disable compat c++ flags -Wno-pre-c++20-compat-pedantic @@ -43,7 +42,6 @@ target_compile_options(lsfg-vk-base PRIVATE -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 ) diff --git a/include/log.hpp b/include/log.hpp new file mode 100644 index 0000000..7327eba --- /dev/null +++ b/include/log.hpp @@ -0,0 +1,37 @@ +#ifndef LOG_HPP +#define LOG_HPP + +#include +#include + +namespace Log { + + const std::string_view WHITE = "\033[1;37m"; + const std::string_view YELLOW = "\033[1;33m"; + const std::string_view RED = "\033[1;31m"; + const std::string_view GRAY = "\033[1;30m"; + const std::string_view RESET = "\033[0m"; + + template + void info(std::format_string fmt, Args&&... args) { + std::cerr << WHITE << std::format(fmt, std::forward(args)...) << RESET << '\n'; + } + + template + void warn(std::format_string fmt, Args&&... args) { + std::cerr << YELLOW << std::format(fmt, std::forward(args)...) << RESET << '\n'; + } + + template + void error(std::format_string fmt, Args&&... args) { + std::cerr << RED << std::format(fmt, std::forward(args)...) << RESET << '\n'; + } + + template + void debug(std::format_string fmt, Args&&... args) { + std::cerr << GRAY << std::format(fmt, std::forward(args)...) << RESET << '\n'; + } + +} + +#endif // LOG_HPP diff --git a/lsfg-vk-gen/CMakeLists.txt b/lsfg-vk-gen/CMakeLists.txt new file mode 100644 index 0000000..b4a7c95 --- /dev/null +++ b/lsfg-vk-gen/CMakeLists.txt @@ -0,0 +1,36 @@ +project(lsfg-vk-gen + VERSION 0.0.1 + DESCRIPTION "lsfg-vk-gen: LSFG on Linux through Vulkan (backend)" + LANGUAGES CXX) + +file(GLOB BACKEND_SOURCES + "src/core/*.cpp" + "src/shaderchains/*.cpp" + "src/*.cpp" +) + +add_library(lsfg-vk-gen SHARED ${BACKEND_SOURCES}) + +target_include_directories(lsfg-vk-gen + PRIVATE include + PUBLIC public) +target_link_libraries(lsfg-vk-gen + PUBLIC vulkan) +target_compile_options(lsfg-vk-gen 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 +) diff --git a/include/context.hpp b/lsfg-vk-gen/include/context.hpp similarity index 100% rename from include/context.hpp rename to lsfg-vk-gen/include/context.hpp diff --git a/include/core/buffer.hpp b/lsfg-vk-gen/include/core/buffer.hpp similarity index 100% rename from include/core/buffer.hpp rename to lsfg-vk-gen/include/core/buffer.hpp diff --git a/include/core/commandbuffer.hpp b/lsfg-vk-gen/include/core/commandbuffer.hpp similarity index 100% rename from include/core/commandbuffer.hpp rename to lsfg-vk-gen/include/core/commandbuffer.hpp diff --git a/include/core/commandpool.hpp b/lsfg-vk-gen/include/core/commandpool.hpp similarity index 100% rename from include/core/commandpool.hpp rename to lsfg-vk-gen/include/core/commandpool.hpp diff --git a/include/core/descriptorpool.hpp b/lsfg-vk-gen/include/core/descriptorpool.hpp similarity index 100% rename from include/core/descriptorpool.hpp rename to lsfg-vk-gen/include/core/descriptorpool.hpp diff --git a/include/core/descriptorset.hpp b/lsfg-vk-gen/include/core/descriptorset.hpp similarity index 100% rename from include/core/descriptorset.hpp rename to lsfg-vk-gen/include/core/descriptorset.hpp diff --git a/include/core/device.hpp b/lsfg-vk-gen/include/core/device.hpp similarity index 100% rename from include/core/device.hpp rename to lsfg-vk-gen/include/core/device.hpp diff --git a/include/core/fence.hpp b/lsfg-vk-gen/include/core/fence.hpp similarity index 100% rename from include/core/fence.hpp rename to lsfg-vk-gen/include/core/fence.hpp diff --git a/include/core/image.hpp b/lsfg-vk-gen/include/core/image.hpp similarity index 100% rename from include/core/image.hpp rename to lsfg-vk-gen/include/core/image.hpp diff --git a/include/core/instance.hpp b/lsfg-vk-gen/include/core/instance.hpp similarity index 100% rename from include/core/instance.hpp rename to lsfg-vk-gen/include/core/instance.hpp diff --git a/include/core/pipeline.hpp b/lsfg-vk-gen/include/core/pipeline.hpp similarity index 100% rename from include/core/pipeline.hpp rename to lsfg-vk-gen/include/core/pipeline.hpp diff --git a/include/core/sampler.hpp b/lsfg-vk-gen/include/core/sampler.hpp similarity index 100% rename from include/core/sampler.hpp rename to lsfg-vk-gen/include/core/sampler.hpp diff --git a/include/core/semaphore.hpp b/lsfg-vk-gen/include/core/semaphore.hpp similarity index 100% rename from include/core/semaphore.hpp rename to lsfg-vk-gen/include/core/semaphore.hpp diff --git a/include/core/shadermodule.hpp b/lsfg-vk-gen/include/core/shadermodule.hpp similarity index 100% rename from include/core/shadermodule.hpp rename to lsfg-vk-gen/include/core/shadermodule.hpp diff --git a/include/shaderchains/alpha.hpp b/lsfg-vk-gen/include/shaderchains/alpha.hpp similarity index 100% rename from include/shaderchains/alpha.hpp rename to lsfg-vk-gen/include/shaderchains/alpha.hpp diff --git a/include/shaderchains/beta.hpp b/lsfg-vk-gen/include/shaderchains/beta.hpp similarity index 100% rename from include/shaderchains/beta.hpp rename to lsfg-vk-gen/include/shaderchains/beta.hpp diff --git a/include/shaderchains/delta.hpp b/lsfg-vk-gen/include/shaderchains/delta.hpp similarity index 100% rename from include/shaderchains/delta.hpp rename to lsfg-vk-gen/include/shaderchains/delta.hpp diff --git a/include/shaderchains/downsample.hpp b/lsfg-vk-gen/include/shaderchains/downsample.hpp similarity index 100% rename from include/shaderchains/downsample.hpp rename to lsfg-vk-gen/include/shaderchains/downsample.hpp diff --git a/include/shaderchains/epsilon.hpp b/lsfg-vk-gen/include/shaderchains/epsilon.hpp similarity index 100% rename from include/shaderchains/epsilon.hpp rename to lsfg-vk-gen/include/shaderchains/epsilon.hpp diff --git a/include/shaderchains/extract.hpp b/lsfg-vk-gen/include/shaderchains/extract.hpp similarity index 100% rename from include/shaderchains/extract.hpp rename to lsfg-vk-gen/include/shaderchains/extract.hpp diff --git a/include/shaderchains/gamma.hpp b/lsfg-vk-gen/include/shaderchains/gamma.hpp similarity index 100% rename from include/shaderchains/gamma.hpp rename to lsfg-vk-gen/include/shaderchains/gamma.hpp diff --git a/include/shaderchains/magic.hpp b/lsfg-vk-gen/include/shaderchains/magic.hpp similarity index 100% rename from include/shaderchains/magic.hpp rename to lsfg-vk-gen/include/shaderchains/magic.hpp diff --git a/include/shaderchains/merge.hpp b/lsfg-vk-gen/include/shaderchains/merge.hpp similarity index 100% rename from include/shaderchains/merge.hpp rename to lsfg-vk-gen/include/shaderchains/merge.hpp diff --git a/include/shaderchains/zeta.hpp b/lsfg-vk-gen/include/shaderchains/zeta.hpp similarity index 100% rename from include/shaderchains/zeta.hpp rename to lsfg-vk-gen/include/shaderchains/zeta.hpp diff --git a/include/utils.hpp b/lsfg-vk-gen/include/utils.hpp similarity index 100% rename from include/utils.hpp rename to lsfg-vk-gen/include/utils.hpp diff --git a/public/lsfg.hpp b/lsfg-vk-gen/public/lsfg.hpp similarity index 100% rename from public/lsfg.hpp rename to lsfg-vk-gen/public/lsfg.hpp diff --git a/src/context.cpp b/lsfg-vk-gen/src/context.cpp similarity index 98% rename from src/context.cpp rename to lsfg-vk-gen/src/context.cpp index 37cb651..46ee8ed 100644 --- a/src/context.cpp +++ b/lsfg-vk-gen/src/context.cpp @@ -94,8 +94,8 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in } void Context::present(const Core::Device& device, int inSem, int outSem) { - Core::Semaphore inSemaphore(device, inSem); - Core::Semaphore outSemaphore(device, outSem); + const Core::Semaphore inSemaphore(device, inSem); + const Core::Semaphore outSemaphore(device, outSem); Core::CommandBuffer cmdBuffer(device, this->cmdPool); cmdBuffer.begin(); diff --git a/src/core/buffer.cpp b/lsfg-vk-gen/src/core/buffer.cpp similarity index 100% rename from src/core/buffer.cpp rename to lsfg-vk-gen/src/core/buffer.cpp diff --git a/src/core/commandbuffer.cpp b/lsfg-vk-gen/src/core/commandbuffer.cpp similarity index 100% rename from src/core/commandbuffer.cpp rename to lsfg-vk-gen/src/core/commandbuffer.cpp diff --git a/src/core/commandpool.cpp b/lsfg-vk-gen/src/core/commandpool.cpp similarity index 100% rename from src/core/commandpool.cpp rename to lsfg-vk-gen/src/core/commandpool.cpp diff --git a/src/core/descriptorpool.cpp b/lsfg-vk-gen/src/core/descriptorpool.cpp similarity index 100% rename from src/core/descriptorpool.cpp rename to lsfg-vk-gen/src/core/descriptorpool.cpp diff --git a/src/core/descriptorset.cpp b/lsfg-vk-gen/src/core/descriptorset.cpp similarity index 100% rename from src/core/descriptorset.cpp rename to lsfg-vk-gen/src/core/descriptorset.cpp diff --git a/src/core/device.cpp b/lsfg-vk-gen/src/core/device.cpp similarity index 100% rename from src/core/device.cpp rename to lsfg-vk-gen/src/core/device.cpp diff --git a/src/core/fence.cpp b/lsfg-vk-gen/src/core/fence.cpp similarity index 100% rename from src/core/fence.cpp rename to lsfg-vk-gen/src/core/fence.cpp diff --git a/src/core/image.cpp b/lsfg-vk-gen/src/core/image.cpp similarity index 100% rename from src/core/image.cpp rename to lsfg-vk-gen/src/core/image.cpp diff --git a/src/core/instance.cpp b/lsfg-vk-gen/src/core/instance.cpp similarity index 100% rename from src/core/instance.cpp rename to lsfg-vk-gen/src/core/instance.cpp diff --git a/src/core/pipeline.cpp b/lsfg-vk-gen/src/core/pipeline.cpp similarity index 100% rename from src/core/pipeline.cpp rename to lsfg-vk-gen/src/core/pipeline.cpp diff --git a/src/core/sampler.cpp b/lsfg-vk-gen/src/core/sampler.cpp similarity index 100% rename from src/core/sampler.cpp rename to lsfg-vk-gen/src/core/sampler.cpp diff --git a/src/core/semaphore.cpp b/lsfg-vk-gen/src/core/semaphore.cpp similarity index 100% rename from src/core/semaphore.cpp rename to lsfg-vk-gen/src/core/semaphore.cpp diff --git a/src/core/shadermodule.cpp b/lsfg-vk-gen/src/core/shadermodule.cpp similarity index 100% rename from src/core/shadermodule.cpp rename to lsfg-vk-gen/src/core/shadermodule.cpp diff --git a/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp similarity index 100% rename from src/lsfg.cpp rename to lsfg-vk-gen/src/lsfg.cpp diff --git a/src/shaderchains/alpha.cpp b/lsfg-vk-gen/src/shaderchains/alpha.cpp similarity index 100% rename from src/shaderchains/alpha.cpp rename to lsfg-vk-gen/src/shaderchains/alpha.cpp diff --git a/src/shaderchains/beta.cpp b/lsfg-vk-gen/src/shaderchains/beta.cpp similarity index 100% rename from src/shaderchains/beta.cpp rename to lsfg-vk-gen/src/shaderchains/beta.cpp diff --git a/src/shaderchains/delta.cpp b/lsfg-vk-gen/src/shaderchains/delta.cpp similarity index 100% rename from src/shaderchains/delta.cpp rename to lsfg-vk-gen/src/shaderchains/delta.cpp diff --git a/src/shaderchains/downsample.cpp b/lsfg-vk-gen/src/shaderchains/downsample.cpp similarity index 100% rename from src/shaderchains/downsample.cpp rename to lsfg-vk-gen/src/shaderchains/downsample.cpp diff --git a/src/shaderchains/epsilon.cpp b/lsfg-vk-gen/src/shaderchains/epsilon.cpp similarity index 100% rename from src/shaderchains/epsilon.cpp rename to lsfg-vk-gen/src/shaderchains/epsilon.cpp diff --git a/src/shaderchains/extract.cpp b/lsfg-vk-gen/src/shaderchains/extract.cpp similarity index 100% rename from src/shaderchains/extract.cpp rename to lsfg-vk-gen/src/shaderchains/extract.cpp diff --git a/src/shaderchains/gamma.cpp b/lsfg-vk-gen/src/shaderchains/gamma.cpp similarity index 100% rename from src/shaderchains/gamma.cpp rename to lsfg-vk-gen/src/shaderchains/gamma.cpp diff --git a/src/shaderchains/magic.cpp b/lsfg-vk-gen/src/shaderchains/magic.cpp similarity index 100% rename from src/shaderchains/magic.cpp rename to lsfg-vk-gen/src/shaderchains/magic.cpp diff --git a/src/shaderchains/merge.cpp b/lsfg-vk-gen/src/shaderchains/merge.cpp similarity index 100% rename from src/shaderchains/merge.cpp rename to lsfg-vk-gen/src/shaderchains/merge.cpp diff --git a/src/shaderchains/zeta.cpp b/lsfg-vk-gen/src/shaderchains/zeta.cpp similarity index 100% rename from src/shaderchains/zeta.cpp rename to lsfg-vk-gen/src/shaderchains/zeta.cpp diff --git a/src/utils.cpp b/lsfg-vk-gen/src/utils.cpp similarity index 100% rename from src/utils.cpp rename to lsfg-vk-gen/src/utils.cpp diff --git a/src/init.cpp b/src/init.cpp new file mode 100644 index 0000000..3938c8a --- /dev/null +++ b/src/init.cpp @@ -0,0 +1,15 @@ +#include "log.hpp" + +extern "C" void __attribute__((constructor)) init(); +extern "C" [[noreturn]] void __attribute__((destructor)) deinit(); + +void init() { + Log::info("lsfg-vk: init() called"); +} + +void deinit() { + Log::debug("lsfg-vk: deinit() called, exiting"); + // for some reason some applications unload the library despite it containing + // the dl functions. this will lead to a segmentation fault, so we exit early. + exit(EXIT_SUCCESS); +} From 37ed1e34f574d09fc4687ce7fa5cd6336e1ec95f Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 09:21:18 +0200 Subject: [PATCH 043/253] implement dynamic laoder with overrides --- CMakeLists.txt | 1 + include/loader/dl.hpp | 133 ++++++++++++++++++++++++++++++ src/init.cpp | 4 + src/loader/dl.cpp | 182 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 320 insertions(+) create mode 100644 include/loader/dl.hpp create mode 100644 src/loader/dl.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 12e045f..b34403a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ project(lsfg-vk add_subdirectory(lsfg-vk-gen) file(GLOB SOURCES + "src/loader/*.cpp" "src/*.cpp" ) diff --git a/include/loader/dl.hpp b/include/loader/dl.hpp new file mode 100644 index 0000000..a0e569b --- /dev/null +++ b/include/loader/dl.hpp @@ -0,0 +1,133 @@ +#ifndef DL_HPP +#define DL_HPP + +#include +#include + +// +// This dynamic loader replaces the standard dlopen, dlsym, and dlclose functions. +// On initialize, the original functions are obtained via dlvsym (glibc exclusive) +// and made available under functions with the "o" prefix. +// +// Any call to regular dlopen, dlsym or dlclose is intercepted and may be +// overriden by registering a File override via `Loader::DL::registerFile`. +// + +namespace Loader::DL { + + /// Dynamic loader override structure. + class File { + public: + /// + /// Create a dynamic loader override for a specific file. + /// + /// @param filename The name of the file to override. + /// + File(std::string filename) + : filename(std::move(filename)) {} + + /// + /// Append a symbol to the dynamic loader override. + /// + /// @param symbol The name of the symbol to add. + /// @param address The address of the symbol. + /// + void defineSymbol(const std::string& symbol, void* address) { + symbols[symbol] = address; + } + + /// Get the filename + [[nodiscard]] const std::string& getFilename() const { return filename; } + /// Get all overriden symbols + [[nodiscard]] const std::unordered_map& getSymbols() const { return symbols; } + + // Find a specific symbol + [[nodiscard]] void* findSymbol(const std::string& symbol) const { + auto it = symbols.find(symbol); + return (it != symbols.end()) ? it->second : nullptr; + } + + /// Get the fake handle + [[nodiscard]] void* getHandle() const { return handle; } + /// Get the real handle + [[nodiscard]] void* getOriginalHandle() const { return handle_orig; } + + /// Set the fake handle + void setHandle(void* new_handle) { handle = new_handle; } + /// Set the real handle + void setOriginalHandle(void* new_handle) { handle_orig = new_handle; } + + /// Copyable, moveable, default destructor + File(const File&) = default; + File(File&&) = default; + File& operator=(const File&) = default; + File& operator=(File&&) = default; + ~File() = default; + private: + std::string filename; + std::unordered_map symbols; + + void* handle = nullptr; + void* handle_orig = nullptr; + }; + + /// + /// Initialize the dynamic loader + /// + void initialize(); + + /// + /// Register a file with the dynamic loader. + /// + /// @param file The file to register. + /// + void registerFile(const File& file); + + /// + /// Disable hooks temporarily. This may be useful + /// when loading third-party libraries you wish not + /// to hook. + /// + void disableHooks(); + + /// + /// Re-enable hooks after they were disabled. + /// + void enableHooks(); + + /// + /// Call the original dlopen function. + /// + /// @param filename The name of the file to open. + /// @param flag The flags to use when opening the file. + /// @return A handle to the opened file, or NULL on failure. + /// + void* odlopen(const char* filename, int flag); + + /// + /// Call the original dlsym function. + /// + /// @param handle The handle to the opened file. + /// @param symbol The name of the symbol to look up. + /// @return A pointer to the symbol, or NULL on failure. + /// + void* odlsym(void* handle, const char* symbol); + + /// + /// Call the original dlclose function. + /// + /// @param handle The handle to the opened file. + /// @return 0 on success, or -1 on failure. + /// + int odlclose(void* handle); + +} + +/// Modified version of the dlopen function. +extern "C" void* dlopen(const char* filename, int flag); +/// Modified version of the dlsym function. +extern "C" void* dlsym(void* handle, const char* symbol); +/// Modified version of the dlclose function. +extern "C" int dlclose(void* handle); + +#endif // DL_HPP diff --git a/src/init.cpp b/src/init.cpp index 3938c8a..34596d8 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1,3 +1,4 @@ +#include "loader/dl.hpp" #include "log.hpp" extern "C" void __attribute__((constructor)) init(); @@ -5,6 +6,9 @@ extern "C" [[noreturn]] void __attribute__((destructor)) deinit(); void init() { Log::info("lsfg-vk: init() called"); + + // hook loaders + Loader::DL::initialize(); } void deinit() { diff --git a/src/loader/dl.cpp b/src/loader/dl.cpp new file mode 100644 index 0000000..265679e --- /dev/null +++ b/src/loader/dl.cpp @@ -0,0 +1,182 @@ +#include "loader/dl.hpp" +#include "log.hpp" + +#include + +using namespace Loader; + +using dlopen_t = void* (*)(const char*, int); +using dlsym_t = void* (*)(void*, const char*); +using dlclose_t = int (*)(void*); + +// glibc exclusive function to get versioned symbols +extern "C" void* dlvsym(long, const char*, const char*); + +namespace { + // original function pointers + dlopen_t dlopen_ptr; + dlsym_t dlsym_ptr; + dlclose_t dlclose_ptr; + + // map of all registered overrides + auto& overrides() { + // this has to be a function rather than a static variable + // because of weird initialization order issues. + static std::unordered_map overrides; + return overrides; + } + + // vector of loaded handles + auto& handles() { + static std::vector handles; + return handles; + } + + bool enable_hooks{true}; +} + +void DL::initialize() { + if (dlopen_ptr || dlsym_ptr || dlclose_ptr) { + Log::warn("lsfg-vk(dl): Dynamic loader already initialized, did you call it twice?"); + return; + } + + dlopen_ptr = reinterpret_cast (dlvsym(-1, "dlopen", "GLIBC_2.2.5")); + dlsym_ptr = reinterpret_cast (dlvsym(-1, "dlsym", "GLIBC_2.2.5")); + dlclose_ptr = reinterpret_cast(dlvsym(-1, "dlclose", "GLIBC_2.2.5")); + if (!dlopen_ptr || !dlsym_ptr || !dlclose_ptr) { + Log::error("lsfg-vk(dl): Failed to initialize dynamic loader, missing symbols"); + exit(EXIT_FAILURE); + } + + Log::debug("lsfg-vk(dl): Initialized dynamic loader with original functions"); +} + +void DL::registerFile(const File& file) { + auto& files = overrides(); + + auto it = files.find(file.getFilename()); + if (it == files.end()) { + // simply register if the file hasn't been registered yet + files.emplace(file.getFilename(), file); + return; + } + + // merge the new file's symbols into the previously registered one + auto& existing_file = it->second; + for (const auto& [symbol, func] : file.getSymbols()) + if (existing_file.findSymbol(symbol) == nullptr) + existing_file.defineSymbol(symbol, func); + else + Log::warn("lsfg-vk(dl): Tried registering symbol {}::{}, but it is already defined", + existing_file.getFilename(), symbol); +} + +void DL::disableHooks() { enable_hooks = false; } +void DL::enableHooks() { enable_hooks = true; } + +extern "C" void* dlopen(const char* filename, int flag) { + auto& files = overrides(); + auto& loaded = handles(); + + // ALWAYS load the library and ensure it's tracked + auto* handle = dlopen_ptr(filename, flag); + if (handle && std::ranges::find(loaded, handle) == loaded.end()) + loaded.push_back(handle); + + // no need to check for overrides if hooks are disabled + if (!enable_hooks || !filename) + return handle; + + // try to find an override for this filename + const std::string filename_str(filename); + auto it = files.find(filename_str); + if (it == files.end()) + return handle; + + auto& file = it->second; + file.setOriginalHandle(handle); + file.setHandle(reinterpret_cast(&file)); + + Log::debug("lsfg-vk(dl): Intercepted module load for {}", file.getFilename()); + return file.getHandle(); +} + +extern "C" void* dlsym(void* handle, const char* symbol) { + const auto& files = overrides(); + + if (!enable_hooks || !handle || !symbol) + return dlsym_ptr(handle, symbol); + + // see if handle is a fake one + const auto it = std::ranges::find_if(files, [handle](const auto& pair) { + return pair.second.getHandle() == handle; + }); + if (it == files.end()) + return dlsym_ptr(handle, symbol); + const auto& file = it->second; + + // find a symbol override + const std::string symbol_str(symbol); + auto* func = file.findSymbol(symbol_str); + if (func == nullptr) + return dlsym_ptr(file.getOriginalHandle(), symbol); + + Log::debug("lsfg-vk(dl): Intercepted symbol {}::{}", file.getFilename(), symbol_str); + return func; +} + +extern "C" int dlclose(void* handle) { + auto& files = overrides(); + auto& loaded = handles(); + + // no handle, let the original dlclose handle it + if (!handle) + return dlclose_ptr(handle); + + // see if the handle is a fake one + auto it = std::ranges::find_if(files, [handle](const auto& pair) { + return pair.second.getHandle() == handle; + }); + if (it == files.end()) { + // if the handle is not fake, check if it's still loaded. + // this is necessary to avoid double closing when + // one handle was acquired while hooks were disabled + auto l_it = std::ranges::find(loaded, handle); + if (l_it == loaded.end()) + return 0; + loaded.erase(l_it); + return dlclose_ptr(handle); + } + + auto& file = it->second; + handle = file.getOriginalHandle(); + file.setHandle(nullptr); + file.setOriginalHandle(nullptr); + + // similarly, if it is fake, check if it's still loaded + // before unloading it again. + auto l_it = std::ranges::find(loaded, handle); + if (l_it == loaded.end()) { + Log::debug("lsfg-vk(dl): Skipping unload for {} (already unloaded)", file.getFilename()); + return 0; + } + loaded.erase(l_it); + + Log::debug("lsfg-vk(dl): Unloaded {}", file.getFilename()); + return dlclose_ptr(handle); +} + +// original function calls + +void* DL::odlopen(const char* filename, int flag) { + return dlopen_ptr(filename, flag); +} + +void* DL::odlsym(void* handle, const char* symbol) { + return dlsym_ptr(handle, symbol); +} + +int DL::odlclose(void* handle) { + return dlclose_ptr(handle); +} From 935dd2f452c002bcafe0f2266340530deda886c6 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 09:22:32 +0200 Subject: [PATCH 044/253] implement Vulkan loader with symbol overrides --- include/loader/vk.hpp | 58 ++++++++++++++++++++++ src/init.cpp | 2 + src/loader/vk.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 include/loader/vk.hpp create mode 100644 src/loader/vk.cpp diff --git a/include/loader/vk.hpp b/include/loader/vk.hpp new file mode 100644 index 0000000..38d6338 --- /dev/null +++ b/include/loader/vk.hpp @@ -0,0 +1,58 @@ +#ifndef VK_HPP +#define VK_HPP + +#include + +#include + +// +// Similar to the dynamic loader, the Vulkan loader replaces the standard +// vkGetInstanceProcAddr and vkGetDeviceProcAddr functions. +// +// One thing that should be noted, is that not every application uses the +// Vulkan loader for every method. On Linux it's not unusual to see dlsym +// being used for vulkan functions, so make sure to register the same +// symbol on both loaders. +// + +namespace Loader::VK { + + /// + /// Initialize the Vulkan loader. + /// + void initialize(); + + /// + /// Register a symbol to the Vulkan loader. + /// + /// @param symbol The name of the Vulkan function to override. + /// @param address The address of the Vulkan function. + /// + void registerSymbol(const std::string& symbol, void* address); + + /// + /// Call the original vkGetInstanceProcAddr function. + /// + /// @param instance The (optional) Vulkan instance. + /// @param pName The name of the function to retrieve. + /// @return The address of the function, or nullptr if not found. + /// + PFN_vkVoidFunction ovkGetInstanceProcAddr(VkInstance instance, const char* pName); + + /// + /// Call the original vkGetDeviceProcAddr function. + /// + /// @param device The Vulkan device. + /// @param pName The name of the function to retrieve. + /// @return The address of the function, or nullptr if not found. + /// + PFN_vkVoidFunction ovkGetDeviceProcAddr(VkDevice device, const char* pName); + +} + +/// Modified version of the vkGetInstanceProcAddr function. +extern "C" PFN_vkVoidFunction myvkGetInstanceProcAddr(VkInstance instance, const char* pName); +/// Modified version of the vkGetDeviceProcAddr function. +extern "C" PFN_vkVoidFunction myvkGetDeviceProcAddr(VkDevice device, const char* pName); + +#endif // VK_HPP diff --git a/src/init.cpp b/src/init.cpp index 34596d8..345963f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1,4 +1,5 @@ #include "loader/dl.hpp" +#include "loader/vk.hpp" #include "log.hpp" extern "C" void __attribute__((constructor)) init(); @@ -9,6 +10,7 @@ void init() { // hook loaders Loader::DL::initialize(); + Loader::VK::initialize(); } void deinit() { diff --git a/src/loader/vk.cpp b/src/loader/vk.cpp new file mode 100644 index 0000000..4628fab --- /dev/null +++ b/src/loader/vk.cpp @@ -0,0 +1,109 @@ +#include "loader/vk.hpp" +#include "loader/dl.hpp" +#include "log.hpp" + +using namespace Loader; + +namespace { + // original function pointers + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr_ptr{}; + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr_ptr{}; + + // map of all overridden symbols + auto& symbols() { + static std::unordered_map symbols; + return symbols; + } +} + +void VK::initialize() { + if (vkGetInstanceProcAddr_ptr || vkGetDeviceProcAddr_ptr) { + Log::warn("lsfg-vk(vk): Vulkan loader already initialized, did you call it twice?"); + return; + } + + // get original function pointers + auto* handle = DL::odlopen("libvulkan.so.1", 0x2); + vkGetInstanceProcAddr_ptr = + reinterpret_cast(DL::odlsym(handle, "vkGetInstanceProcAddr")); + vkGetDeviceProcAddr_ptr = + reinterpret_cast (DL::odlsym(handle, "vkGetDeviceProcAddr")); + if (!vkGetInstanceProcAddr_ptr || !vkGetDeviceProcAddr_ptr) { + Log::error("lsfg-vk(vk): Failed to initialize Vulkan loader, missing symbols"); + exit(EXIT_FAILURE); + } + + // register dynamic loader overrides + DL::File vulkanLib{"libvulkan.so.1"}; + vulkanLib.defineSymbol("vkGetInstanceProcAddr", + reinterpret_cast(myvkGetInstanceProcAddr)); + vulkanLib.defineSymbol("vkGetDeviceProcAddr", + reinterpret_cast(myvkGetDeviceProcAddr)); + DL::registerFile(vulkanLib); + + DL::File vulkanLib2{"libvulkan.so"}; + vulkanLib2.defineSymbol("vkGetInstanceProcAddr", + reinterpret_cast(myvkGetInstanceProcAddr)); + vulkanLib2.defineSymbol("vkGetDeviceProcAddr", + reinterpret_cast(myvkGetDeviceProcAddr)); + DL::registerFile(vulkanLib2); + + // register vulkan loader overrides + VK::registerSymbol("vkGetInstanceProcAddr", reinterpret_cast(myvkGetInstanceProcAddr)); + VK::registerSymbol("vkGetDeviceProcAddr", reinterpret_cast(myvkGetDeviceProcAddr)); + + Log::debug("lsfg-vk(vk): Initialized Vulkan loader with original functions"); +} + +void VK::registerSymbol(const std::string& symbol, void* address) { + auto& syms = symbols(); + + const auto it = syms.find(symbol); + if (it != syms.end()) { + Log::warn("lsfg-vk(vk): Tried registering symbol {}, but it is already defined", symbol); + return; + } + + syms.emplace(symbol, address); +} + +PFN_vkVoidFunction myvkGetInstanceProcAddr(VkInstance instance, const char* pName) { + const auto& syms = symbols(); + + if (!pName) + return vkGetInstanceProcAddr_ptr(instance, pName); + + // try to find an override + const std::string pName_str(pName); + const auto it = syms.find(pName_str); + if (it == syms.end()) + return vkGetInstanceProcAddr_ptr(instance, pName); + + Log::debug("lsfg-vk(vk): Intercepted Vulkan symbol {}", pName_str); + return reinterpret_cast(it->second); +} + +PFN_vkVoidFunction myvkGetDeviceProcAddr(VkDevice device, const char* pName) { + const auto& syms = symbols(); + + if (!pName) + return vkGetDeviceProcAddr_ptr(device, pName); + + const std::string pName_str(pName); + auto it = syms.find(pName_str); + if (it == syms.end()) + return vkGetDeviceProcAddr_ptr(device, pName); + + Log::debug("lsfg-vk(vk): Intercepted Vulkan symbol {}", pName_str); + return reinterpret_cast(it->second); +} + +// original function calls + +PFN_vkVoidFunction VK::ovkGetInstanceProcAddr(VkInstance instance, const char* pName) { + return vkGetInstanceProcAddr_ptr(instance, pName); +} + +PFN_vkVoidFunction VK::ovkGetDeviceProcAddr(VkDevice device, const char* pName) { + return vkGetDeviceProcAddr_ptr(device, pName); +} From f31b5842d818c65c5e92ef5f79061a4b3c09a811 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 09:37:19 +0200 Subject: [PATCH 045/253] first batch of original vulkan pointers --- include/vulkan/funcs.hpp | 26 ++++++++++++++++++++++++ src/vulkan/funcs.cpp | 44 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 include/vulkan/funcs.hpp create mode 100644 src/vulkan/funcs.cpp diff --git a/include/vulkan/funcs.hpp b/include/vulkan/funcs.hpp new file mode 100644 index 0000000..9a2428a --- /dev/null +++ b/include/vulkan/funcs.hpp @@ -0,0 +1,26 @@ +#ifndef FUNCS_HPP +#define FUNCS_HPP + +#include + +namespace Vulkan::Funcs { + + /// + /// Initialize the global Vulkan function pointers. + /// + void initialize(); + + /// Call to the original vkCreateInstance function. + VkResult vkCreateInstanceOriginal( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance + ); + /// Call to the original vkDestroyInstance function. + void vkDestroyInstanceOriginal( + VkInstance instance, + const VkAllocationCallbacks* pAllocator + ); +} + +#endif // FUNCS_HPP diff --git a/src/vulkan/funcs.cpp b/src/vulkan/funcs.cpp new file mode 100644 index 0000000..31edaf9 --- /dev/null +++ b/src/vulkan/funcs.cpp @@ -0,0 +1,44 @@ +#include "vulkan/funcs.hpp" +#include "loader/dl.hpp" +#include "log.hpp" + +using namespace Vulkan; + +namespace { + PFN_vkCreateInstance vkCreateInstance_ptr{}; + PFN_vkDestroyInstance vkDestroyInstance_ptr{}; +} + +void Funcs::initialize() { + if (vkCreateInstance_ptr || vkDestroyInstance_ptr) { + Log::warn("lsfg-vk(funcs): Global Vulkan functions already initialized, did you call it twice?"); + return; + } + + auto* handle = Loader::DL::odlopen("libvulkan.so.1", 0x2); + vkCreateInstance_ptr = reinterpret_cast( + Loader::DL::odlsym(handle, "vkCreateInstance")); + vkDestroyInstance_ptr = reinterpret_cast( + Loader::DL::odlsym(handle, "vkDestroyInstance")); + if (!vkCreateInstance_ptr || !vkDestroyInstance_ptr) { + Log::error("lsfg-vk(funcs): Failed to initialize Vulkan functions, missing symbols"); + exit(EXIT_FAILURE); + } + + Log::debug("lsfg-vk(funcs): Initialized global Vulkan functions with original pointers"); +} + +// original function calls + +VkResult Funcs::vkCreateInstanceOriginal( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) { + return vkCreateInstance_ptr(pCreateInfo, pAllocator, pInstance); +} + +void Funcs::vkDestroyInstanceOriginal( + VkInstance instance, + const VkAllocationCallbacks* pAllocator) { + vkDestroyInstance_ptr(instance, pAllocator); +} From e9bee45a36aecd38e79a2d7f149fd3fbda7a3e81 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 09:37:29 +0200 Subject: [PATCH 046/253] second batch of original vulkan pointers alongside basic hooks --- CMakeLists.txt | 1 + include/vulkan/funcs.hpp | 31 +++++++++++++++++-- include/vulkan/hooks.hpp | 13 ++++++++ src/init.cpp | 8 +++++ src/vulkan/funcs.cpp | 44 +++++++++++++++++++++++++-- src/vulkan/hooks.cpp | 66 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 include/vulkan/hooks.hpp create mode 100644 src/vulkan/hooks.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b34403a..3a10293 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(lsfg-vk-gen) file(GLOB SOURCES "src/loader/*.cpp" + "src/vulkan/*.cpp" "src/*.cpp" ) diff --git a/include/vulkan/funcs.hpp b/include/vulkan/funcs.hpp index 9a2428a..0ad93d6 100644 --- a/include/vulkan/funcs.hpp +++ b/include/vulkan/funcs.hpp @@ -10,17 +10,44 @@ namespace Vulkan::Funcs { /// void initialize(); + /// + /// Initialize the instance Vulkan function pointers. + /// + /// @param instance The Vulkan instance to initialize. + /// + void initializeInstance(VkInstance instance); + + /// + /// Initialize the device Vulkan function pointers. + /// + /// @param device The Vulkan device to initialize. + /// + void initializeDevice(VkDevice device); + /// Call to the original vkCreateInstance function. - VkResult vkCreateInstanceOriginal( + VkResult ovkCreateInstance( const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance ); /// Call to the original vkDestroyInstance function. - void vkDestroyInstanceOriginal( + 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 + ); } #endif // FUNCS_HPP diff --git a/include/vulkan/hooks.hpp b/include/vulkan/hooks.hpp new file mode 100644 index 0000000..471a5d5 --- /dev/null +++ b/include/vulkan/hooks.hpp @@ -0,0 +1,13 @@ +#ifndef HOOKS_HPP +#define HOOKS_HPP + +namespace Vulkan::Hooks { + + /// + /// Install overrides for hooked Vulkan functions. + /// + void initialize(); + +} + +#endif // HOOKS_HPP diff --git a/src/init.cpp b/src/init.cpp index 345963f..d016dfe 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1,5 +1,7 @@ #include "loader/dl.hpp" #include "loader/vk.hpp" +#include "vulkan/funcs.hpp" +#include "vulkan/hooks.hpp" #include "log.hpp" extern "C" void __attribute__((constructor)) init(); @@ -11,6 +13,12 @@ void init() { // hook loaders Loader::DL::initialize(); Loader::VK::initialize(); + + // setup vulkan stuff + Vulkan::Funcs::initialize(); + Vulkan::Hooks::initialize(); + + Log::info("lsfg-vk: init() completed successfully"); } void deinit() { diff --git a/src/vulkan/funcs.cpp b/src/vulkan/funcs.cpp index 31edaf9..51a74bd 100644 --- a/src/vulkan/funcs.cpp +++ b/src/vulkan/funcs.cpp @@ -1,5 +1,6 @@ #include "vulkan/funcs.hpp" #include "loader/dl.hpp" +#include "loader/vk.hpp" #include "log.hpp" using namespace Vulkan; @@ -7,6 +8,9 @@ using namespace Vulkan; namespace { PFN_vkCreateInstance vkCreateInstance_ptr{}; PFN_vkDestroyInstance vkDestroyInstance_ptr{}; + + PFN_vkCreateDevice vkCreateDevice_ptr{}; + PFN_vkDestroyDevice vkDestroyDevice_ptr{}; } void Funcs::initialize() { @@ -28,17 +32,53 @@ void Funcs::initialize() { Log::debug("lsfg-vk(funcs): Initialized global Vulkan functions with original pointers"); } +void Funcs::initializeInstance(VkInstance instance) { + if (vkCreateDevice_ptr || vkDestroyDevice_ptr) { + Log::warn("lsfg-vk(funcs): Instance Vulkan functions already initialized, did you call it twice?"); + return; + } + + vkCreateDevice_ptr = reinterpret_cast( + Loader::VK::ovkGetInstanceProcAddr(instance, "vkCreateDevice")); + vkDestroyDevice_ptr = reinterpret_cast( + Loader::VK::ovkGetInstanceProcAddr(instance, "vkDestroyDevice")); + if (!vkCreateDevice_ptr || !vkDestroyDevice_ptr) { + Log::error("lsfg-vk(funcs): Failed to initialize Vulkan instance functions, missing symbols"); + exit(EXIT_FAILURE); + } + + Log::debug("lsfg-vk(funcs): Initialized instance Vulkan functions with original pointers"); +} + +void Funcs::initializeDevice(VkDevice device) { + Log::debug("lsfg-vk(funcs): Initialized device Vulkan functions with original pointers"); +} + // original function calls -VkResult Funcs::vkCreateInstanceOriginal( +VkResult Funcs::ovkCreateInstance( const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { return vkCreateInstance_ptr(pCreateInfo, pAllocator, pInstance); } -void Funcs::vkDestroyInstanceOriginal( +void Funcs::ovkDestroyInstance( VkInstance instance, const VkAllocationCallbacks* pAllocator) { vkDestroyInstance_ptr(instance, pAllocator); } + +VkResult Funcs::ovkCreateDevice( + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice) { + return vkCreateDevice_ptr(physicalDevice, pCreateInfo, pAllocator, pDevice); +} + +void Funcs::ovkDestroyDevice( + VkDevice device, + const VkAllocationCallbacks* pAllocator) { + vkDestroyDevice_ptr(device, pAllocator); +} diff --git a/src/vulkan/hooks.cpp b/src/vulkan/hooks.cpp new file mode 100644 index 0000000..aef658c --- /dev/null +++ b/src/vulkan/hooks.cpp @@ -0,0 +1,66 @@ +#include "vulkan/hooks.hpp" +#include "vulkan/funcs.hpp" +#include "loader/dl.hpp" +#include "loader/vk.hpp" +#include "log.hpp" + +#include + +using namespace Vulkan; + +namespace { + bool initialized{false}; + + VkResult myvkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) { + auto res = Funcs::ovkCreateInstance(pCreateInfo, pAllocator, pInstance); + + Funcs::initializeInstance(*pInstance); + + return res; + } + + VkResult myvkCreateDevice( + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice) { + auto res = Funcs::ovkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); + + Funcs::initializeDevice(*pDevice); + + return res; + } + +} + +void Hooks::initialize() { + if (initialized) { + Log::warn("Vulkan hooks already initialized, did you call it twice?"); + return; + } + + // register hooks to vulkan loader + Loader::VK::registerSymbol("vkCreateInstance", + reinterpret_cast(myvkCreateInstance)); + Loader::VK::registerSymbol("vkCreateDevice", + reinterpret_cast(myvkCreateDevice)); + + // register hooks to dynamic loader under libvulkan.so.1 + Loader::DL::File vk1("libvulkan.so.1"); + vk1.defineSymbol("vkCreateInstance", + reinterpret_cast(myvkCreateInstance)); + vk1.defineSymbol("vkCreateDevice", + reinterpret_cast(myvkCreateDevice)); + Loader::DL::registerFile(vk1); + + // register hooks to dynamic loader under libvulkan.so + Loader::DL::File vk2("libvulkan.so"); + vk2.defineSymbol("vkCreateInstance", + reinterpret_cast(myvkCreateInstance)); + vk2.defineSymbol("vkCreateDevice", + reinterpret_cast(myvkCreateDevice)); + Loader::DL::registerFile(vk2); +} From b4e00d2d650c01761165e56b22c41ed8bfbd3d4a Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 09:43:56 +0200 Subject: [PATCH 047/253] base application class --- include/application.hpp | 45 +++++++++++++++++++++++++++++++++++++++++ src/application.cpp | 9 +++++++++ src/vulkan/hooks.cpp | 44 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 include/application.hpp create mode 100644 src/application.cpp diff --git a/include/application.hpp b/include/application.hpp new file mode 100644 index 0000000..16f4d0a --- /dev/null +++ b/include/application.hpp @@ -0,0 +1,45 @@ +#ifndef APPLICATION_HPP +#define APPLICATION_HPP + +#include + +/// +/// Main application class, wrapping around the Vulkan device. +/// +class Application { +public: + + /// + /// Create the application. + /// + /// @param device Vulkan device + /// @param physicalDevice Vulkan physical device + /// + /// @throws std::invalid_argument if the device or physicalDevice is null. + /// @throws LSFG::vulkan_error if any Vulkan call fails. + /// + Application(VkDevice device, VkPhysicalDevice physicalDevice); + + /// Get the Vulkan device. + [[nodiscard]] VkDevice getDevice() const { return this->device; } + /// Get the Vulkan physical device. + [[nodiscard]] VkPhysicalDevice getPhysicalDevice() const { return this->physicalDevice; } + + // Non-copyable and non-movable + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + Application(Application&&) = delete; + Application& operator=(Application&&) = delete; + + /// Destructor, cleans up resources. + ~Application() = default; // no resources to clean up as of right now. +private: + // (non-owned resources) + VkDevice device; + VkPhysicalDevice physicalDevice; + + // (owned resources) + +}; + +#endif // APPLICATION_HPP diff --git a/src/application.cpp b/src/application.cpp new file mode 100644 index 0000000..b22b10c --- /dev/null +++ b/src/application.cpp @@ -0,0 +1,9 @@ +#include "application.hpp" + +#include + +Application::Application(VkDevice device, VkPhysicalDevice physicalDevice) + : device(device), physicalDevice(physicalDevice) { + if (device == VK_NULL_HANDLE || physicalDevice == VK_NULL_HANDLE) + throw std::invalid_argument("Invalid Vulkan device or physical device"); +} diff --git a/src/vulkan/hooks.cpp b/src/vulkan/hooks.cpp index aef658c..e711fca 100644 --- a/src/vulkan/hooks.cpp +++ b/src/vulkan/hooks.cpp @@ -2,14 +2,18 @@ #include "vulkan/funcs.hpp" #include "loader/dl.hpp" #include "loader/vk.hpp" +#include "application.hpp" #include "log.hpp" -#include +#include + +#include using namespace Vulkan; namespace { bool initialized{false}; + std::optional application; VkResult myvkCreateInstance( const VkInstanceCreateInfo* pCreateInfo, @@ -31,9 +35,41 @@ namespace { Funcs::initializeDevice(*pDevice); + // create the main application + if (application.has_value()) { + Log::error("Application already initialized, are you trying to create a second device?"); + exit(EXIT_FAILURE); + } + + try { + application.emplace(*pDevice, physicalDevice); + Log::info("lsfg-vk(hooks): Application created successfully"); + } catch (const LSFG::vulkan_error& e) { + Log::error("Encountered Vulkan error {:x} while creating application: {}", + static_cast(e.error()), e.what()); + exit(EXIT_FAILURE); + } catch (const std::exception& e) { + Log::error("Encountered error while creating application: {}", e.what()); + exit(EXIT_FAILURE); + } + return res; } + void myvkDestroyDevice( + VkDevice device, + const VkAllocationCallbacks* pAllocator) { + // destroy the main application + if (application.has_value()) { + application.reset(); + Log::info("lsfg-vk(hooks): Application destroyed successfully"); + } else { + Log::warn("lsfg-vk(hooks): No application to destroy, continuing"); + } + + Funcs::ovkDestroyDevice(device, pAllocator); + } + } void Hooks::initialize() { @@ -47,6 +83,8 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateInstance)); Loader::VK::registerSymbol("vkCreateDevice", reinterpret_cast(myvkCreateDevice)); + Loader::VK::registerSymbol("vkDestroyDevice", + reinterpret_cast(myvkDestroyDevice)); // register hooks to dynamic loader under libvulkan.so.1 Loader::DL::File vk1("libvulkan.so.1"); @@ -54,6 +92,8 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateInstance)); vk1.defineSymbol("vkCreateDevice", reinterpret_cast(myvkCreateDevice)); + vk1.defineSymbol("vkDestroyDevice", + reinterpret_cast(myvkDestroyDevice)); Loader::DL::registerFile(vk1); // register hooks to dynamic loader under libvulkan.so @@ -62,5 +102,7 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateInstance)); vk2.defineSymbol("vkCreateDevice", reinterpret_cast(myvkCreateDevice)); + vk2.defineSymbol("vkDestroyDevice", + reinterpret_cast(myvkDestroyDevice)); Loader::DL::registerFile(vk2); } From 59b05ce2201b44ad3213b2ebfbc0ba8b63341661 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 09:51:23 +0200 Subject: [PATCH 048/253] base swapchain context class --- CMakeLists.txt | 2 +- include/application.hpp | 76 +++++++++++++++++++++++++++++++++++++- include/vulkan/funcs.hpp | 14 +++++++ src/application.cpp | 30 +++++++++++++++ src/vulkan/funcs.cpp | 33 +++++++++++++++++ src/vulkan/hooks.cpp | 79 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 232 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a10293..d487f54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ add_library(lsfg-vk SHARED ${SOURCES}) target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk - PRIVATE lsfg-vk-gen) + PRIVATE lsfg-vk-gen vulkan) target_compile_options(lsfg-vk PRIVATE -Weverything # disable compat c++ flags diff --git a/include/application.hpp b/include/application.hpp index 16f4d0a..60bad94 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -1,14 +1,18 @@ #ifndef APPLICATION_HPP #define APPLICATION_HPP +#include +#include + #include +class SwapchainContext; + /// /// Main application class, wrapping around the Vulkan device. /// class Application { public: - /// /// Create the application. /// @@ -20,6 +24,31 @@ public: /// Application(VkDevice device, VkPhysicalDevice physicalDevice); + /// + /// Add a swapchain to the application. + /// + /// @param handle The Vulkan handle of the swapchain. + /// @param format The format of the swapchain. + /// @param extent The extent of the images. + /// @param images The swapchain images. + /// + /// @throws std::invalid_argument if the handle is already added. + /// @throws ls::vulkan_error if any Vulkan call fails. + /// + void addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, + const std::vector& images); + + /// + /// Remove a swapchain from the application. + /// + /// @param handle The Vulkan handle of the swapchain state to remove. + /// @return true if the swapchain was removed, false if it was already retired. + /// + /// @throws std::invalid_argument if the handle is null. + /// + bool removeSwapchain(VkSwapchainKHR handle); + + /// Get the Vulkan device. [[nodiscard]] VkDevice getDevice() const { return this->device; } /// Get the Vulkan physical device. @@ -39,7 +68,52 @@ private: VkPhysicalDevice physicalDevice; // (owned resources) + std::unordered_map swapchains; +}; +/// +/// An application's Vulkan swapchain and it's associated resources. +/// +class SwapchainContext { +public: + + /// + /// Create the swapchain context. + /// + /// @param swapchain The Vulkan swapchain handle. + /// @param format The format of the swapchain images. + /// @param extent The extent of the swapchain images. + /// @param images The swapchain images. + /// + /// @throws std::invalid_argument if any parameter is null + /// @throws ls::vulkan_error if any Vulkan call fails. + /// + SwapchainContext(VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, + const std::vector& images); + + /// Get the Vulkan swapchain handle. + [[nodiscard]] VkSwapchainKHR handle() const { return this->swapchain; } + /// Get the format of the swapchain images. + [[nodiscard]] VkFormat getFormat() const { return this->format; } + /// Get the extent of the swapchain images. + [[nodiscard]] VkExtent2D getExtent() const { return this->extent; } + /// Get the swapchain images. + [[nodiscard]] const std::vector& getImages() const { return this->images; } + + // Non-copyable, trivially moveable and destructible + SwapchainContext(const SwapchainContext&) = delete; + SwapchainContext& operator=(const SwapchainContext&) = delete; + SwapchainContext(SwapchainContext&&) = default; + SwapchainContext& operator=(SwapchainContext&&) = default; + ~SwapchainContext() = default; +private: + // (non-owned resources) + VkSwapchainKHR swapchain{}; + VkFormat format{}; + VkExtent2D extent{}; + std::vector images; + + // (owned resources) }; #endif // APPLICATION_HPP diff --git a/include/vulkan/funcs.hpp b/include/vulkan/funcs.hpp index 0ad93d6..a91d07c 100644 --- a/include/vulkan/funcs.hpp +++ b/include/vulkan/funcs.hpp @@ -48,6 +48,20 @@ namespace Vulkan::Funcs { VkDevice device, const VkAllocationCallbacks* pAllocator ); + + /// Call to the original vkCreateSwapchainKHR function. + VkResult ovkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain + ); + /// Call to the original vkDestroySwapchainKHR function. + void ovkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator + ); } #endif // FUNCS_HPP diff --git a/src/application.cpp b/src/application.cpp index b22b10c..7f06f81 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -7,3 +7,33 @@ Application::Application(VkDevice device, VkPhysicalDevice physicalDevice) if (device == VK_NULL_HANDLE || physicalDevice == VK_NULL_HANDLE) throw std::invalid_argument("Invalid Vulkan device or physical device"); } + +void Application::addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, + const std::vector& images) { + // add the swapchain handle + auto it = this->swapchains.find(handle); + if (it != this->swapchains.end()) + throw std::invalid_argument("Swapchain handle already exists"); + + // initialize the swapchain context + this->swapchains.emplace(handle, SwapchainContext(handle, format, extent, images)); +} + +SwapchainContext::SwapchainContext(VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, + const std::vector& images) + : swapchain(swapchain), format(format), extent(extent), images(images) { + if (swapchain == VK_NULL_HANDLE || format == VK_FORMAT_UNDEFINED) + throw std::invalid_argument("Invalid swapchain or images"); +} + +bool Application::removeSwapchain(VkSwapchainKHR handle) { + if (handle == VK_NULL_HANDLE) + throw std::invalid_argument("Invalid swapchain handle"); + + // remove the swapchain context + auto it = this->swapchains.find(handle); + if (it == this->swapchains.end()) + return false; // already retired... hopefully + this->swapchains.erase(it); + return true; +} diff --git a/src/vulkan/funcs.cpp b/src/vulkan/funcs.cpp index 51a74bd..226f602 100644 --- a/src/vulkan/funcs.cpp +++ b/src/vulkan/funcs.cpp @@ -2,6 +2,7 @@ #include "loader/dl.hpp" #include "loader/vk.hpp" #include "log.hpp" +#include using namespace Vulkan; @@ -11,6 +12,9 @@ namespace { PFN_vkCreateDevice vkCreateDevice_ptr{}; PFN_vkDestroyDevice vkDestroyDevice_ptr{}; + + PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR_ptr{}; + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR_ptr{}; } void Funcs::initialize() { @@ -51,6 +55,20 @@ void Funcs::initializeInstance(VkInstance instance) { } void Funcs::initializeDevice(VkDevice device) { + if (vkCreateSwapchainKHR_ptr || vkDestroySwapchainKHR_ptr) { + Log::warn("lsfg-vk(funcs): Device Vulkan functions already initialized, did you call it twice?"); + return; + } + + vkCreateSwapchainKHR_ptr = reinterpret_cast( + Loader::VK::ovkGetDeviceProcAddr(device, "vkCreateSwapchainKHR")); + vkDestroySwapchainKHR_ptr = reinterpret_cast( + Loader::VK::ovkGetDeviceProcAddr(device, "vkDestroySwapchainKHR")); + if (!vkCreateSwapchainKHR_ptr || !vkDestroySwapchainKHR_ptr) { + Log::error("lsfg-vk(funcs): Failed to initialize Vulkan device functions, missing symbols"); + exit(EXIT_FAILURE); + } + Log::debug("lsfg-vk(funcs): Initialized device Vulkan functions with original pointers"); } @@ -82,3 +100,18 @@ void Funcs::ovkDestroyDevice( const VkAllocationCallbacks* pAllocator) { vkDestroyDevice_ptr(device, pAllocator); } + +VkResult Funcs::ovkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain) { + return vkCreateSwapchainKHR_ptr(device, pCreateInfo, pAllocator, pSwapchain); +} + +void Funcs::ovkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator) { + vkDestroySwapchainKHR_ptr(device, swapchain, pAllocator); +} diff --git a/src/vulkan/hooks.cpp b/src/vulkan/hooks.cpp index e711fca..5799fe4 100644 --- a/src/vulkan/hooks.cpp +++ b/src/vulkan/hooks.cpp @@ -56,6 +56,73 @@ namespace { return res; } + VkResult myvkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain) { + auto res = Funcs::ovkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + + // add the swapchain to the application + if (!application.has_value()) { + Log::error("Application not initialized, cannot create swapchain"); + exit(EXIT_FAILURE); + } + + try { + if (pCreateInfo->oldSwapchain) { + if (!application->removeSwapchain(pCreateInfo->oldSwapchain)) + throw std::runtime_error("Failed to remove old swapchain"); + Log::info("lsfg-vk(hooks): Swapchain retired successfully"); + } + + uint32_t imageCount{}; + auto res = vkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr); + if (res != VK_SUCCESS || imageCount == 0) + throw LSFG::vulkan_error(res, "Failed to get swapchain images count"); + + std::vector swapchainImages(imageCount); + res = vkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, swapchainImages.data()); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to get swapchain images"); + + application->addSwapchain(*pSwapchain, + pCreateInfo->imageFormat, pCreateInfo->imageExtent, swapchainImages); + Log::info("lsfg-vk(hooks): Swapchain created successfully with {} images", + swapchainImages.size()); + } catch (const LSFG::vulkan_error& e) { + Log::error("Encountered Vulkan error {:x} while creating swapchain: {}", + static_cast(e.error()), e.what()); + exit(EXIT_FAILURE); + } catch (const std::exception& e) { + Log::error("Encountered error while creating swapchain: {}", e.what()); + exit(EXIT_FAILURE); + } + + return res; + } + + void myvkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator) { + if (!application.has_value()) { + Log::error("Application not initialized, cannot destroy swapchain"); + exit(EXIT_FAILURE); + } + + // remove the swapchain from the application + try { + if (application->removeSwapchain(swapchain)) + Log::info("lsfg-vk(hooks): Swapchain retired successfully"); + } catch (const std::exception& e) { + Log::error("Encountered error while removing swapchain: {}", e.what()); + exit(EXIT_FAILURE); + } + + Funcs::ovkDestroySwapchainKHR(device, swapchain, pAllocator); + } + void myvkDestroyDevice( VkDevice device, const VkAllocationCallbacks* pAllocator) { @@ -85,6 +152,10 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateDevice)); Loader::VK::registerSymbol("vkDestroyDevice", reinterpret_cast(myvkDestroyDevice)); + Loader::VK::registerSymbol("vkCreateSwapchainKHR", + reinterpret_cast(myvkCreateSwapchainKHR)); + Loader::VK::registerSymbol("vkDestroySwapchainKHR", + reinterpret_cast(myvkDestroySwapchainKHR)); // register hooks to dynamic loader under libvulkan.so.1 Loader::DL::File vk1("libvulkan.so.1"); @@ -94,6 +165,10 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateDevice)); vk1.defineSymbol("vkDestroyDevice", reinterpret_cast(myvkDestroyDevice)); + vk1.defineSymbol("vkCreateSwapchainKHR", + reinterpret_cast(myvkCreateSwapchainKHR)); + vk1.defineSymbol("vkDestroySwapchainKHR", + reinterpret_cast(myvkDestroySwapchainKHR)); Loader::DL::registerFile(vk1); // register hooks to dynamic loader under libvulkan.so @@ -104,5 +179,9 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateDevice)); vk2.defineSymbol("vkDestroyDevice", reinterpret_cast(myvkDestroyDevice)); + vk2.defineSymbol("vkCreateSwapchainKHR", + reinterpret_cast(myvkCreateSwapchainKHR)); + vk2.defineSymbol("vkDestroySwapchainKHR", + reinterpret_cast(myvkDestroySwapchainKHR)); Loader::DL::registerFile(vk2); } From eb1cee1355c922525cf5f332d1e6f3038ad9361b Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 09:53:48 +0200 Subject: [PATCH 049/253] link statically with Vulkan --- CMakeLists.txt | 1 - include/{vulkan => }/hooks.hpp | 2 +- include/vulkan/funcs.hpp | 67 ------------------- src/{vulkan => }/hooks.cpp | 32 ++------- src/init.cpp | 8 +-- src/vulkan/funcs.cpp | 117 --------------------------------- 6 files changed, 10 insertions(+), 217 deletions(-) rename include/{vulkan => }/hooks.hpp (85%) delete mode 100644 include/vulkan/funcs.hpp rename src/{vulkan => }/hooks.cpp (85%) delete mode 100644 src/vulkan/funcs.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d487f54..5a8a0f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,6 @@ add_subdirectory(lsfg-vk-gen) file(GLOB SOURCES "src/loader/*.cpp" - "src/vulkan/*.cpp" "src/*.cpp" ) diff --git a/include/vulkan/hooks.hpp b/include/hooks.hpp similarity index 85% rename from include/vulkan/hooks.hpp rename to include/hooks.hpp index 471a5d5..f4ba294 100644 --- a/include/vulkan/hooks.hpp +++ b/include/hooks.hpp @@ -1,7 +1,7 @@ #ifndef HOOKS_HPP #define HOOKS_HPP -namespace Vulkan::Hooks { +namespace Hooks { /// /// Install overrides for hooked Vulkan functions. diff --git a/include/vulkan/funcs.hpp b/include/vulkan/funcs.hpp deleted file mode 100644 index a91d07c..0000000 --- a/include/vulkan/funcs.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef FUNCS_HPP -#define FUNCS_HPP - -#include - -namespace Vulkan::Funcs { - - /// - /// Initialize the global Vulkan function pointers. - /// - void initialize(); - - /// - /// Initialize the instance Vulkan function pointers. - /// - /// @param instance The Vulkan instance to initialize. - /// - void initializeInstance(VkInstance instance); - - /// - /// Initialize the device Vulkan function pointers. - /// - /// @param device The Vulkan device to initialize. - /// - void initializeDevice(VkDevice device); - - /// 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 vkCreateSwapchainKHR function. - VkResult ovkCreateSwapchainKHR( - VkDevice device, - const VkSwapchainCreateInfoKHR* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkSwapchainKHR* pSwapchain - ); - /// Call to the original vkDestroySwapchainKHR function. - void ovkDestroySwapchainKHR( - VkDevice device, - VkSwapchainKHR swapchain, - const VkAllocationCallbacks* pAllocator - ); -} - -#endif // FUNCS_HPP diff --git a/src/vulkan/hooks.cpp b/src/hooks.cpp similarity index 85% rename from src/vulkan/hooks.cpp rename to src/hooks.cpp index 5799fe4..518aaf0 100644 --- a/src/vulkan/hooks.cpp +++ b/src/hooks.cpp @@ -1,5 +1,4 @@ -#include "vulkan/hooks.hpp" -#include "vulkan/funcs.hpp" +#include "hooks.hpp" #include "loader/dl.hpp" #include "loader/vk.hpp" #include "application.hpp" @@ -9,31 +8,18 @@ #include -using namespace Vulkan; +using namespace Hooks; namespace { bool initialized{false}; std::optional application; - VkResult myvkCreateInstance( - const VkInstanceCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkInstance* pInstance) { - auto res = Funcs::ovkCreateInstance(pCreateInfo, pAllocator, pInstance); - - Funcs::initializeInstance(*pInstance); - - return res; - } - VkResult myvkCreateDevice( VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { - auto res = Funcs::ovkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); - - Funcs::initializeDevice(*pDevice); + auto res = vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); // create the main application if (application.has_value()) { @@ -61,7 +47,7 @@ namespace { const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain) { - auto res = Funcs::ovkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + auto res = vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); // add the swapchain to the application if (!application.has_value()) { @@ -120,7 +106,7 @@ namespace { exit(EXIT_FAILURE); } - Funcs::ovkDestroySwapchainKHR(device, swapchain, pAllocator); + vkDestroySwapchainKHR(device, swapchain, pAllocator); } void myvkDestroyDevice( @@ -134,7 +120,7 @@ namespace { Log::warn("lsfg-vk(hooks): No application to destroy, continuing"); } - Funcs::ovkDestroyDevice(device, pAllocator); + vkDestroyDevice(device, pAllocator); } } @@ -146,8 +132,6 @@ void Hooks::initialize() { } // register hooks to vulkan loader - Loader::VK::registerSymbol("vkCreateInstance", - reinterpret_cast(myvkCreateInstance)); Loader::VK::registerSymbol("vkCreateDevice", reinterpret_cast(myvkCreateDevice)); Loader::VK::registerSymbol("vkDestroyDevice", @@ -159,8 +143,6 @@ void Hooks::initialize() { // register hooks to dynamic loader under libvulkan.so.1 Loader::DL::File vk1("libvulkan.so.1"); - vk1.defineSymbol("vkCreateInstance", - reinterpret_cast(myvkCreateInstance)); vk1.defineSymbol("vkCreateDevice", reinterpret_cast(myvkCreateDevice)); vk1.defineSymbol("vkDestroyDevice", @@ -173,8 +155,6 @@ void Hooks::initialize() { // register hooks to dynamic loader under libvulkan.so Loader::DL::File vk2("libvulkan.so"); - vk2.defineSymbol("vkCreateInstance", - reinterpret_cast(myvkCreateInstance)); vk2.defineSymbol("vkCreateDevice", reinterpret_cast(myvkCreateDevice)); vk2.defineSymbol("vkDestroyDevice", diff --git a/src/init.cpp b/src/init.cpp index d016dfe..3127d86 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1,7 +1,6 @@ #include "loader/dl.hpp" #include "loader/vk.hpp" -#include "vulkan/funcs.hpp" -#include "vulkan/hooks.hpp" +#include "hooks.hpp" #include "log.hpp" extern "C" void __attribute__((constructor)) init(); @@ -14,9 +13,8 @@ void init() { Loader::DL::initialize(); Loader::VK::initialize(); - // setup vulkan stuff - Vulkan::Funcs::initialize(); - Vulkan::Hooks::initialize(); + // setup hooks + Hooks::initialize(); Log::info("lsfg-vk: init() completed successfully"); } diff --git a/src/vulkan/funcs.cpp b/src/vulkan/funcs.cpp deleted file mode 100644 index 226f602..0000000 --- a/src/vulkan/funcs.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "vulkan/funcs.hpp" -#include "loader/dl.hpp" -#include "loader/vk.hpp" -#include "log.hpp" -#include - -using namespace Vulkan; - -namespace { - PFN_vkCreateInstance vkCreateInstance_ptr{}; - PFN_vkDestroyInstance vkDestroyInstance_ptr{}; - - PFN_vkCreateDevice vkCreateDevice_ptr{}; - PFN_vkDestroyDevice vkDestroyDevice_ptr{}; - - PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR_ptr{}; - PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR_ptr{}; -} - -void Funcs::initialize() { - if (vkCreateInstance_ptr || vkDestroyInstance_ptr) { - Log::warn("lsfg-vk(funcs): Global Vulkan functions already initialized, did you call it twice?"); - return; - } - - auto* handle = Loader::DL::odlopen("libvulkan.so.1", 0x2); - vkCreateInstance_ptr = reinterpret_cast( - Loader::DL::odlsym(handle, "vkCreateInstance")); - vkDestroyInstance_ptr = reinterpret_cast( - Loader::DL::odlsym(handle, "vkDestroyInstance")); - if (!vkCreateInstance_ptr || !vkDestroyInstance_ptr) { - Log::error("lsfg-vk(funcs): Failed to initialize Vulkan functions, missing symbols"); - exit(EXIT_FAILURE); - } - - Log::debug("lsfg-vk(funcs): Initialized global Vulkan functions with original pointers"); -} - -void Funcs::initializeInstance(VkInstance instance) { - if (vkCreateDevice_ptr || vkDestroyDevice_ptr) { - Log::warn("lsfg-vk(funcs): Instance Vulkan functions already initialized, did you call it twice?"); - return; - } - - vkCreateDevice_ptr = reinterpret_cast( - Loader::VK::ovkGetInstanceProcAddr(instance, "vkCreateDevice")); - vkDestroyDevice_ptr = reinterpret_cast( - Loader::VK::ovkGetInstanceProcAddr(instance, "vkDestroyDevice")); - if (!vkCreateDevice_ptr || !vkDestroyDevice_ptr) { - Log::error("lsfg-vk(funcs): Failed to initialize Vulkan instance functions, missing symbols"); - exit(EXIT_FAILURE); - } - - Log::debug("lsfg-vk(funcs): Initialized instance Vulkan functions with original pointers"); -} - -void Funcs::initializeDevice(VkDevice device) { - if (vkCreateSwapchainKHR_ptr || vkDestroySwapchainKHR_ptr) { - Log::warn("lsfg-vk(funcs): Device Vulkan functions already initialized, did you call it twice?"); - return; - } - - vkCreateSwapchainKHR_ptr = reinterpret_cast( - Loader::VK::ovkGetDeviceProcAddr(device, "vkCreateSwapchainKHR")); - vkDestroySwapchainKHR_ptr = reinterpret_cast( - Loader::VK::ovkGetDeviceProcAddr(device, "vkDestroySwapchainKHR")); - if (!vkCreateSwapchainKHR_ptr || !vkDestroySwapchainKHR_ptr) { - Log::error("lsfg-vk(funcs): Failed to initialize Vulkan device functions, missing symbols"); - exit(EXIT_FAILURE); - } - - Log::debug("lsfg-vk(funcs): Initialized device Vulkan functions with original pointers"); -} - -// original function calls - -VkResult Funcs::ovkCreateInstance( - const VkInstanceCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkInstance* pInstance) { - return vkCreateInstance_ptr(pCreateInfo, pAllocator, pInstance); -} - -void Funcs::ovkDestroyInstance( - VkInstance instance, - const VkAllocationCallbacks* pAllocator) { - vkDestroyInstance_ptr(instance, pAllocator); -} - -VkResult Funcs::ovkCreateDevice( - VkPhysicalDevice physicalDevice, - const VkDeviceCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkDevice* pDevice) { - return vkCreateDevice_ptr(physicalDevice, pCreateInfo, pAllocator, pDevice); -} - -void Funcs::ovkDestroyDevice( - VkDevice device, - const VkAllocationCallbacks* pAllocator) { - vkDestroyDevice_ptr(device, pAllocator); -} - -VkResult Funcs::ovkCreateSwapchainKHR( - VkDevice device, - const VkSwapchainCreateInfoKHR* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkSwapchainKHR* pSwapchain) { - return vkCreateSwapchainKHR_ptr(device, pCreateInfo, pAllocator, pSwapchain); -} - -void Funcs::ovkDestroySwapchainKHR( - VkDevice device, - VkSwapchainKHR swapchain, - const VkAllocationCallbacks* pAllocator) { - vkDestroySwapchainKHR_ptr(device, swapchain, pAllocator); -} From 13076e9fe3522ee5545a573e5498f1015ed5e5a6 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 09:59:24 +0200 Subject: [PATCH 050/253] simple presenting logic --- include/application.hpp | 19 +++++++++++++++++-- src/application.cpp | 29 +++++++++++++++++++++++++++++ src/hooks.cpp | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/include/application.hpp b/include/application.hpp index 60bad94..0556b5b 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -33,11 +33,26 @@ public: /// @param images The swapchain images. /// /// @throws std::invalid_argument if the handle is already added. - /// @throws ls::vulkan_error if any Vulkan call fails. + /// @throws LSFG::vulkan_error if any Vulkan call fails. /// void addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, const std::vector& images); + /// + /// Present the next frame on a given swapchain. + /// + /// @param handle The Vulkan handle of the swapchain to present on. + /// @param queue The Vulkan queue to present the frame on. + /// @param semaphores The semaphores to wait on before presenting. + /// @param idx The index of the swapchain image to present. + /// + /// @throws std::invalid_argument if the handle is not found. + /// @throws LSFG::vulkan_error if any Vulkan call fails. + /// + void presentSwapchain(VkSwapchainKHR handle, VkQueue queue, + const std::vector& semaphores, uint32_t idx); + + /// /// Remove a swapchain from the application. /// @@ -86,7 +101,7 @@ public: /// @param images The swapchain images. /// /// @throws std::invalid_argument if any parameter is null - /// @throws ls::vulkan_error if any Vulkan call fails. + /// @throws LSFG::vulkan_error if any Vulkan call fails. /// SwapchainContext(VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, const std::vector& images); diff --git a/src/application.cpp b/src/application.cpp index 7f06f81..3a69504 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,5 +1,7 @@ #include "application.hpp" +#include + #include Application::Application(VkDevice device, VkPhysicalDevice physicalDevice) @@ -19,6 +21,33 @@ void Application::addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2 this->swapchains.emplace(handle, SwapchainContext(handle, format, extent, images)); } +void Application::presentSwapchain(VkSwapchainKHR handle, VkQueue queue, + const std::vector& semaphores, uint32_t idx) { + if (handle == VK_NULL_HANDLE) + throw std::invalid_argument("Invalid swapchain handle"); + + // find the swapchain context + auto it = this->swapchains.find(handle); + if (it == this->swapchains.end()) + throw std::logic_error("Swapchain not found"); + + // TODO: present + const VkPresentInfoKHR presentInfo = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = nullptr, + .waitSemaphoreCount = static_cast(semaphores.size()), + .pWaitSemaphores = semaphores.data(), + .swapchainCount = 1, + .pSwapchains = &handle, + .pImageIndices = &idx, + .pResults = nullptr // can be null if not needed + }; + auto res = vkQueuePresentKHR(queue, &presentInfo); + if (res != VK_SUCCESS) // do NOT check VK_SUBOPTIMAL_KHR + throw LSFG::vulkan_error(res, "Failed to present swapchain"); +} + + SwapchainContext::SwapchainContext(VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, const std::vector& images) : swapchain(swapchain), format(format), extent(extent), images(images) { diff --git a/src/hooks.cpp b/src/hooks.cpp index 518aaf0..40773a1 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -88,6 +88,36 @@ namespace { return res; } + VkResult myvkQueuePresentKHR( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo) { + if (!application.has_value()) { + Log::error("Application not initialized, cannot present frame"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + // present the next frame + try { + std::vector waitSemaphores(pPresentInfo->waitSemaphoreCount); + std::copy_n(pPresentInfo->pWaitSemaphores, waitSemaphores.size(), waitSemaphores.data()); + + application->presentSwapchain(*pPresentInfo->pSwapchains, + queue, waitSemaphores, *pPresentInfo->pImageIndices); + + Log::info("lsfg-vk(hooks): Frame presented successfully"); + } catch (const LSFG::vulkan_error& e) { + Log::error("Encountered Vulkan error {:x} while presenting: {}", + static_cast(e.error()), e.what()); + return e.error(); // do not exit + } catch (const std::exception& e) { + Log::error("Encountered error while creating presenting: {}", e.what()); + exit(EXIT_FAILURE); + } + + return VK_SUCCESS; + } + + void myvkDestroySwapchainKHR( VkDevice device, VkSwapchainKHR swapchain, @@ -140,6 +170,8 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateSwapchainKHR)); Loader::VK::registerSymbol("vkDestroySwapchainKHR", reinterpret_cast(myvkDestroySwapchainKHR)); + Loader::VK::registerSymbol("vkQueuePresentKHR", + reinterpret_cast(myvkQueuePresentKHR)); // register hooks to dynamic loader under libvulkan.so.1 Loader::DL::File vk1("libvulkan.so.1"); @@ -151,6 +183,8 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateSwapchainKHR)); vk1.defineSymbol("vkDestroySwapchainKHR", reinterpret_cast(myvkDestroySwapchainKHR)); + vk1.defineSymbol("vkQueuePresentKHR", + reinterpret_cast(myvkQueuePresentKHR)); Loader::DL::registerFile(vk1); // register hooks to dynamic loader under libvulkan.so @@ -163,5 +197,7 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateSwapchainKHR)); vk2.defineSymbol("vkDestroySwapchainKHR", reinterpret_cast(myvkDestroySwapchainKHR)); + vk2.defineSymbol("vkQueuePresentKHR", + reinterpret_cast(myvkQueuePresentKHR)); Loader::DL::registerFile(vk2); } From 83dde594c63defb15970c77a9031c7e529e375d7 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 10:10:53 +0200 Subject: [PATCH 051/253] some reshuffling --- include/application.hpp | 37 ++++++++++++++++++++++--------- src/application.cpp | 49 +++++++++++++++-------------------------- src/hooks.cpp | 35 ++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 43 deletions(-) diff --git a/include/application.hpp b/include/application.hpp index 0556b5b..7dfc341 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -18,11 +18,13 @@ public: /// /// @param device Vulkan device /// @param physicalDevice Vulkan physical device + /// @param graphicsQueue Vulkan queue for graphics operations + /// @param presentQueue Vulkan queue for presentation operations /// - /// @throws std::invalid_argument if the device or physicalDevice is null. /// @throws LSFG::vulkan_error if any Vulkan call fails. /// - Application(VkDevice device, VkPhysicalDevice physicalDevice); + Application(VkDevice device, VkPhysicalDevice physicalDevice, + VkQueue graphicsQueue, VkQueue presentQueue); /// /// Add a swapchain to the application. @@ -32,7 +34,6 @@ public: /// @param extent The extent of the images. /// @param images The swapchain images. /// - /// @throws std::invalid_argument if the handle is already added. /// @throws LSFG::vulkan_error if any Vulkan call fails. /// void addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, @@ -46,28 +47,27 @@ public: /// @param semaphores The semaphores to wait on before presenting. /// @param idx The index of the swapchain image to present. /// - /// @throws std::invalid_argument if the handle is not found. /// @throws LSFG::vulkan_error if any Vulkan call fails. /// void presentSwapchain(VkSwapchainKHR handle, VkQueue queue, const std::vector& semaphores, uint32_t idx); - /// /// Remove a swapchain from the application. /// /// @param handle The Vulkan handle of the swapchain state to remove. /// @return true if the swapchain was removed, false if it was already retired. /// - /// @throws std::invalid_argument if the handle is null. - /// bool removeSwapchain(VkSwapchainKHR handle); - /// Get the Vulkan device. [[nodiscard]] VkDevice getDevice() const { return this->device; } /// Get the Vulkan physical device. [[nodiscard]] VkPhysicalDevice getPhysicalDevice() const { return this->physicalDevice; } + /// Get the Vulkan graphics queue. + [[nodiscard]] VkQueue getGraphicsQueue() const { return this->graphicsQueue; } + /// Get the Vulkan present queue. + [[nodiscard]] VkQueue getPresentQueue() const { return this->presentQueue; } // Non-copyable and non-movable Application(const Application&) = delete; @@ -81,6 +81,8 @@ private: // (non-owned resources) VkDevice device; VkPhysicalDevice physicalDevice; + VkQueue graphicsQueue; + VkQueue presentQueue; // (owned resources) std::unordered_map swapchains; @@ -95,16 +97,29 @@ public: /// /// Create the swapchain context. /// + /// @param app The application context to use. /// @param swapchain The Vulkan swapchain handle. /// @param format The format of the swapchain images. /// @param extent The extent of the swapchain images. /// @param images The swapchain images. /// - /// @throws std::invalid_argument if any parameter is null /// @throws LSFG::vulkan_error if any Vulkan call fails. /// - SwapchainContext(VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, - const std::vector& images); + SwapchainContext(const Application& app, VkSwapchainKHR swapchain, + VkFormat format, VkExtent2D extent, const std::vector& images); + + /// + /// Present the next frame + /// + /// @param app The application context to use + /// @param queue The Vulkan queue to present the frame on. + /// @param semaphores The semaphores to wait on before presenting. + /// @param idx The index of the swapchain image to present. + /// + /// @throws LSFG::vulkan_error if any Vulkan call fails. + /// + void present(const Application& app, VkQueue queue, + const std::vector& semaphores, uint32_t idx); /// Get the Vulkan swapchain handle. [[nodiscard]] VkSwapchainKHR handle() const { return this->swapchain; } diff --git a/src/application.cpp b/src/application.cpp index 3a69504..f6216eb 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -4,62 +4,49 @@ #include -Application::Application(VkDevice device, VkPhysicalDevice physicalDevice) - : device(device), physicalDevice(physicalDevice) { - if (device == VK_NULL_HANDLE || physicalDevice == VK_NULL_HANDLE) - throw std::invalid_argument("Invalid Vulkan device or physical device"); -} +Application::Application(VkDevice device, VkPhysicalDevice physicalDevice, + VkQueue graphicsQueue, VkQueue presentQueue) + : device(device), physicalDevice(physicalDevice), + graphicsQueue(graphicsQueue), presentQueue(presentQueue) {} void Application::addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, const std::vector& images) { - // add the swapchain handle auto it = this->swapchains.find(handle); if (it != this->swapchains.end()) - throw std::invalid_argument("Swapchain handle already exists"); + return; // already added - // initialize the swapchain context - this->swapchains.emplace(handle, SwapchainContext(handle, format, extent, images)); + this->swapchains.emplace(handle, SwapchainContext(*this, handle, format, extent, images)); } +SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapchain, + VkFormat format, VkExtent2D extent, const std::vector& images) + : swapchain(swapchain), format(format), extent(extent), images(images) {} + void Application::presentSwapchain(VkSwapchainKHR handle, VkQueue queue, const std::vector& semaphores, uint32_t idx) { - if (handle == VK_NULL_HANDLE) - throw std::invalid_argument("Invalid swapchain handle"); - - // find the swapchain context auto it = this->swapchains.find(handle); if (it == this->swapchains.end()) throw std::logic_error("Swapchain not found"); - // TODO: present + it->second.present(*this, queue, semaphores, idx); +} + +void SwapchainContext::present(const Application& app, VkQueue queue, + const std::vector& semaphores, uint32_t idx) { const VkPresentInfoKHR presentInfo = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .pNext = nullptr, .waitSemaphoreCount = static_cast(semaphores.size()), .pWaitSemaphores = semaphores.data(), .swapchainCount = 1, - .pSwapchains = &handle, - .pImageIndices = &idx, - .pResults = nullptr // can be null if not needed + .pSwapchains = &this->swapchain, + .pImageIndices = &idx }; auto res = vkQueuePresentKHR(queue, &presentInfo); - if (res != VK_SUCCESS) // do NOT check VK_SUBOPTIMAL_KHR + if (res != VK_SUCCESS) // FIXME: somehow return VK_SUBOPTIMAL_KHR throw LSFG::vulkan_error(res, "Failed to present swapchain"); } - -SwapchainContext::SwapchainContext(VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, - const std::vector& images) - : swapchain(swapchain), format(format), extent(extent), images(images) { - if (swapchain == VK_NULL_HANDLE || format == VK_FORMAT_UNDEFINED) - throw std::invalid_argument("Invalid swapchain or images"); -} - bool Application::removeSwapchain(VkSwapchainKHR handle) { - if (handle == VK_NULL_HANDLE) - throw std::invalid_argument("Invalid swapchain handle"); - - // remove the swapchain context auto it = this->swapchains.find(handle); if (it == this->swapchains.end()) return false; // already retired... hopefully diff --git a/src/hooks.cpp b/src/hooks.cpp index 40773a1..3147cd9 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -21,6 +21,39 @@ namespace { VkDevice* pDevice) { auto res = vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); + // extract graphics and present queues + std::vector queueCreateInfos(pCreateInfo->queueCreateInfoCount); + std::copy_n(pCreateInfo->pQueueCreateInfos, queueCreateInfos.size(), queueCreateInfos.data()); + + uint32_t familyCount{}; + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr); + std::vector families(familyCount); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, families.data()); + + std::optional graphicsFamilyIdx; + std::optional presentFamilyIdx; + for (uint32_t i = 0; i < families.size(); ++i) { + auto it = std::ranges::find_if(queueCreateInfos, + [i](const VkDeviceQueueCreateInfo& info) { + return info.queueFamilyIndex == i; + }) ; + if (it == queueCreateInfos.end()) + continue; // skip if this family is not used by the device + if (families.at(i).queueFlags & VK_QUEUE_GRAPHICS_BIT) + graphicsFamilyIdx.emplace(i); + if (families.at(i).queueFlags & VK_QUEUE_COMPUTE_BIT) + presentFamilyIdx.emplace(i); + } + if (!graphicsFamilyIdx.has_value() || !presentFamilyIdx.has_value()) { + Log::error("No suitable queue family found for graphics or present"); + exit(EXIT_FAILURE); + } + + VkQueue graphicsQueue{}; + vkGetDeviceQueue(*pDevice, *graphicsFamilyIdx, 0, &graphicsQueue); + VkQueue presentQueue{}; + vkGetDeviceQueue(*pDevice, *presentFamilyIdx, 0, &presentQueue); + // create the main application if (application.has_value()) { Log::error("Application already initialized, are you trying to create a second device?"); @@ -28,7 +61,7 @@ namespace { } try { - application.emplace(*pDevice, physicalDevice); + application.emplace(*pDevice, physicalDevice, graphicsQueue, presentQueue); Log::info("lsfg-vk(hooks): Application created successfully"); } catch (const LSFG::vulkan_error& e) { Log::error("Encountered Vulkan error {:x} while creating application: {}", From 4e6d3deaac1b77c5c30e73ea9ec4dadf51c14a9e Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 10:13:38 +0200 Subject: [PATCH 052/253] add required extensions --- src/hooks.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/hooks.cpp b/src/hooks.cpp index 3147cd9..e908dcb 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -14,12 +14,53 @@ namespace { bool initialized{false}; std::optional application; + VkResult myvkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) { + // add extensions + std::vector extensions(pCreateInfo->enabledExtensionCount); + std::copy_n(pCreateInfo->ppEnabledExtensionNames, extensions.size(), extensions.data()); + + const std::vector requiredExtensions = { + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME + }; + for (const auto& ext : requiredExtensions) { + auto it = std::ranges::find(extensions, ext); + if (it == extensions.end()) + extensions.push_back(ext); + } + + VkInstanceCreateInfo createInfo = *pCreateInfo; + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + return vkCreateInstance(&createInfo, pAllocator, pInstance); + } + VkResult myvkCreateDevice( VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { - auto res = vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); + // add extensions + std::vector extensions(pCreateInfo->enabledExtensionCount); + std::copy_n(pCreateInfo->ppEnabledExtensionNames, extensions.size(), extensions.data()); + + const std::vector requiredExtensions = { + VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME + }; + for (const auto& ext : requiredExtensions) { + auto it = std::ranges::find(extensions, ext); + if (it == extensions.end()) + extensions.push_back(ext); + } + + VkDeviceCreateInfo createInfo = *pCreateInfo; + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + auto res = vkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice); // extract graphics and present queues std::vector queueCreateInfos(pCreateInfo->queueCreateInfoCount); @@ -150,7 +191,6 @@ namespace { return VK_SUCCESS; } - void myvkDestroySwapchainKHR( VkDevice device, VkSwapchainKHR swapchain, @@ -195,6 +235,8 @@ void Hooks::initialize() { } // register hooks to vulkan loader + Loader::VK::registerSymbol("vkCreateInstance", + reinterpret_cast(myvkCreateInstance)); Loader::VK::registerSymbol("vkCreateDevice", reinterpret_cast(myvkCreateDevice)); Loader::VK::registerSymbol("vkDestroyDevice", @@ -208,6 +250,8 @@ void Hooks::initialize() { // register hooks to dynamic loader under libvulkan.so.1 Loader::DL::File vk1("libvulkan.so.1"); + vk1.defineSymbol("vkCreateInstance", + reinterpret_cast(myvkCreateInstance)); vk1.defineSymbol("vkCreateDevice", reinterpret_cast(myvkCreateDevice)); vk1.defineSymbol("vkDestroyDevice", @@ -222,6 +266,8 @@ void Hooks::initialize() { // register hooks to dynamic loader under libvulkan.so Loader::DL::File vk2("libvulkan.so"); + vk2.defineSymbol("vkCreateInstance", + reinterpret_cast(myvkCreateInstance)); vk2.defineSymbol("vkCreateDevice", reinterpret_cast(myvkCreateDevice)); vk2.defineSymbol("vkDestroyDevice", From 969fcfadeb353b9446670608c9d0520ca7313f65 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 10:48:20 +0200 Subject: [PATCH 053/253] implement lsfg base --- CMakeLists.txt | 1 + include/application.hpp | 18 +++-- include/mini/image.hpp | 73 ++++++++++++++++++++ lsfg-vk-gen/src/lsfg.cpp | 5 ++ src/application.cpp | 40 +++++++++-- src/mini/image.cpp | 142 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 268 insertions(+), 11 deletions(-) create mode 100644 include/mini/image.hpp create mode 100644 src/mini/image.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a8a0f8..0fb353c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(lsfg-vk-gen) file(GLOB SOURCES "src/loader/*.cpp" + "src/mini/*.cpp" "src/*.cpp" ) diff --git a/include/application.hpp b/include/application.hpp index 7dfc341..045982a 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -1,6 +1,7 @@ #ifndef APPLICATION_HPP #define APPLICATION_HPP +#include "mini/image.hpp" #include #include @@ -76,7 +77,7 @@ public: Application& operator=(Application&&) = delete; /// Destructor, cleans up resources. - ~Application() = default; // no resources to clean up as of right now. + ~Application(); private: // (non-owned resources) VkDevice device; @@ -93,7 +94,6 @@ private: /// class SwapchainContext { public: - /// /// Create the swapchain context. /// @@ -130,20 +130,24 @@ public: /// Get the swapchain images. [[nodiscard]] const std::vector& getImages() const { return this->images; } - // Non-copyable, trivially moveable and destructible + // Non-copyable, trivially moveable SwapchainContext(const SwapchainContext&) = delete; SwapchainContext& operator=(const SwapchainContext&) = delete; SwapchainContext(SwapchainContext&&) = default; SwapchainContext& operator=(SwapchainContext&&) = default; - ~SwapchainContext() = default; + + /// Destructor, cleans up resources. + ~SwapchainContext(); private: // (non-owned resources) - VkSwapchainKHR swapchain{}; - VkFormat format{}; - VkExtent2D extent{}; + VkSwapchainKHR swapchain; + VkFormat format; + VkExtent2D extent; std::vector images; // (owned resources) + Mini::Image frame_0, frame_1; + int32_t lsfgId; }; #endif // APPLICATION_HPP diff --git a/include/mini/image.hpp b/include/mini/image.hpp new file mode 100644 index 0000000..ed4d182 --- /dev/null +++ b/include/mini/image.hpp @@ -0,0 +1,73 @@ +#ifndef IMAGE_HPP +#define IMAGE_HPP + +#include + +#include + +namespace Mini { + + /// + /// C++ wrapper class for a Vulkan image. + /// + /// This class manages the lifetime of a Vulkan image. + /// + class Image { + public: + Image() noexcept = default; + + /// + /// Create the image and export the backing fd + /// + /// @param device Vulkan device + /// @param physicalDevice Vulkan physical device + /// @param extent Extent of the image in pixels. + /// @param format Vulkan format of the image + /// @param usage Usage flags for the image + /// @param aspectFlags Aspect flags for the image view + /// @param fd Pointer to an integer where the file descriptor will be stored. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Image(VkDevice device, VkPhysicalDevice physicalDevice, VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->image; } + /// Get the Vulkan device memory handle. + [[nodiscard]] auto getMemory() const { return *this->memory; } + /// Get the Vulkan image view handle. + [[nodiscard]] auto getView() const { return *this->view; } + /// Get the extent of the image. + [[nodiscard]] VkExtent2D getExtent() const { return this->extent; } + /// Get the format of the image. + [[nodiscard]] VkFormat getFormat() const { return this->format; } + /// Get the aspect flags of the image. + [[nodiscard]] VkImageAspectFlags getAspectFlags() const { return this->aspectFlags; } + + /// Set the layout of the image. + void setLayout(VkImageLayout layout) { *this->layout = layout; } + /// Get the current layout of the image. + [[nodiscard]] VkImageLayout getLayout() const { return *this->layout; } + + /// Trivially copyable, moveable and destructible + Image(const Image&) noexcept = default; + Image& operator=(const Image&) noexcept = default; + Image(Image&&) noexcept = default; + Image& operator=(Image&&) noexcept = default; + ~Image() = default; + private: + std::shared_ptr image; + std::shared_ptr memory; + std::shared_ptr view; + + std::shared_ptr layout; + + VkExtent2D extent{}; + VkFormat format{}; + VkImageAspectFlags aspectFlags{}; + }; + +} + +#endif // IMAGE_HPP diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp index 25c1d38..cf70a7e 100644 --- a/lsfg-vk-gen/src/lsfg.cpp +++ b/lsfg-vk-gen/src/lsfg.cpp @@ -2,6 +2,7 @@ #include "core/device.hpp" #include "core/instance.hpp" #include "context.hpp" +#include "utils.hpp" #include #include @@ -22,6 +23,8 @@ void LSFG::initialize() { instance.emplace(); device.emplace(*instance); + Globals::initializeGlobals(*device); + std::srand(static_cast(std::time(nullptr))); } @@ -61,6 +64,8 @@ void LSFG::finalize() { if (!instance.has_value() && !device.has_value()) return; + Globals::uninitializeGlobals(); + instance.reset(); device.reset(); } diff --git a/src/application.cpp b/src/application.cpp index f6216eb..11dfd84 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,13 +1,16 @@ #include "application.hpp" +#include "log.hpp" +#include "mini/image.hpp" #include - -#include +#include Application::Application(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, VkQueue presentQueue) : device(device), physicalDevice(physicalDevice), - graphicsQueue(graphicsQueue), presentQueue(presentQueue) {} + graphicsQueue(graphicsQueue), presentQueue(presentQueue) { + LSFG::initialize(); +} void Application::addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, const std::vector& images) { @@ -20,7 +23,23 @@ void Application::addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2 SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, const std::vector& images) - : swapchain(swapchain), format(format), extent(extent), images(images) {} + : swapchain(swapchain), format(format), extent(extent), images(images) { + int frame0fd{}; + this->frame_0 = Mini::Image( + app.getDevice(), app.getPhysicalDevice(), + extent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, &frame0fd + ); + int frame1fd{}; + this->frame_1 = Mini::Image( + app.getDevice(), app.getPhysicalDevice(), + extent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, &frame1fd + ); + this->lsfgId = LSFG::createContext(extent.width, extent.height, frame0fd, frame1fd); +} void Application::presentSwapchain(VkSwapchainKHR handle, VkQueue queue, const std::vector& semaphores, uint32_t idx) { @@ -46,6 +65,14 @@ void SwapchainContext::present(const Application& app, VkQueue queue, throw LSFG::vulkan_error(res, "Failed to present swapchain"); } +SwapchainContext::~SwapchainContext() { + try { + LSFG::deleteContext(this->lsfgId); + } catch (const std::exception&) { + return; + } +} + bool Application::removeSwapchain(VkSwapchainKHR handle) { auto it = this->swapchains.find(handle); if (it == this->swapchains.end()) @@ -53,3 +80,8 @@ bool Application::removeSwapchain(VkSwapchainKHR handle) { this->swapchains.erase(it); return true; } + +Application::~Application() { + this->swapchains.clear(); + LSFG::finalize(); +} diff --git a/src/mini/image.cpp b/src/mini/image.cpp new file mode 100644 index 0000000..831c869 --- /dev/null +++ b/src/mini/image.cpp @@ -0,0 +1,142 @@ +#include "mini/image.hpp" +#include "lsfg.hpp" + +#include +#include + +using namespace Mini; + +Image::Image(VkDevice device, VkPhysicalDevice physicalDevice, + VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd) + : extent(extent), format(format), aspectFlags(aspectFlags) { + // create image + const VkExternalMemoryImageCreateInfo externalInfo{ + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR + }; + const VkImageCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &externalInfo, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = { + .width = extent.width, + .height = extent.height, + .depth = 1 + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VkImage imageHandle{}; + auto res = vkCreateImage(device, &desc, nullptr, &imageHandle); + if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create Vulkan image"); + + // find memory type + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps); + + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device, imageHandle, &memReqs); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + std::optional memType{}; + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN + (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + memType.emplace(i); + break; + } // NOLINTEND + } + if (!memType.has_value()) + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image"); +#pragma clang diagnostic pop + + // allocate and bind memory + const VkMemoryDedicatedAllocateInfoKHR dedicatedInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR, + .image = imageHandle, + }; + const VkExportMemoryAllocateInfo exportInfo{ + .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, + .pNext = &dedicatedInfo, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR + }; + const VkMemoryAllocateInfo allocInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &exportInfo, + .allocationSize = memReqs.size, + .memoryTypeIndex = memType.value() + }; + VkDeviceMemory memoryHandle{}; + res = vkAllocateMemory(device, &allocInfo, nullptr, &memoryHandle); + if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image"); + + res = vkBindImageMemory(device, 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, &viewDesc, nullptr, &viewHandle); + if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create image view"); + + // obtain the sharing fd + auto vkGetMemoryFdKHR = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetMemoryFdKHR")); + + const VkMemoryGetFdInfoKHR fdInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, + .memory = memoryHandle, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR, + }; + res = vkGetMemoryFdKHR(device, &fdInfo, fd); + if (res != VK_SUCCESS || *fd < 0) + throw LSFG::vulkan_error(res, "Failed to obtain sharing fd for Vulkan image"); + + // store objects in shared ptr + this->layout = std::make_shared(VK_IMAGE_LAYOUT_UNDEFINED); + this->image = std::shared_ptr( + new VkImage(imageHandle), + [dev = device](VkImage* img) { + vkDestroyImage(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device](VkDeviceMemory* mem) { + vkFreeMemory(dev, *mem, nullptr); + } + ); + this->view = std::shared_ptr( + new VkImageView(viewHandle), + [dev = device](VkImageView* imgView) { + vkDestroyImageView(dev, *imgView, nullptr); + } + ); +} From 70bf51fa725b2d8518f0000e874d3b24542d7803 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 1 Jul 2025 11:18:05 +0200 Subject: [PATCH 054/253] lots of fixes --- include/application.hpp | 8 ++-- include/mini/semaphore.hpp | 53 ++++++++++++++++++++++ lsfg-vk-gen/src/context.cpp | 5 ++- lsfg-vk-gen/src/core/commandbuffer.cpp | 6 ++- lsfg-vk-gen/src/core/semaphore.cpp | 3 ++ src/application.cpp | 18 ++++---- src/hooks.cpp | 8 +++- src/mini/semaphore.cpp | 61 ++++++++++++++++++++++++++ 8 files changed, 142 insertions(+), 20 deletions(-) create mode 100644 include/mini/semaphore.hpp create mode 100644 src/mini/semaphore.cpp diff --git a/include/application.hpp b/include/application.hpp index 045982a..1da5417 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -130,14 +130,12 @@ public: /// Get the swapchain images. [[nodiscard]] const std::vector& getImages() const { return this->images; } - // Non-copyable, trivially moveable + // Non-copyable, trivially moveable and destructible SwapchainContext(const SwapchainContext&) = delete; SwapchainContext& operator=(const SwapchainContext&) = delete; SwapchainContext(SwapchainContext&&) = default; SwapchainContext& operator=(SwapchainContext&&) = default; - - /// Destructor, cleans up resources. - ~SwapchainContext(); + ~SwapchainContext() = default; private: // (non-owned resources) VkSwapchainKHR swapchain; @@ -147,7 +145,7 @@ private: // (owned resources) Mini::Image frame_0, frame_1; - int32_t lsfgId; + std::shared_ptr lsfgId; }; #endif // APPLICATION_HPP diff --git a/include/mini/semaphore.hpp b/include/mini/semaphore.hpp new file mode 100644 index 0000000..833ce35 --- /dev/null +++ b/include/mini/semaphore.hpp @@ -0,0 +1,53 @@ +#ifndef SEMAPHORE_HPP +#define SEMAPHORE_HPP + +#include + +#include + +namespace Mini { + + /// + /// C++ wrapper class for a Vulkan semaphore. + /// + /// This class manages the lifetime of a Vulkan semaphore. + /// + class Semaphore { + public: + Semaphore() noexcept = default; + + /// + /// Create the semaphore. + /// + /// @param device Vulkan device + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Semaphore(VkDevice device); + + /// + /// Import a semaphore. + /// + /// @param device Vulkan device + /// @param fd File descriptor to import the semaphore from. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Semaphore(VkDevice device, int* fd); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->semaphore; } + + // Trivially copyable, moveable and destructible + Semaphore(const Semaphore&) noexcept = default; + Semaphore& operator=(const Semaphore&) noexcept = default; + Semaphore(Semaphore&&) noexcept = default; + Semaphore& operator=(Semaphore&&) noexcept = default; + ~Semaphore() = default; + private: + std::shared_ptr semaphore; + }; + +} + +#endif // SEMAPHORE_HPP diff --git a/lsfg-vk-gen/src/context.cpp b/lsfg-vk-gen/src/context.cpp index 46ee8ed..fcfbef4 100644 --- a/lsfg-vk-gen/src/context.cpp +++ b/lsfg-vk-gen/src/context.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace LSFG; @@ -119,8 +120,8 @@ void Context::present(const Core::Device& device, int inSem, int outSem) { cmdBuffer.end(); cmdBuffer.submit(device.getComputeQueue(), std::nullopt, - { inSemaphore }, {}, - { outSemaphore }, {}); + { inSemaphore }, std::nullopt, + { outSemaphore }, std::nullopt); fc++; } diff --git a/lsfg-vk-gen/src/core/commandbuffer.cpp b/lsfg-vk-gen/src/core/commandbuffer.cpp index c556127..86a1d0e 100644 --- a/lsfg-vk-gen/src/core/commandbuffer.cpp +++ b/lsfg-vk-gen/src/core/commandbuffer.cpp @@ -83,10 +83,12 @@ void CommandBuffer::submit(VkQueue queue, std::optional fence, timelineInfo.pSignalSemaphoreValues = signalSemaphoreValues->data(); } - std::vector waitSemaphoresHandles(waitSemaphores.size()); + std::vector waitSemaphoresHandles; + waitSemaphoresHandles.reserve(waitSemaphores.size()); for (const auto& semaphore : waitSemaphores) waitSemaphoresHandles.push_back(semaphore.handle()); - std::vector signalSemaphoresHandles(signalSemaphores.size()); + std::vector signalSemaphoresHandles; + signalSemaphoresHandles.reserve(signalSemaphores.size()); for (const auto& semaphore : signalSemaphores) signalSemaphoresHandles.push_back(semaphore.handle()); diff --git a/lsfg-vk-gen/src/core/semaphore.cpp b/lsfg-vk-gen/src/core/semaphore.cpp index 78b66d4..267c8f5 100644 --- a/lsfg-vk-gen/src/core/semaphore.cpp +++ b/lsfg-vk-gen/src/core/semaphore.cpp @@ -45,6 +45,9 @@ Semaphore::Semaphore(const Core::Device& device, int fd) { throw LSFG::vulkan_error(res, "Unable to create semaphore"); // import semaphore from fd + auto vkImportSemaphoreFdKHR = reinterpret_cast( + vkGetDeviceProcAddr(device.handle(), "vkImportSemaphoreFdKHR")); + const VkImportSemaphoreFdInfoKHR importInfo{ .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, .semaphore = semaphoreHandle, diff --git a/src/application.cpp b/src/application.cpp index 11dfd84..8646a0f 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,8 +1,10 @@ #include "application.hpp" #include "log.hpp" #include "mini/image.hpp" +#include "mini/semaphore.hpp" #include +#include #include Application::Application(VkDevice device, VkPhysicalDevice physicalDevice, @@ -38,7 +40,13 @@ SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapch VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, &frame1fd ); - this->lsfgId = LSFG::createContext(extent.width, extent.height, frame0fd, frame1fd); + auto id = LSFG::createContext(extent.width, extent.height, frame0fd, frame1fd); + this->lsfgId = std::shared_ptr( + new int32_t(id), + [](const int32_t* id) { + LSFG::deleteContext(*id); + } + ); } void Application::presentSwapchain(VkSwapchainKHR handle, VkQueue queue, @@ -65,14 +73,6 @@ void SwapchainContext::present(const Application& app, VkQueue queue, throw LSFG::vulkan_error(res, "Failed to present swapchain"); } -SwapchainContext::~SwapchainContext() { - try { - LSFG::deleteContext(this->lsfgId); - } catch (const std::exception&) { - return; - } -} - bool Application::removeSwapchain(VkSwapchainKHR handle) { auto it = this->swapchains.find(handle); if (it == this->swapchains.end()) diff --git a/src/hooks.cpp b/src/hooks.cpp index e908dcb..2c2e5cd 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -7,6 +7,7 @@ #include #include +#include using namespace Hooks; @@ -24,7 +25,8 @@ namespace { const std::vector requiredExtensions = { VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, - VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME + VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME }; for (const auto& ext : requiredExtensions) { auto it = std::ranges::find(extensions, ext); @@ -49,7 +51,9 @@ namespace { const std::vector requiredExtensions = { VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, - VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME + VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME }; for (const auto& ext : requiredExtensions) { auto it = std::ranges::find(extensions, ext); diff --git a/src/mini/semaphore.cpp b/src/mini/semaphore.cpp new file mode 100644 index 0000000..e798563 --- /dev/null +++ b/src/mini/semaphore.cpp @@ -0,0 +1,61 @@ +#include "mini/semaphore.hpp" +#include "lsfg.hpp" +#include + +using namespace Mini; + +Semaphore::Semaphore(VkDevice device) { + // create semaphore + const VkSemaphoreCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO + }; + VkSemaphore semaphoreHandle{}; + auto res = vkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle); + if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create semaphore"); + + // store semaphore in shared ptr + this->semaphore = std::shared_ptr( + new VkSemaphore(semaphoreHandle), + [dev = device](VkSemaphore* semaphoreHandle) { + vkDestroySemaphore(dev, *semaphoreHandle, nullptr); + } + ); +} + +Semaphore::Semaphore(VkDevice device, int* fd) { + // create semaphore + const VkExportSemaphoreCreateInfo exportInfo{ + .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT + }; + const VkSemaphoreCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &exportInfo + }; + VkSemaphore semaphoreHandle{}; + auto res = vkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle); + if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create semaphore"); + + // export semaphore to fd + auto vkGetSemaphoreFdKHR = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkGetSemaphoreFdKHR")); + + const VkSemaphoreGetFdInfoKHR fdInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, + .semaphore = semaphoreHandle, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT + }; + res = vkGetSemaphoreFdKHR(device, &fdInfo, fd); + if (res != VK_SUCCESS || *fd < 0) + throw LSFG::vulkan_error(res, "Unable to export semaphore to fd"); + + // store semaphore in shared ptr + this->semaphore = std::shared_ptr( + new VkSemaphore(semaphoreHandle), + [dev = device](VkSemaphore* semaphoreHandle) { + vkDestroySemaphore(dev, *semaphoreHandle, nullptr); + } + ); +} From ad0fd6eb7747d6fcf4768de2e62266b20b511d8a Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 2 Jul 2025 08:35:23 +0200 Subject: [PATCH 055/253] some progress on rendering --- include/application.hpp | 20 +++++-- include/mini/commandbuffer.hpp | 95 ++++++++++++++++++++++++++++++++++ include/mini/commandpool.hpp | 44 ++++++++++++++++ src/application.cpp | 35 +++++++++++-- src/hooks.cpp | 9 +--- src/mini/commandbuffer.cpp | 88 +++++++++++++++++++++++++++++++ src/mini/commandpool.cpp | 24 +++++++++ src/mini/image.cpp | 1 - src/mini/semaphore.cpp | 1 - 9 files changed, 300 insertions(+), 17 deletions(-) create mode 100644 include/mini/commandbuffer.hpp create mode 100644 include/mini/commandpool.hpp create mode 100644 src/mini/commandbuffer.cpp create mode 100644 src/mini/commandpool.cpp diff --git a/include/application.hpp b/include/application.hpp index 1da5417..7519640 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -1,7 +1,10 @@ #ifndef APPLICATION_HPP #define APPLICATION_HPP +#include "mini/commandpool.hpp" #include "mini/image.hpp" +#include "mini/semaphore.hpp" +#include #include #include @@ -20,12 +23,12 @@ public: /// @param device Vulkan device /// @param physicalDevice Vulkan physical device /// @param graphicsQueue Vulkan queue for graphics operations - /// @param presentQueue Vulkan queue for presentation operations + /// @param graphicsQueueFamilyIndex The family index of the graphics queue /// /// @throws LSFG::vulkan_error if any Vulkan call fails. /// Application(VkDevice device, VkPhysicalDevice physicalDevice, - VkQueue graphicsQueue, VkQueue presentQueue); + VkQueue graphicsQueue, uint32_t graphicsQueueFamilyIndex); /// /// Add a swapchain to the application. @@ -67,8 +70,8 @@ public: [[nodiscard]] VkPhysicalDevice getPhysicalDevice() const { return this->physicalDevice; } /// Get the Vulkan graphics queue. [[nodiscard]] VkQueue getGraphicsQueue() const { return this->graphicsQueue; } - /// Get the Vulkan present queue. - [[nodiscard]] VkQueue getPresentQueue() const { return this->presentQueue; } + /// Get the graphics queue family index. + [[nodiscard]] uint32_t getGraphicsQueueFamilyIndex() const { return this->graphicsQueueFamilyIndex; } // Non-copyable and non-movable Application(const Application&) = delete; @@ -83,7 +86,7 @@ private: VkDevice device; VkPhysicalDevice physicalDevice; VkQueue graphicsQueue; - VkQueue presentQueue; + uint32_t graphicsQueueFamilyIndex; // (owned resources) std::unordered_map swapchains; @@ -144,8 +147,15 @@ private: std::vector images; // (owned resources) + Mini::CommandPool cmdPool; + std::array copySemaphores; // copy current swap to frame + std::array acquireSemaphores; // acquire new swapchain image + std::array renderSemaphores; // fg is done + std::array presentSemaphores; // copy is done, ready to present + Mini::Image frame_0, frame_1; std::shared_ptr lsfgId; + uint64_t frameIdx{0}; }; #endif // APPLICATION_HPP diff --git a/include/mini/commandbuffer.hpp b/include/mini/commandbuffer.hpp new file mode 100644 index 0000000..0607a8d --- /dev/null +++ b/include/mini/commandbuffer.hpp @@ -0,0 +1,95 @@ +#ifndef COMMANDBUFFER_HPP +#define COMMANDBUFFER_HPP + +#include "mini/commandpool.hpp" +#include "mini/semaphore.hpp" + +#include + +#include +#include + +namespace Mini { + + /// State of the command buffer. + enum class CommandBufferState { + /// Command buffer is not initialized or has been destroyed. + Invalid, + /// Command buffer has been created. + Empty, + /// Command buffer recording has started. + Recording, + /// Command buffer recording has ended. + Full, + /// Command buffer has been submitted to a queue. + Submitted + }; + + /// + /// C++ wrapper class for a Vulkan command buffer. + /// + /// This class manages the lifetime of a Vulkan command buffer. + /// + class CommandBuffer { + public: + CommandBuffer() noexcept = default; + + /// + /// Create the command buffer. + /// + /// @param device Vulkan device + /// @param pool Vulkan command pool + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + CommandBuffer(VkDevice device, const CommandPool& pool); + + /// + /// Begin recording commands in the command buffer. + /// + /// @throws std::logic_error if the command buffer is in Empty state + /// @throws LSFG::vulkan_error if beginning the command buffer fails. + /// + void begin(); + + /// + /// End recording commands in the command buffer. + /// + /// @throws std::logic_error if the command buffer is not in Recording state + /// @throws LSFG::vulkan_error if ending the command buffer fails. + /// + void end(); + + /// + /// Submit the command buffer to a queue. + /// + /// @param queue Vulkan queue to submit to + /// @param waitSemaphores Semaphores to wait on before executing the command buffer + /// @param signalSemaphores Semaphores to signal after executing the command buffer + /// + /// @throws std::logic_error if the command buffer is not in Full state. + /// @throws LSFG::vulkan_error if submission fails. + /// + void submit(VkQueue queue, + const std::vector& waitSemaphores = {}, + const std::vector& signalSemaphores = {}); + + /// Get the state of the command buffer. + [[nodiscard]] CommandBufferState getState() const { return *this->state; } + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->commandBuffer; } + + /// Trivially copyable, moveable and destructible + CommandBuffer(const CommandBuffer&) noexcept = default; + CommandBuffer& operator=(const CommandBuffer&) noexcept = default; + CommandBuffer(CommandBuffer&&) noexcept = default; + CommandBuffer& operator=(CommandBuffer&&) noexcept = default; + ~CommandBuffer() = default; + private: + std::shared_ptr state; + std::shared_ptr commandBuffer; + }; + +} + +#endif // COMMANDBUFFER_HPP diff --git a/include/mini/commandpool.hpp b/include/mini/commandpool.hpp new file mode 100644 index 0000000..6860ba0 --- /dev/null +++ b/include/mini/commandpool.hpp @@ -0,0 +1,44 @@ +#ifndef COMMANDPOOL_HPP +#define COMMANDPOOL_HPP + +#include + +#include + +namespace Mini { + + /// + /// C++ wrapper class for a Vulkan command pool. + /// + /// This class manages the lifetime of a Vulkan command pool. + /// + class CommandPool { + public: + CommandPool() noexcept = default; + + /// + /// Create the command pool. + /// + /// @param device Vulkan device + /// @param graphicsFamilyIdx Index of the graphics queue family + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + CommandPool(VkDevice device, uint32_t graphicsFamilyIdx); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->commandPool; } + + /// Trivially copyable, moveable and destructible + CommandPool(const CommandPool&) noexcept = default; + CommandPool& operator=(const CommandPool&) noexcept = default; + CommandPool(CommandPool&&) noexcept = default; + CommandPool& operator=(CommandPool&&) noexcept = default; + ~CommandPool() = default; + private: + std::shared_ptr commandPool; + }; + +} + +#endif // COMMANDPOOL_HPP diff --git a/src/application.cpp b/src/application.cpp index 8646a0f..9e63c42 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,5 +1,5 @@ #include "application.hpp" -#include "log.hpp" +#include "mini/commandpool.hpp" #include "mini/image.hpp" #include "mini/semaphore.hpp" @@ -8,9 +8,9 @@ #include Application::Application(VkDevice device, VkPhysicalDevice physicalDevice, - VkQueue graphicsQueue, VkQueue presentQueue) + VkQueue graphicsQueue, uint32_t graphicsQueueFamilyIndex) : device(device), physicalDevice(physicalDevice), - graphicsQueue(graphicsQueue), presentQueue(presentQueue) { + graphicsQueue(graphicsQueue), graphicsQueueFamilyIndex(graphicsQueueFamilyIndex) { LSFG::initialize(); } @@ -26,6 +26,8 @@ void Application::addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2 SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, const std::vector& images) : swapchain(swapchain), format(format), extent(extent), images(images) { + this->cmdPool = Mini::CommandPool(app.getDevice(), app.getGraphicsQueueFamilyIndex()); + int frame0fd{}; this->frame_0 = Mini::Image( app.getDevice(), app.getPhysicalDevice(), @@ -33,6 +35,7 @@ SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapch VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, &frame0fd ); + int frame1fd{}; this->frame_1 = Mini::Image( app.getDevice(), app.getPhysicalDevice(), @@ -40,6 +43,7 @@ SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapch VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, &frame1fd ); + auto id = LSFG::createContext(extent.width, extent.height, frame0fd, frame1fd); this->lsfgId = std::shared_ptr( new int32_t(id), @@ -60,6 +64,29 @@ void Application::presentSwapchain(VkSwapchainKHR handle, VkQueue queue, void SwapchainContext::present(const Application& app, VkQueue queue, const std::vector& semaphores, uint32_t idx) { + // create semaphores + int copySem{}; + int presentSem{}; + Mini::Semaphore& copySemaphore = this->copySemaphores.at(this->frameIdx % 8); + copySemaphore = Mini::Semaphore(app.getDevice(), ©Sem); + Mini::Semaphore& acquireSemaphore = this->acquireSemaphores.at(this->frameIdx % 8); + acquireSemaphore = Mini::Semaphore(app.getDevice()); + Mini::Semaphore& renderSemaphore = this->renderSemaphores.at(this->frameIdx % 8); + renderSemaphore = Mini::Semaphore(app.getDevice()); + Mini::Semaphore& presentSemaphore = this->presentSemaphores.at(this->frameIdx % 8); + presentSemaphore = Mini::Semaphore(app.getDevice(), &presentSem); + + // ... + + // render the intermediary frame + // FIXME: i forgot the flippin output + // FIXME: semaphores are being destroyed in the context + LSFG::presentContext(*this->lsfgId, copySem, presentSem); + + // ... + + // ... + const VkPresentInfoKHR presentInfo = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = static_cast(semaphores.size()), @@ -71,6 +98,8 @@ void SwapchainContext::present(const Application& app, VkQueue queue, auto res = vkQueuePresentKHR(queue, &presentInfo); if (res != VK_SUCCESS) // FIXME: somehow return VK_SUBOPTIMAL_KHR throw LSFG::vulkan_error(res, "Failed to present swapchain"); + + this->frameIdx++; } bool Application::removeSwapchain(VkSwapchainKHR handle) { diff --git a/src/hooks.cpp b/src/hooks.cpp index 2c2e5cd..67e82f6 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -76,7 +76,6 @@ namespace { vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, families.data()); std::optional graphicsFamilyIdx; - std::optional presentFamilyIdx; for (uint32_t i = 0; i < families.size(); ++i) { auto it = std::ranges::find_if(queueCreateInfos, [i](const VkDeviceQueueCreateInfo& info) { @@ -86,18 +85,14 @@ namespace { continue; // skip if this family is not used by the device if (families.at(i).queueFlags & VK_QUEUE_GRAPHICS_BIT) graphicsFamilyIdx.emplace(i); - if (families.at(i).queueFlags & VK_QUEUE_COMPUTE_BIT) - presentFamilyIdx.emplace(i); } - if (!graphicsFamilyIdx.has_value() || !presentFamilyIdx.has_value()) { + if (!graphicsFamilyIdx.has_value()) { Log::error("No suitable queue family found for graphics or present"); exit(EXIT_FAILURE); } VkQueue graphicsQueue{}; vkGetDeviceQueue(*pDevice, *graphicsFamilyIdx, 0, &graphicsQueue); - VkQueue presentQueue{}; - vkGetDeviceQueue(*pDevice, *presentFamilyIdx, 0, &presentQueue); // create the main application if (application.has_value()) { @@ -106,7 +101,7 @@ namespace { } try { - application.emplace(*pDevice, physicalDevice, graphicsQueue, presentQueue); + application.emplace(*pDevice, physicalDevice, graphicsQueue, *graphicsFamilyIdx); Log::info("lsfg-vk(hooks): Application created successfully"); } catch (const LSFG::vulkan_error& e) { Log::error("Encountered Vulkan error {:x} while creating application: {}", diff --git a/src/mini/commandbuffer.cpp b/src/mini/commandbuffer.cpp new file mode 100644 index 0000000..9cbc472 --- /dev/null +++ b/src/mini/commandbuffer.cpp @@ -0,0 +1,88 @@ +#include "mini/commandbuffer.hpp" +#include "lsfg.hpp" + +using namespace Mini; + +CommandBuffer::CommandBuffer(VkDevice device, const CommandPool& pool) { + // create command buffer + const VkCommandBufferAllocateInfo desc{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = pool.handle(), + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1 + }; + VkCommandBuffer commandBufferHandle{}; + auto res = vkAllocateCommandBuffers(device, &desc, &commandBufferHandle); + if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to allocate command buffer"); + + // store command buffer in shared ptr + this->state = std::make_shared(CommandBufferState::Empty); + this->commandBuffer = std::shared_ptr( + new VkCommandBuffer(commandBufferHandle), + [dev = device, 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::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, + const std::vector& waitSemaphores, + const std::vector& signalSemaphores) { + if (*this->state != CommandBufferState::Full) + throw std::logic_error("Command buffer is not in Full state"); + + const std::vector waitStages(waitSemaphores.size(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); + + std::vector waitSemaphoresHandles; + waitSemaphoresHandles.reserve(waitSemaphores.size()); + for (const auto& semaphore : waitSemaphores) + waitSemaphoresHandles.push_back(semaphore.handle()); + std::vector signalSemaphoresHandles; + signalSemaphoresHandles.reserve(signalSemaphores.size()); + for (const auto& semaphore : signalSemaphores) + signalSemaphoresHandles.push_back(semaphore.handle()); + + const VkSubmitInfo submitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphoresHandles.data(), + .pWaitDstStageMask = waitStages.data(), + .commandBufferCount = 1, + .pCommandBuffers = &(*this->commandBuffer), + .signalSemaphoreCount = static_cast(signalSemaphores.size()), + .pSignalSemaphores = signalSemaphoresHandles.data() + }; + auto res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to submit command buffer"); + + *this->state = CommandBufferState::Submitted; +} diff --git a/src/mini/commandpool.cpp b/src/mini/commandpool.cpp new file mode 100644 index 0000000..d0a6b8e --- /dev/null +++ b/src/mini/commandpool.cpp @@ -0,0 +1,24 @@ +#include "mini/commandpool.hpp" +#include "lsfg.hpp" + +using namespace Mini; + +CommandPool::CommandPool(VkDevice device, uint32_t graphicsFamilyIdx) { + // create command pool + const VkCommandPoolCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .queueFamilyIndex = graphicsFamilyIdx + }; + VkCommandPool commandPoolHandle{}; + auto res = vkCreateCommandPool(device, &desc, nullptr, &commandPoolHandle); + if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Unable to create command pool"); + + // store command pool in shared ptr + this->commandPool = std::shared_ptr( + new VkCommandPool(commandPoolHandle), + [dev = device](VkCommandPool* commandPoolHandle) { + vkDestroyCommandPool(dev, *commandPoolHandle, nullptr); + } + ); +} diff --git a/src/mini/image.cpp b/src/mini/image.cpp index 831c869..2ec53a7 100644 --- a/src/mini/image.cpp +++ b/src/mini/image.cpp @@ -2,7 +2,6 @@ #include "lsfg.hpp" #include -#include using namespace Mini; diff --git a/src/mini/semaphore.cpp b/src/mini/semaphore.cpp index e798563..6d803bc 100644 --- a/src/mini/semaphore.cpp +++ b/src/mini/semaphore.cpp @@ -1,6 +1,5 @@ #include "mini/semaphore.hpp" #include "lsfg.hpp" -#include using namespace Mini; From a7df89824cf8b7bdb7647150e3f9ef09b2f4fd02 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 2 Jul 2025 09:49:10 +0200 Subject: [PATCH 056/253] is it done? --- include/application.hpp | 10 +- include/mini/commandbuffer.hpp | 4 +- lsfg-vk-gen/include/context.hpp | 8 +- lsfg-vk-gen/include/shaderchains/merge.hpp | 4 +- lsfg-vk-gen/public/lsfg.hpp | 3 +- lsfg-vk-gen/src/context.cpp | 14 +- lsfg-vk-gen/src/lsfg.cpp | 4 +- lsfg-vk-gen/src/shaderchains/magic.cpp | 2 +- lsfg-vk-gen/src/shaderchains/merge.cpp | 6 +- src/application.cpp | 228 +++++++++++++++++++-- src/mini/commandbuffer.cpp | 17 +- 11 files changed, 253 insertions(+), 47 deletions(-) diff --git a/include/application.hpp b/include/application.hpp index 7519640..19f2775 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -1,10 +1,12 @@ #ifndef APPLICATION_HPP #define APPLICATION_HPP +#include "mini/commandbuffer.hpp" #include "mini/commandpool.hpp" #include "mini/image.hpp" #include "mini/semaphore.hpp" #include +#include #include #include @@ -148,14 +150,18 @@ private: // (owned resources) Mini::CommandPool cmdPool; - std::array copySemaphores; // copy current swap to frame + std::array cmdBufs1; + std::array cmdBufs2; + std::array copySemaphores1; // copy current swap to frame + std::array copySemaphores2; // (for present) std::array acquireSemaphores; // acquire new swapchain image std::array renderSemaphores; // fg is done std::array presentSemaphores; // copy is done, ready to present - Mini::Image frame_0, frame_1; + Mini::Image frame_0, frame_1, out_img; std::shared_ptr lsfgId; uint64_t frameIdx{0}; + std::optional deferredIdx; // index of the frame to present next }; #endif // APPLICATION_HPP diff --git a/include/mini/commandbuffer.hpp b/include/mini/commandbuffer.hpp index 0607a8d..6b0e4f2 100644 --- a/include/mini/commandbuffer.hpp +++ b/include/mini/commandbuffer.hpp @@ -71,8 +71,8 @@ namespace Mini { /// @throws LSFG::vulkan_error if submission fails. /// void submit(VkQueue queue, - const std::vector& waitSemaphores = {}, - const std::vector& signalSemaphores = {}); + const std::vector& waitSemaphores = {}, + const std::vector& signalSemaphores = {}); /// Get the state of the command buffer. [[nodiscard]] CommandBufferState getState() const { return *this->state; } diff --git a/lsfg-vk-gen/include/context.hpp b/lsfg-vk-gen/include/context.hpp index 15768f3..f056d46 100644 --- a/lsfg-vk-gen/include/context.hpp +++ b/lsfg-vk-gen/include/context.hpp @@ -1,9 +1,11 @@ #ifndef CONTEXT_HPP #define CONTEXT_HPP +#include "core/commandbuffer.hpp" #include "core/commandpool.hpp" #include "core/descriptorpool.hpp" #include "core/image.hpp" +#include "core/semaphore.hpp" #include "shaderchains/alpha.hpp" #include "shaderchains/beta.hpp" #include "shaderchains/delta.hpp" @@ -30,10 +32,11 @@ namespace LSFG { /// @param height Height of the input images. /// @param in0 File descriptor for the first input image. /// @param in1 File descriptor for the second input image. + /// @param out File descriptor for the output image. /// /// @throws LSFG::vulkan_error if the generator fails to initialize. /// - Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1); + Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1, int out); /// /// Schedule the next generation. @@ -57,6 +60,9 @@ namespace LSFG { Core::CommandPool cmdPool; Core::Image inImg_0, inImg_1; // inImg_0 is next (inImg_1 prev) when fc % 2 == 0 + std::array inSemaphores; + std::array outSemaphores; + std::array cmdBuffers; uint64_t fc{0}; Shaderchains::Downsample downsampleChain; diff --git a/lsfg-vk-gen/include/shaderchains/merge.hpp b/lsfg-vk-gen/include/shaderchains/merge.hpp index 3af00cf..1205c2b 100644 --- a/lsfg-vk-gen/include/shaderchains/merge.hpp +++ b/lsfg-vk-gen/include/shaderchains/merge.hpp @@ -34,6 +34,7 @@ namespace LSFG::Shaderchains { /// @param inImg3 The first related input texture /// @param inImg4 The second related input texture /// @param inImg5 The third related input texture + /// @param outFd File descriptor for the output image. /// /// @throws LSFG::vulkan_error if resource creation fails. /// @@ -42,7 +43,8 @@ namespace LSFG::Shaderchains { Core::Image inImg2, Core::Image inImg3, Core::Image inImg4, - Core::Image inImg5); + Core::Image inImg5, + int outFd); /// /// Dispatch the shaderchain. diff --git a/lsfg-vk-gen/public/lsfg.hpp b/lsfg-vk-gen/public/lsfg.hpp index d9a25bf..55fa9a2 100644 --- a/lsfg-vk-gen/public/lsfg.hpp +++ b/lsfg-vk-gen/public/lsfg.hpp @@ -21,11 +21,12 @@ namespace LSFG { /// @param height Height of the input images. /// @param in0 File descriptor for the first input image. /// @param in1 File descriptor for the second input image. + /// @param out File descriptor for the output image. /// @return A unique identifier for the created context. /// /// @throws LSFG::vulkan_error if the context cannot be created. /// - int32_t createContext(uint32_t width, uint32_t height, int in0, int in1); + int32_t createContext(uint32_t width, uint32_t height, int in0, int in1, int out); /// /// Present a context. diff --git a/lsfg-vk-gen/src/context.cpp b/lsfg-vk-gen/src/context.cpp index fcfbef4..c8546e7 100644 --- a/lsfg-vk-gen/src/context.cpp +++ b/lsfg-vk-gen/src/context.cpp @@ -8,7 +8,7 @@ using namespace LSFG; -Context::Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1) { +Context::Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1, int out) { // import images this->inImg_0 = Core::Image(device, { width, height }, VK_FORMAT_R8G8B8A8_UNORM, @@ -90,15 +90,19 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in this->inImg_0, this->zetaChains.at(2).getOutImage(), this->epsilonChains.at(2).getOutImage(), - this->deltaChains.at(2).getOutImage() + this->deltaChains.at(2).getOutImage(), + out ); } void Context::present(const Core::Device& device, int inSem, int outSem) { - const Core::Semaphore inSemaphore(device, inSem); - const Core::Semaphore outSemaphore(device, outSem); + auto& inSemaphore = this->inSemaphores.at(this->fc % 8); + inSemaphore = Core::Semaphore(device, inSem); + auto& outSemaphore = this->outSemaphores.at(this->fc % 8); + outSemaphore = Core::Semaphore(device, outSem); - Core::CommandBuffer cmdBuffer(device, this->cmdPool); + auto& cmdBuffer = this->cmdBuffers.at(this->fc % 8); + cmdBuffer = Core::CommandBuffer(device, this->cmdPool); cmdBuffer.begin(); this->downsampleChain.Dispatch(cmdBuffer, fc); diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp index cf70a7e..7b0750e 100644 --- a/lsfg-vk-gen/src/lsfg.cpp +++ b/lsfg-vk-gen/src/lsfg.cpp @@ -28,12 +28,12 @@ void LSFG::initialize() { std::srand(static_cast(std::time(nullptr))); } -int32_t LSFG::createContext(uint32_t width, uint32_t height, int in0, int in1) { +int32_t LSFG::createContext(uint32_t width, uint32_t height, int in0, int in1, int out) { if (!instance.has_value() || !device.has_value()) throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); auto id = std::rand(); - contexts.emplace(id, Context(*device, width, height, in0, in1)); + contexts.emplace(id, Context(*device, width, height, in0, in1, out)); return id; } diff --git a/lsfg-vk-gen/src/shaderchains/magic.cpp b/lsfg-vk-gen/src/shaderchains/magic.cpp index 450bc91..dca66b5 100644 --- a/lsfg-vk-gen/src/shaderchains/magic.cpp +++ b/lsfg-vk-gen/src/shaderchains/magic.cpp @@ -103,6 +103,6 @@ void Magic::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipeline.bind(buf); - this->descriptorSets.at(fc).bind(buf, this->pipeline); + this->descriptorSets.at(fc % 3).bind(buf, this->pipeline); buf.dispatch(threadsX, threadsY, 1); } diff --git a/lsfg-vk-gen/src/shaderchains/merge.cpp b/lsfg-vk-gen/src/shaderchains/merge.cpp index 43bd957..36ff3c3 100644 --- a/lsfg-vk-gen/src/shaderchains/merge.cpp +++ b/lsfg-vk-gen/src/shaderchains/merge.cpp @@ -8,7 +8,8 @@ Merge::Merge(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg2, Core::Image inImg3, Core::Image inImg4, - Core::Image inImg5) + Core::Image inImg5, + int outFd) : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), @@ -30,7 +31,8 @@ Merge::Merge(const Core::Device& device, const Core::DescriptorPool& pool, extent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); + VK_IMAGE_ASPECT_COLOR_BIT, + outFd); for (size_t fc = 0; fc < 2; fc++) { this->descriptorSets.at(fc).update(device) diff --git a/src/application.cpp b/src/application.cpp index 9e63c42..c9623b2 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,8 +1,11 @@ #include "application.hpp" +#include "mini/commandbuffer.hpp" #include "mini/commandpool.hpp" #include "mini/image.hpp" #include "mini/semaphore.hpp" +#include +#include #include #include #include @@ -44,7 +47,15 @@ SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapch VK_IMAGE_ASPECT_COLOR_BIT, &frame1fd ); - auto id = LSFG::createContext(extent.width, extent.height, frame0fd, frame1fd); + int outfd{}; + this->out_img = Mini::Image( + app.getDevice(), app.getPhysicalDevice(), + extent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, &outfd + ); + + auto id = LSFG::createContext(extent.width, extent.height, frame0fd, frame1fd, outfd); this->lsfgId = std::shared_ptr( new int32_t(id), [](const int32_t* id) { @@ -64,39 +75,222 @@ void Application::presentSwapchain(VkSwapchainKHR handle, VkQueue queue, void SwapchainContext::present(const Application& app, VkQueue queue, const std::vector& semaphores, uint32_t idx) { + + // present deferred frame if any + if (this->deferredIdx.has_value()) { + VkSemaphore deferredSemaphore = this->copySemaphores2.at((this->frameIdx - 1) % 8).handle(); + const uint32_t deferredIdx = this->deferredIdx.value(); + const VkPresentInfoKHR presentInfo{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &deferredSemaphore, + .swapchainCount = 1, + .pSwapchains = &this->swapchain, + .pImageIndices = &deferredIdx, + }; + auto res = vkQueuePresentKHR(queue, &presentInfo); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + throw LSFG::vulkan_error(res, "Failed to present deferred swapchain image"); + } + this->deferredIdx.emplace(idx); + // create semaphores int copySem{}; - int presentSem{}; - Mini::Semaphore& copySemaphore = this->copySemaphores.at(this->frameIdx % 8); - copySemaphore = Mini::Semaphore(app.getDevice(), ©Sem); + Mini::Semaphore& copySemaphore1 = this->copySemaphores1.at(this->frameIdx % 8); + copySemaphore1 = Mini::Semaphore(app.getDevice(), ©Sem); + Mini::Semaphore& copySemaphore2 = this->copySemaphores2.at(this->frameIdx % 8); + copySemaphore2 = Mini::Semaphore(app.getDevice()); Mini::Semaphore& acquireSemaphore = this->acquireSemaphores.at(this->frameIdx % 8); acquireSemaphore = Mini::Semaphore(app.getDevice()); + int renderSem{}; Mini::Semaphore& renderSemaphore = this->renderSemaphores.at(this->frameIdx % 8); - renderSemaphore = Mini::Semaphore(app.getDevice()); + renderSemaphore = Mini::Semaphore(app.getDevice(), &renderSem); Mini::Semaphore& presentSemaphore = this->presentSemaphores.at(this->frameIdx % 8); - presentSemaphore = Mini::Semaphore(app.getDevice(), &presentSem); + presentSemaphore = Mini::Semaphore(app.getDevice()); - // ... + // copy swapchain image to next frame image + auto& cmdBuf1 = this->cmdBufs1.at(this->frameIdx % 8); + cmdBuf1 = Mini::CommandBuffer(app.getDevice(), this->cmdPool); + cmdBuf1.begin(); + + auto& srcImage1 = this->images.at(idx); + auto& dstImage1 = (this->frameIdx % 2 == 0) ? this->frame_0 : this->frame_1; + + const VkImageMemoryBarrier srcBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .image = srcImage1, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + const VkImageMemoryBarrier dstBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = dstImage1.handle(), + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + const std::vector barriers = { srcBarrier, dstBarrier }; + vkCmdPipelineBarrier(cmdBuf1.handle(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 2, barriers.data()); + + const VkImageCopy imageCopy{ + .srcSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 + }, + .dstSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 + }, + .extent = { + .width = this->extent.width, + .height = this->extent.height, + .depth = 1 + } + }; + vkCmdCopyImage(cmdBuf1.handle(), + srcImage1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstImage1.handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &imageCopy); + + const VkImageMemoryBarrier presentBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .image = dstImage1.handle(), + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + vkCmdPipelineBarrier(cmdBuf1.handle(), + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, nullptr, 0, nullptr, 1, &presentBarrier); + + cmdBuf1.end(); + cmdBuf1.submit(app.getGraphicsQueue(), + semaphores, { copySemaphore1.handle(), copySemaphore2.handle() }); // render the intermediary frame - // FIXME: i forgot the flippin output - // FIXME: semaphores are being destroyed in the context - LSFG::presentContext(*this->lsfgId, copySem, presentSem); + try { + LSFG::presentContext(*this->lsfgId, copySem, renderSem); + } catch(std::exception& e) { + std::cerr << "LSFG error: " << e.what() << '\n'; + } - // ... + // acquire the next swapchain image + uint32_t newIdx{}; + auto res = vkAcquireNextImageKHR(app.getDevice(), this->swapchain, UINT64_MAX, + acquireSemaphore.handle(), VK_NULL_HANDLE, &newIdx); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image"); - // ... + // copy the output image to the swapchain image + auto& cmdBuf2 = this->cmdBufs2.at((this->frameIdx + 1) % 8); + cmdBuf2 = Mini::CommandBuffer(app.getDevice(), this->cmdPool); + cmdBuf2.begin(); + auto& srcImage2 = this->out_img; + auto& dstImage2 = this->images.at(newIdx); + + const VkImageMemoryBarrier srcBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .image = srcImage2.handle(), + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + const VkImageMemoryBarrier dstBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = dstImage2, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + const std::vector barriers2 = { srcBarrier2, dstBarrier2 }; + vkCmdPipelineBarrier(cmdBuf2.handle(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 2, barriers2.data()); + + const VkImageCopy imageCopy2{ + .srcSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 + }, + .dstSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 + }, + .extent = { + .width = this->extent.width, + .height = this->extent.height, + .depth = 1 + } + }; + vkCmdCopyImage(cmdBuf2.handle(), + srcImage2.handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstImage2, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &imageCopy2); + + const VkImageMemoryBarrier presentBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .image = dstImage2, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + vkCmdPipelineBarrier(cmdBuf2.handle(), + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, nullptr, 0, nullptr, 1, &presentBarrier2); + + cmdBuf2.end(); + cmdBuf2.submit(app.getGraphicsQueue(), + { acquireSemaphore.handle(), renderSemaphore.handle() }, + { presentSemaphore.handle() }); + + // present the swapchain image + VkSemaphore presentSemaphoreHandle = presentSemaphore.handle(); const VkPresentInfoKHR presentInfo = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .waitSemaphoreCount = static_cast(semaphores.size()), - .pWaitSemaphores = semaphores.data(), + .waitSemaphoreCount = 1, + .pWaitSemaphores = &presentSemaphoreHandle, .swapchainCount = 1, .pSwapchains = &this->swapchain, - .pImageIndices = &idx + .pImageIndices = &newIdx, }; - auto res = vkQueuePresentKHR(queue, &presentInfo); - if (res != VK_SUCCESS) // FIXME: somehow return VK_SUBOPTIMAL_KHR + res = vkQueuePresentKHR(queue, &presentInfo); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) // FIXME: somehow return VK_SUBOPTIMAL_KHR throw LSFG::vulkan_error(res, "Failed to present swapchain"); this->frameIdx++; diff --git a/src/mini/commandbuffer.cpp b/src/mini/commandbuffer.cpp index 9cbc472..2014ca5 100644 --- a/src/mini/commandbuffer.cpp +++ b/src/mini/commandbuffer.cpp @@ -53,32 +53,23 @@ void CommandBuffer::end() { } void CommandBuffer::submit(VkQueue queue, - const std::vector& waitSemaphores, - const std::vector& signalSemaphores) { + const std::vector& waitSemaphores, + const std::vector& signalSemaphores) { if (*this->state != CommandBufferState::Full) throw std::logic_error("Command buffer is not in Full state"); const std::vector waitStages(waitSemaphores.size(), VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT); - std::vector waitSemaphoresHandles; - waitSemaphoresHandles.reserve(waitSemaphores.size()); - for (const auto& semaphore : waitSemaphores) - waitSemaphoresHandles.push_back(semaphore.handle()); - std::vector signalSemaphoresHandles; - signalSemaphoresHandles.reserve(signalSemaphores.size()); - for (const auto& semaphore : signalSemaphores) - signalSemaphoresHandles.push_back(semaphore.handle()); - const VkSubmitInfo submitInfo{ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = static_cast(waitSemaphores.size()), - .pWaitSemaphores = waitSemaphoresHandles.data(), + .pWaitSemaphores = waitSemaphores.data(), .pWaitDstStageMask = waitStages.data(), .commandBufferCount = 1, .pCommandBuffers = &(*this->commandBuffer), .signalSemaphoreCount = static_cast(signalSemaphores.size()), - .pSignalSemaphores = signalSemaphoresHandles.data() + .pSignalSemaphores = signalSemaphores.data() }; auto res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); if (res != VK_SUCCESS) From 260026beedc3933eccbae434db8cf766d76d20e9 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 2 Jul 2025 10:01:19 +0200 Subject: [PATCH 057/253] better definitions for some reason --- include/loader/dl.hpp | 6 +++--- src/loader/dl.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/loader/dl.hpp b/include/loader/dl.hpp index a0e569b..6ceb44d 100644 --- a/include/loader/dl.hpp +++ b/include/loader/dl.hpp @@ -124,10 +124,10 @@ namespace Loader::DL { } /// Modified version of the dlopen function. -extern "C" void* dlopen(const char* filename, int flag); +extern "C" void* dlopen(const char* filename, int flag) noexcept; /// Modified version of the dlsym function. -extern "C" void* dlsym(void* handle, const char* symbol); +extern "C" void* dlsym(void* handle, const char* symbol) noexcept; /// Modified version of the dlclose function. -extern "C" int dlclose(void* handle); +extern "C" int dlclose(void* handle) noexcept; #endif // DL_HPP diff --git a/src/loader/dl.cpp b/src/loader/dl.cpp index 265679e..da25349 100644 --- a/src/loader/dl.cpp +++ b/src/loader/dl.cpp @@ -75,7 +75,7 @@ void DL::registerFile(const File& file) { void DL::disableHooks() { enable_hooks = false; } void DL::enableHooks() { enable_hooks = true; } -extern "C" void* dlopen(const char* filename, int flag) { +void* dlopen(const char* filename, int flag) noexcept { auto& files = overrides(); auto& loaded = handles(); @@ -102,7 +102,7 @@ extern "C" void* dlopen(const char* filename, int flag) { return file.getHandle(); } -extern "C" void* dlsym(void* handle, const char* symbol) { +void* dlsym(void* handle, const char* symbol) noexcept { const auto& files = overrides(); if (!enable_hooks || !handle || !symbol) @@ -126,7 +126,7 @@ extern "C" void* dlsym(void* handle, const char* symbol) { return func; } -extern "C" int dlclose(void* handle) { +int dlclose(void* handle) noexcept { auto& files = overrides(); auto& loaded = handles(); From f62c2578daebc1f8742594f1adf85852daaa80d1 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 2 Jul 2025 10:51:43 +0200 Subject: [PATCH 058/253] increase swapchain image count --- src/hooks.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hooks.cpp b/src/hooks.cpp index 67e82f6..fe45493 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -120,7 +120,11 @@ namespace { const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain) { - auto res = vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + VkSwapchainCreateInfoKHR createInfo = *pCreateInfo; + createInfo.minImageCount += 2; + createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + auto res = vkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); // add the swapchain to the application if (!application.has_value()) { From 3a6458ab7a7ca695e6807fc70f4b098f3a00b210 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 2 Jul 2025 11:39:14 +0200 Subject: [PATCH 059/253] pass pNext to present --- include/application.hpp | 6 ++++-- src/application.cpp | 8 ++++---- src/hooks.cpp | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/application.hpp b/include/application.hpp index 19f2775..5133a04 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -52,11 +52,12 @@ public: /// @param queue The Vulkan queue to present the frame on. /// @param semaphores The semaphores to wait on before presenting. /// @param idx The index of the swapchain image to present. + /// @param pNext Pointer to the next structure in a chain, if any. /// /// @throws LSFG::vulkan_error if any Vulkan call fails. /// void presentSwapchain(VkSwapchainKHR handle, VkQueue queue, - const std::vector& semaphores, uint32_t idx); + const std::vector& semaphores, uint32_t idx, const void* pNext); /// /// Remove a swapchain from the application. @@ -120,11 +121,12 @@ public: /// @param queue The Vulkan queue to present the frame on. /// @param semaphores The semaphores to wait on before presenting. /// @param idx The index of the swapchain image to present. + /// @param pNext Pointer to the next structure in a chain, if any. /// /// @throws LSFG::vulkan_error if any Vulkan call fails. /// void present(const Application& app, VkQueue queue, - const std::vector& semaphores, uint32_t idx); + const std::vector& semaphores, uint32_t idx, const void* pNext); /// Get the Vulkan swapchain handle. [[nodiscard]] VkSwapchainKHR handle() const { return this->swapchain; } diff --git a/src/application.cpp b/src/application.cpp index c9623b2..216019a 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -65,17 +65,16 @@ SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapch } void Application::presentSwapchain(VkSwapchainKHR handle, VkQueue queue, - const std::vector& semaphores, uint32_t idx) { + const std::vector& semaphores, uint32_t idx, const void* pNext) { auto it = this->swapchains.find(handle); if (it == this->swapchains.end()) throw std::logic_error("Swapchain not found"); - it->second.present(*this, queue, semaphores, idx); + it->second.present(*this, queue, semaphores, idx, pNext); } void SwapchainContext::present(const Application& app, VkQueue queue, - const std::vector& semaphores, uint32_t idx) { - + const std::vector& semaphores, uint32_t idx, const void* pNext) { // present deferred frame if any if (this->deferredIdx.has_value()) { VkSemaphore deferredSemaphore = this->copySemaphores2.at((this->frameIdx - 1) % 8).handle(); @@ -283,6 +282,7 @@ void SwapchainContext::present(const Application& app, VkQueue queue, VkSemaphore presentSemaphoreHandle = presentSemaphore.handle(); const VkPresentInfoKHR presentInfo = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = pNext, .waitSemaphoreCount = 1, .pWaitSemaphores = &presentSemaphoreHandle, .swapchainCount = 1, diff --git a/src/hooks.cpp b/src/hooks.cpp index fe45493..97d78cd 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -179,7 +179,7 @@ namespace { std::copy_n(pPresentInfo->pWaitSemaphores, waitSemaphores.size(), waitSemaphores.data()); application->presentSwapchain(*pPresentInfo->pSwapchains, - queue, waitSemaphores, *pPresentInfo->pImageIndices); + queue, waitSemaphores, *pPresentInfo->pImageIndices, pPresentInfo->pNext); Log::info("lsfg-vk(hooks): Frame presented successfully"); } catch (const LSFG::vulkan_error& e) { From f3b5cfc1319cfbc195e82058bf77d98dbb6943ab Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 2 Jul 2025 13:30:45 +0200 Subject: [PATCH 060/253] multi frame generation for backend --- lsfg-vk-gen/include/context.hpp | 16 +-- lsfg-vk-gen/include/shaderchains/beta.hpp | 12 ++- lsfg-vk-gen/include/shaderchains/delta.hpp | 12 ++- .../include/shaderchains/downsample.hpp | 4 +- lsfg-vk-gen/include/shaderchains/epsilon.hpp | 12 ++- lsfg-vk-gen/include/shaderchains/extract.hpp | 11 +- lsfg-vk-gen/include/shaderchains/gamma.hpp | 15 ++- lsfg-vk-gen/include/shaderchains/magic.hpp | 11 +- lsfg-vk-gen/include/shaderchains/merge.hpp | 17 +-- lsfg-vk-gen/include/shaderchains/zeta.hpp | 12 ++- lsfg-vk-gen/public/lsfg.hpp | 10 +- lsfg-vk-gen/src/context.cpp | 101 ++++++++++++------ lsfg-vk-gen/src/lsfg.cpp | 7 +- lsfg-vk-gen/src/shaderchains/beta.cpp | 32 ++++-- lsfg-vk-gen/src/shaderchains/delta.cpp | 39 ++++--- lsfg-vk-gen/src/shaderchains/downsample.cpp | 8 +- lsfg-vk-gen/src/shaderchains/epsilon.cpp | 37 ++++--- lsfg-vk-gen/src/shaderchains/extract.cpp | 37 ++++--- lsfg-vk-gen/src/shaderchains/gamma.cpp | 100 +++++++++-------- lsfg-vk-gen/src/shaderchains/magic.cpp | 52 +++++---- lsfg-vk-gen/src/shaderchains/merge.cpp | 59 +++++----- lsfg-vk-gen/src/shaderchains/zeta.cpp | 37 ++++--- 22 files changed, 402 insertions(+), 239 deletions(-) diff --git a/lsfg-vk-gen/include/context.hpp b/lsfg-vk-gen/include/context.hpp index f056d46..41fa127 100644 --- a/lsfg-vk-gen/include/context.hpp +++ b/lsfg-vk-gen/include/context.hpp @@ -32,22 +32,24 @@ namespace LSFG { /// @param height Height of the input images. /// @param in0 File descriptor for the first input image. /// @param in1 File descriptor for the second input image. - /// @param out File descriptor for the output image. + /// @param outN File descriptor for the output image. /// /// @throws LSFG::vulkan_error if the generator fails to initialize. /// - Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1, int out); + Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1, + const std::vector& outN); /// /// Schedule the next generation. /// /// @param device The Vulkan device to use. /// @param inSem Semaphore to wait on before starting the generation. - /// @param outSem Semaphore to signal when the generation is complete. + /// @param outSem Semaphores to signal after each generation is done. /// /// @throws LSFG::vulkan_error if the generator fails to present. /// - void present(const Core::Device& device, int inSem, int outSem); + void present(const Core::Device& device, int inSem, + const std::vector& outSem); // Trivially copyable, moveable and destructible Context(const Context&) = default; @@ -61,8 +63,10 @@ namespace LSFG { Core::Image inImg_0, inImg_1; // inImg_0 is next (inImg_1 prev) when fc % 2 == 0 std::array inSemaphores; - std::array outSemaphores; - std::array cmdBuffers; + std::array, 8> internalSemaphores; + std::vector> outSemaphores; + std::array cmdBuffers1; + std::vector> cmdBuffers2; uint64_t fc{0}; Shaderchains::Downsample downsampleChain; diff --git a/lsfg-vk-gen/include/shaderchains/beta.hpp b/lsfg-vk-gen/include/shaderchains/beta.hpp index 1effbc5..8b39c85 100644 --- a/lsfg-vk-gen/include/shaderchains/beta.hpp +++ b/lsfg-vk-gen/include/shaderchains/beta.hpp @@ -32,23 +32,26 @@ namespace LSFG::Shaderchains { /// @param inImgs_0 The next input images to process (when fc % 3 == 0) /// @param inImgs_1 The prev input images to process (when fc % 3 == 0) /// @param inImgs_2 The prev prev input images to process (when fc % 3 == 0) + /// @param genc Amount of frames to generate. /// /// @throws LSFG::vulkan_error if resource creation fails. /// Beta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs_0, std::array inImgs_1, - std::array inImgs_2); + std::array inImgs_2, + size_t genc); /// /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. /// @param fc The frame count, used to select the input images. + /// @param pass The pass number /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); + void Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass); /// Get the output images. [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } @@ -62,9 +65,10 @@ namespace LSFG::Shaderchains { private: std::array shaderModules; std::array pipelines; - std::array descriptorSets; // first shader has special logic + std::array descriptorSets; // first shader has special logic std::array specialDescriptorSets; - Core::Buffer buffer; + std::vector nDescriptorSets; + std::vector buffers; std::array inImgs_0; std::array inImgs_1; diff --git a/lsfg-vk-gen/include/shaderchains/delta.hpp b/lsfg-vk-gen/include/shaderchains/delta.hpp index 33f8fb9..90f7631 100644 --- a/lsfg-vk-gen/include/shaderchains/delta.hpp +++ b/lsfg-vk-gen/include/shaderchains/delta.hpp @@ -31,21 +31,24 @@ namespace LSFG::Shaderchains { /// @param pool The descriptor pool to allocate in. /// @param inImgs The input images to process. /// @param optImg An optional additional input from the previous pass. + /// @param genc Amount of frames to generate. /// /// @throws LSFG::vulkan_error if resource creation fails. /// Delta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs, - std::optional optImg); + std::optional optImg, + size_t genc); /// /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. + /// @param pass The pass number. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf); + void Dispatch(const Core::CommandBuffer& buf, uint64_t pass); /// Get the output image. [[nodiscard]] const auto& getOutImage() const { return this->outImg; } @@ -59,8 +62,9 @@ namespace LSFG::Shaderchains { private: std::array shaderModules; std::array pipelines; - std::array descriptorSets; - Core::Buffer buffer; + std::array descriptorSets; + std::vector nDescriptorSets; + std::vector buffers; std::array inImgs; std::optional optImg; diff --git a/lsfg-vk-gen/include/shaderchains/downsample.hpp b/lsfg-vk-gen/include/shaderchains/downsample.hpp index 78a9f78..f136b69 100644 --- a/lsfg-vk-gen/include/shaderchains/downsample.hpp +++ b/lsfg-vk-gen/include/shaderchains/downsample.hpp @@ -30,11 +30,13 @@ namespace LSFG::Shaderchains { /// @param pool The descriptor pool to allocate in. /// @param inImg_0 The next full image to downsample (when fc % 2 == 0) /// @param inImg_1 The next full image to downsample (when fc % 2 == 1) + /// @param genc Amount of frames to generate. /// /// @throws LSFG::vulkan_error if resource creation fails. /// Downsample(const Core::Device& device, const Core::DescriptorPool& pool, - Core::Image inImg_0, Core::Image inImg_1); + Core::Image inImg_0, Core::Image inImg_1, + size_t genc); /// /// Dispatch the shaderchain. diff --git a/lsfg-vk-gen/include/shaderchains/epsilon.hpp b/lsfg-vk-gen/include/shaderchains/epsilon.hpp index 28e09d2..2559726 100644 --- a/lsfg-vk-gen/include/shaderchains/epsilon.hpp +++ b/lsfg-vk-gen/include/shaderchains/epsilon.hpp @@ -32,22 +32,25 @@ namespace LSFG::Shaderchains { /// @param inImgs1 The first set of input images to process. /// @param inImg2 The second type image to process. /// @param optImg An optional additional input from the previous pass. + /// @param genc Amount of frames to generate. /// /// @throws LSFG::vulkan_error if resource creation fails. /// Epsilon(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, - std::optional optImg); + std::optional optImg, + size_t genc); /// /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. + /// @param pass The pass number. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf); + void Dispatch(const Core::CommandBuffer& buf, uint64_t pass); /// Get the output image. [[nodiscard]] const auto& getOutImage() const { return this->outImg; } @@ -61,8 +64,9 @@ namespace LSFG::Shaderchains { private: std::array shaderModules; std::array pipelines; - std::array descriptorSets; - Core::Buffer buffer; + std::array descriptorSets; + std::vector nDescriptorSets; + std::vector buffers; std::array inImgs1; Core::Image inImg2; diff --git a/lsfg-vk-gen/include/shaderchains/extract.hpp b/lsfg-vk-gen/include/shaderchains/extract.hpp index a56a401..92d0bf3 100644 --- a/lsfg-vk-gen/include/shaderchains/extract.hpp +++ b/lsfg-vk-gen/include/shaderchains/extract.hpp @@ -30,22 +30,25 @@ namespace LSFG::Shaderchains { /// @param inImg1 The first set of input images to process. /// @param inImg2 The second type image to process. /// @param outExtent The extent of the output image. + /// @param genc Amount of frames to generate. /// /// @throws LSFG::vulkan_error if resource creation fails. /// Extract(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg1, Core::Image inImg2, - VkExtent2D outExtent); + VkExtent2D outExtent, + size_t genc); /// /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. + /// @param pass The pass number. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf); + void Dispatch(const Core::CommandBuffer& buf, uint64_t pass); /// Get the output image. [[nodiscard]] const auto& getOutImage() const { return this->outImg; } @@ -59,8 +62,8 @@ namespace LSFG::Shaderchains { private: Core::ShaderModule shaderModule; Core::Pipeline pipeline; - Core::DescriptorSet descriptorSet; - Core::Buffer buffer; + std::vector nDescriptorSets; + std::vector buffers; Core::Image inImg1; Core::Image inImg2; diff --git a/lsfg-vk-gen/include/shaderchains/gamma.hpp b/lsfg-vk-gen/include/shaderchains/gamma.hpp index 9aca275..5041e30 100644 --- a/lsfg-vk-gen/include/shaderchains/gamma.hpp +++ b/lsfg-vk-gen/include/shaderchains/gamma.hpp @@ -38,6 +38,7 @@ namespace LSFG::Shaderchains { /// @param optImg1 An optional additional input from the previous pass. /// @param optImg2 An optional additional input image for processing non-first passes. /// @param outExtent The extent of the output image. + /// @param genc Amount of frames to generate. /// /// @throws LSFG::vulkan_error if resource creation fails. /// @@ -48,17 +49,19 @@ namespace LSFG::Shaderchains { Core::Image inImg2, std::optional optImg1, std::optional optImg2, - VkExtent2D outExtent); + VkExtent2D outExtent, + size_t genc); /// /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. /// @param fc The frame count, used to select the input images. + /// @param pass The pass number. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); + void Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass); /// Get the first output image. [[nodiscard]] const auto& getOutImage1() const { return this->outImg1; } @@ -74,9 +77,11 @@ namespace LSFG::Shaderchains { private: std::array shaderModules; std::array pipelines; - std::array descriptorSets; // first shader has special logic - std::array specialDescriptorSets; - Core::Buffer buffer; + std::array descriptorSets; // first shader has special logic + std::vector n1DescriptorSets; + std::vector n2DescriptorSets; + std::vector> nSpecialDescriptorSets; + std::vector buffers; std::array inImgs1_0; std::array inImgs1_1; diff --git a/lsfg-vk-gen/include/shaderchains/magic.hpp b/lsfg-vk-gen/include/shaderchains/magic.hpp index 7a0b33f..00ecf27 100644 --- a/lsfg-vk-gen/include/shaderchains/magic.hpp +++ b/lsfg-vk-gen/include/shaderchains/magic.hpp @@ -35,6 +35,7 @@ namespace LSFG::Shaderchains { /// @param inImg2 The second input image to process. /// @param inImg3 The third input image to process, next step up the resolution. /// @param optImg An optional additional input from the previous pass. + /// @param genc Amount of frames to generate. /// /// @throws LSFG::vulkan_error if resource creation fails. /// @@ -44,17 +45,19 @@ namespace LSFG::Shaderchains { std::array inImgs1_2, Core::Image inImg2, Core::Image inImg3, - std::optional optImg); + std::optional optImg, + size_t genc); /// /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. /// @param fc The frame count, used to select the input images. + /// @param pass The pass number. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); + void Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass); /// Get the first set of output images [[nodiscard]] const auto& getOutImages1() const { return this->outImgs1; } @@ -72,8 +75,8 @@ namespace LSFG::Shaderchains { private: Core::ShaderModule shaderModule; Core::Pipeline pipeline; - std::array descriptorSets; - Core::Buffer buffer; + std::vector> nDescriptorSets; + std::vector buffers; std::array inImgs1_0; std::array inImgs1_1; diff --git a/lsfg-vk-gen/include/shaderchains/merge.hpp b/lsfg-vk-gen/include/shaderchains/merge.hpp index 1205c2b..bdfb5e3 100644 --- a/lsfg-vk-gen/include/shaderchains/merge.hpp +++ b/lsfg-vk-gen/include/shaderchains/merge.hpp @@ -34,7 +34,8 @@ namespace LSFG::Shaderchains { /// @param inImg3 The first related input texture /// @param inImg4 The second related input texture /// @param inImg5 The third related input texture - /// @param outFd File descriptor for the output image. + /// @param outFds File descriptors for the output images. + /// @param genc The amount of frames to generaten. /// /// @throws LSFG::vulkan_error if resource creation fails. /// @@ -44,20 +45,22 @@ namespace LSFG::Shaderchains { Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, - int outFd); + const std::vector& outFds, + size_t genc); /// /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. /// @param fc The frame count, used to select the input images. + /// @param pass The pass number. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); + void Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass); /// Get the output image - [[nodiscard]] const auto& getOutImage() const { return this->outImg; } + [[nodiscard]] const auto& getOutImage(size_t pass) const { return this->outImgs.at(pass); } /// Trivially copyable, moveable and destructible Merge(const Merge&) noexcept = default; @@ -68,8 +71,8 @@ namespace LSFG::Shaderchains { private: Core::ShaderModule shaderModule; Core::Pipeline pipeline; - std::array descriptorSets; // one for each input combination - Core::Buffer buffer; + std::vector> nDescriptorSets; // per combo + std::vector buffers; Core::Image inImg1; Core::Image inImg2; @@ -77,7 +80,7 @@ namespace LSFG::Shaderchains { Core::Image inImg4; Core::Image inImg5; - Core::Image outImg; + std::vector outImgs; }; } diff --git a/lsfg-vk-gen/include/shaderchains/zeta.hpp b/lsfg-vk-gen/include/shaderchains/zeta.hpp index 372c0ae..bf08c30 100644 --- a/lsfg-vk-gen/include/shaderchains/zeta.hpp +++ b/lsfg-vk-gen/include/shaderchains/zeta.hpp @@ -32,22 +32,25 @@ namespace LSFG::Shaderchains { /// @param inImgs1 The first set of input images to process. /// @param inImg2 The second type image to process. /// @param inImg3 The third type image to process. + /// @param genc The amount of frames to generate. /// /// @throws LSFG::vulkan_error if resource creation fails. /// Zeta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, - Core::Image inImg3); + Core::Image inImg3, + size_t genc); /// /// Dispatch the shaderchain. /// /// @param buf The command buffer to use for dispatching. + /// @param pass The pass number. /// /// @throws std::logic_error if the command buffer is not recording. /// - void Dispatch(const Core::CommandBuffer& buf); + void Dispatch(const Core::CommandBuffer& buf, uint64_t pass); /// Get the output image. [[nodiscard]] const auto& getOutImage() const { return this->outImg; } @@ -61,8 +64,9 @@ namespace LSFG::Shaderchains { private: std::array shaderModules; std::array pipelines; - std::array descriptorSets; - Core::Buffer buffer; + std::array descriptorSets; + std::vector nDescriptorSets; + std::vector buffers; std::array inImgs1; Core::Image inImg2; diff --git a/lsfg-vk-gen/public/lsfg.hpp b/lsfg-vk-gen/public/lsfg.hpp index 55fa9a2..2c3db5e 100644 --- a/lsfg-vk-gen/public/lsfg.hpp +++ b/lsfg-vk-gen/public/lsfg.hpp @@ -3,6 +3,7 @@ #include +#include #include namespace LSFG { @@ -21,23 +22,24 @@ namespace LSFG { /// @param height Height of the input images. /// @param in0 File descriptor for the first input image. /// @param in1 File descriptor for the second input image. - /// @param out File descriptor for the output image. + /// @param outN File descriptor for each output image. This defines the LSFG level. /// @return A unique identifier for the created context. /// /// @throws LSFG::vulkan_error if the context cannot be created. /// - int32_t createContext(uint32_t width, uint32_t height, int in0, int in1, int out); + int32_t createContext(uint32_t width, uint32_t height, int in0, int in1, + const std::vector& outN); /// /// Present a context. /// /// @param id Unique identifier of the context to present. /// @param inSem Semaphore to wait on before starting the generation. - /// @param outSem Semaphore to signal when the generation is complete. + /// @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, int outSem); + void presentContext(int32_t id, int inSem, const std::vector& outSem); /// /// Delete an LSFG context. diff --git a/lsfg-vk-gen/src/context.cpp b/lsfg-vk-gen/src/context.cpp index c8546e7..fcf6340 100644 --- a/lsfg-vk-gen/src/context.cpp +++ b/lsfg-vk-gen/src/context.cpp @@ -8,7 +8,8 @@ using namespace LSFG; -Context::Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1, int out) { +Context::Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1, + const std::vector& outN) { // import images this->inImg_0 = Core::Image(device, { width, height }, VK_FORMAT_R8G8B8A8_UNORM, @@ -25,16 +26,24 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in this->descPool = Core::DescriptorPool(device); this->cmdPool = Core::CommandPool(device); + // prepare vectors + for (size_t i = 0; i < 8; i++) + this->internalSemaphores.at(i).resize(outN.size()); + for (size_t i = 0; i < outN.size(); i++) { + this->outSemaphores.emplace_back(); + this->cmdBuffers2.emplace_back(); + } + // create shader chains this->downsampleChain = Shaderchains::Downsample(device, this->descPool, - this->inImg_0, this->inImg_1); + this->inImg_0, this->inImg_1, outN.size()); for (size_t i = 0; i < 7; i++) this->alphaChains.at(i) = Shaderchains::Alpha(device, this->descPool, this->downsampleChain.getOutImages().at(i)); this->betaChain = Shaderchains::Beta(device, this->descPool, this->alphaChains.at(0).getOutImages0(), this->alphaChains.at(0).getOutImages1(), - this->alphaChains.at(0).getOutImages2()); + this->alphaChains.at(0).getOutImages2(), outN.size()); for (size_t i = 0; i < 7; i++) { if (i < 4) { this->gammaChains.at(i) = Shaderchains::Gamma(device, this->descPool, @@ -46,7 +55,8 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in : std::optional{this->gammaChains.at(i - 1).getOutImage2()}, i == 0 ? std::nullopt : std::optional{this->gammaChains.at(i - 1).getOutImage1()}, - this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() + this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent(), + outN.size() ); } else { this->magicChains.at(i - 4) = Shaderchains::Magic(device, this->descPool, @@ -57,31 +67,36 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in : this->extractChains.at(i - 5).getOutImage(), i == 4 ? this->gammaChains.at(i - 1).getOutImage1() : this->zetaChains.at(i - 5).getOutImage(), - i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()} + i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()}, + outN.size() ); this->deltaChains.at(i - 4) = Shaderchains::Delta(device, this->descPool, this->magicChains.at(i - 4).getOutImages1(), i == 4 ? std::nullopt - : std::optional{this->deltaChains.at(i - 5).getOutImage()} + : std::optional{this->deltaChains.at(i - 5).getOutImage()}, + outN.size() ); this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(device, this->descPool, this->magicChains.at(i - 4).getOutImages2(), this->betaChain.getOutImages().at(6 - i), i == 4 ? std::nullopt - : std::optional{this->epsilonChains.at(i - 5).getOutImage()} + : std::optional{this->epsilonChains.at(i - 5).getOutImage()}, + outN.size() ); this->zetaChains.at(i - 4) = Shaderchains::Zeta(device, this->descPool, this->magicChains.at(i - 4).getOutImages3(), i == 4 ? this->gammaChains.at(i - 1).getOutImage1() : this->zetaChains.at(i - 5).getOutImage(), - this->betaChain.getOutImages().at(6 - i) + this->betaChain.getOutImages().at(6 - i), + outN.size() ); if (i >= 6) continue; // no extract for i >= 6 this->extractChains.at(i - 4) = Shaderchains::Extract(device, this->descPool, this->zetaChains.at(i - 4).getOutImage(), this->epsilonChains.at(i - 4).getOutImage(), - this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent() + this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent(), + outN.size() ); } } @@ -91,41 +106,61 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in this->zetaChains.at(2).getOutImage(), this->epsilonChains.at(2).getOutImage(), this->deltaChains.at(2).getOutImage(), - out + outN, + outN.size() ); } -void Context::present(const Core::Device& device, int inSem, int outSem) { +void Context::present(const Core::Device& device, int inSem, + const std::vector& outSem) { auto& inSemaphore = this->inSemaphores.at(this->fc % 8); inSemaphore = Core::Semaphore(device, inSem); - auto& outSemaphore = this->outSemaphores.at(this->fc % 8); - outSemaphore = Core::Semaphore(device, outSem); + auto& internalSemaphores = this->internalSemaphores.at(this->fc % 8); + for (size_t i = 0; i < outSem.size(); i++) + internalSemaphores.at(i) = Core::Semaphore(device); - auto& cmdBuffer = this->cmdBuffers.at(this->fc % 8); - cmdBuffer = Core::CommandBuffer(device, this->cmdPool); - cmdBuffer.begin(); + auto& cmdBuffer1 = this->cmdBuffers1.at(this->fc % 8); + cmdBuffer1 = Core::CommandBuffer(device, this->cmdPool); + cmdBuffer1.begin(); - this->downsampleChain.Dispatch(cmdBuffer, fc); + this->downsampleChain.Dispatch(cmdBuffer1, fc); for (size_t i = 0; i < 7; i++) - this->alphaChains.at(6 - i).Dispatch(cmdBuffer, fc); - this->betaChain.Dispatch(cmdBuffer, fc); - for (size_t i = 0; i < 4; i++) - this->gammaChains.at(i).Dispatch(cmdBuffer, fc); - for (size_t i = 0; i < 3; i++) { - this->magicChains.at(i).Dispatch(cmdBuffer, fc); - this->deltaChains.at(i).Dispatch(cmdBuffer); - this->epsilonChains.at(i).Dispatch(cmdBuffer); - this->zetaChains.at(i).Dispatch(cmdBuffer); - if (i < 2) - this->extractChains.at(i).Dispatch(cmdBuffer); - } - this->mergeChain.Dispatch(cmdBuffer, fc); + this->alphaChains.at(6 - i).Dispatch(cmdBuffer1, fc); - cmdBuffer.end(); + cmdBuffer1.end(); - cmdBuffer.submit(device.getComputeQueue(), std::nullopt, + cmdBuffer1.submit(device.getComputeQueue(), std::nullopt, { inSemaphore }, std::nullopt, - { outSemaphore }, std::nullopt); + internalSemaphores, std::nullopt); + + + for (size_t pass = 0; pass < outSem.size(); pass++) { + auto& outSemaphore = this->outSemaphores.at(pass).at(this->fc % 8); + outSemaphore = Core::Semaphore(device, outSem.at(pass)); + + auto& cmdBuffer2 = this->cmdBuffers2.at(pass).at(this->fc % 8); + cmdBuffer2 = Core::CommandBuffer(device, this->cmdPool); + cmdBuffer2.begin(); + + this->betaChain.Dispatch(cmdBuffer2, fc, pass); + for (size_t i = 0; i < 4; i++) + this->gammaChains.at(i).Dispatch(cmdBuffer2, fc, pass); + for (size_t i = 0; i < 3; i++) { + this->magicChains.at(i).Dispatch(cmdBuffer2, fc, pass); + this->deltaChains.at(i).Dispatch(cmdBuffer2, pass); + this->epsilonChains.at(i).Dispatch(cmdBuffer2, pass); + this->zetaChains.at(i).Dispatch(cmdBuffer2, pass); + if (i < 2) + this->extractChains.at(i).Dispatch(cmdBuffer2, pass); + } + this->mergeChain.Dispatch(cmdBuffer2, fc, pass); + + cmdBuffer2.end(); + + cmdBuffer2.submit(device.getComputeQueue(), std::nullopt, + { internalSemaphores.at(pass) }, std::nullopt, + { outSemaphore }, std::nullopt); + } fc++; } diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp index 7b0750e..7519d5a 100644 --- a/lsfg-vk-gen/src/lsfg.cpp +++ b/lsfg-vk-gen/src/lsfg.cpp @@ -28,16 +28,17 @@ void LSFG::initialize() { std::srand(static_cast(std::time(nullptr))); } -int32_t LSFG::createContext(uint32_t width, uint32_t height, int in0, int in1, int out) { +int32_t LSFG::createContext(uint32_t width, uint32_t height, int in0, int in1, + const std::vector& outN) { if (!instance.has_value() || !device.has_value()) throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); auto id = std::rand(); - contexts.emplace(id, Context(*device, width, height, in0, in1, out)); + contexts.emplace(id, Context(*device, width, height, in0, in1, outN)); return id; } -void LSFG::presentContext(int32_t id, int inSem, int outSem) { +void LSFG::presentContext(int32_t id, int inSem, const std::vector& outSem) { if (!instance.has_value() || !device.has_value()) throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); diff --git a/lsfg-vk-gen/src/shaderchains/beta.cpp b/lsfg-vk-gen/src/shaderchains/beta.cpp index c3d3a2e..19f0cef 100644 --- a/lsfg-vk-gen/src/shaderchains/beta.cpp +++ b/lsfg-vk-gen/src/shaderchains/beta.cpp @@ -6,7 +6,8 @@ using namespace LSFG::Shaderchains; Beta::Beta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs_0, std::array inImgs_1, - std::array inImgs_2) + std::array inImgs_2, + size_t genc) : inImgs_0(std::move(inImgs_0)), inImgs_1(std::move(inImgs_1)), inImgs_2(std::move(inImgs_2)) { @@ -36,14 +37,21 @@ Beta::Beta(const Core::Device& device, const Core::DescriptorPool& pool, for (size_t i = 0; i < 5; i++) { this->pipelines.at(i) = Core::Pipeline(device, this->shaderModules.at(i)); - if (i == 0) continue; // first shader has special logic + if (i == 0 || i == 4) continue; // first shader has special logic this->descriptorSets.at(i - 1) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); } for (size_t i = 0; i < 3; i++) this->specialDescriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(0)); - this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + for (size_t i = 0; i < genc; i++) + this->nDescriptorSets.emplace_back(device, pool, + this->shaderModules.at(4)); + for (size_t i = 0; i < genc; i++) { + auto data = Globals::fgBuffer; + data.timestamp = static_cast(i + 1) / static_cast(genc + 1); + this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + } const auto extent = this->inImgs_0.at(0).getExtent(); @@ -104,15 +112,17 @@ Beta::Beta(const Core::Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) .build(); - this->descriptorSets.at(3).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t i = 0; i < genc; i++) { + this->nDescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) + .build(); + } } -void Beta::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { +void Beta::Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass) { const auto extent = this->tempImgs1.at(0).getExtent(); // first pass @@ -170,6 +180,6 @@ void Beta::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipelines.at(4).bind(buf); - this->descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + this->nDescriptorSets.at(pass).bind(buf, this->pipelines.at(4)); buf.dispatch(threadsX, threadsY, 1); } diff --git a/lsfg-vk-gen/src/shaderchains/delta.cpp b/lsfg-vk-gen/src/shaderchains/delta.cpp index 295ce97..7503729 100644 --- a/lsfg-vk-gen/src/shaderchains/delta.cpp +++ b/lsfg-vk-gen/src/shaderchains/delta.cpp @@ -5,7 +5,8 @@ using namespace LSFG::Shaderchains; Delta::Delta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs, - std::optional optImg) + std::optional optImg, + size_t genc) : inImgs(std::move(inImgs)), optImg(std::move(optImg)) { this->shaderModules = {{ @@ -30,13 +31,19 @@ Delta::Delta(const Core::Device& device, const Core::DescriptorPool& pool, for (size_t i = 0; i < 4; i++) { this->pipelines.at(i) = Core::Pipeline(device, this->shaderModules.at(i)); + if (i == 3) continue; this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); } - - Globals::FgBuffer data = Globals::fgBuffer; - data.firstIterS = !this->optImg.has_value(); - this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + for (size_t i = 0; i < genc; i++) + this->nDescriptorSets.emplace_back(device, pool, + this->shaderModules.at(3)); + for (size_t i = 0; i < genc; i++) { + auto data = Globals::fgBuffer; + data.timestamp = static_cast(i + 1) / static_cast(genc + 1); + data.firstIterS = !this->optImg.has_value(); + this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + } const auto extent = this->inImgs.at(0).getExtent(); @@ -74,17 +81,19 @@ Delta::Delta(const Core::Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) .build(); - this->descriptorSets.at(3).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t i = 0; i < genc; i++) { + this->nDescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) + .build(); + } } -void Delta::Dispatch(const Core::CommandBuffer& buf) { +void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t pass) { const auto extent = this->tempImgs1.at(0).getExtent(); // first pass @@ -128,6 +137,6 @@ void Delta::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipelines.at(3).bind(buf); - this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); + this->nDescriptorSets.at(pass).bind(buf, this->pipelines.at(3)); buf.dispatch(threadsX, threadsY, 1); } diff --git a/lsfg-vk-gen/src/shaderchains/downsample.cpp b/lsfg-vk-gen/src/shaderchains/downsample.cpp index 7b6ac12..f06dd59 100644 --- a/lsfg-vk-gen/src/shaderchains/downsample.cpp +++ b/lsfg-vk-gen/src/shaderchains/downsample.cpp @@ -4,7 +4,8 @@ using namespace LSFG::Shaderchains; Downsample::Downsample(const Core::Device& device, const Core::DescriptorPool& pool, - Core::Image inImg_0, Core::Image inImg_1) + Core::Image inImg_0, Core::Image inImg_1, + size_t genc) : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) { this->shaderModule = Core::ShaderModule(device, "rsc/shaders/downsample.spv", @@ -15,7 +16,10 @@ Downsample::Downsample(const Core::Device& device, const Core::DescriptorPool& p this->pipeline = Core::Pipeline(device, this->shaderModule); for (size_t i = 0; i < 2; i++) this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModule); - this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + + auto data = Globals::fgBuffer; + data.timestamp = 1.0F / static_cast(genc + 1); + this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); auto extent = this->inImg_0.getExtent(); for (size_t i = 0; i < 7; i++) diff --git a/lsfg-vk-gen/src/shaderchains/epsilon.cpp b/lsfg-vk-gen/src/shaderchains/epsilon.cpp index 8ba9474..2093623 100644 --- a/lsfg-vk-gen/src/shaderchains/epsilon.cpp +++ b/lsfg-vk-gen/src/shaderchains/epsilon.cpp @@ -6,7 +6,8 @@ using namespace LSFG::Shaderchains; Epsilon::Epsilon(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, - std::optional optImg) + std::optional optImg, + size_t genc) : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), optImg(std::move(optImg)) { @@ -32,10 +33,18 @@ Epsilon::Epsilon(const Core::Device& device, const Core::DescriptorPool& pool, for (size_t i = 0; i < 4; i++) { this->pipelines.at(i) = Core::Pipeline(device, this->shaderModules.at(i)); + if (i == 3) continue; this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); } - this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + for (size_t i = 0; i < genc; i++) + this->nDescriptorSets.emplace_back(device, pool, + this->shaderModules.at(3)); + for (size_t i = 0; i < genc; i++) { + auto data = Globals::fgBuffer; + data.timestamp = static_cast(i + 1) / static_cast(genc + 1); + this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + } const auto extent = this->inImgs1.at(0).getExtent(); @@ -73,18 +82,20 @@ Epsilon::Epsilon(const Core::Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) .build(); - this->descriptorSets.at(3).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t i = 0; i < genc; i++) { + this->nDescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) + .build(); + } } -void Epsilon::Dispatch(const Core::CommandBuffer& buf) { +void Epsilon::Dispatch(const Core::CommandBuffer& buf, uint64_t pass) { const auto extent = this->tempImgs1.at(0).getExtent(); // first pass @@ -129,6 +140,6 @@ void Epsilon::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipelines.at(3).bind(buf); - this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); + this->nDescriptorSets.at(pass).bind(buf, this->pipelines.at(3)); buf.dispatch(threadsX, threadsY, 1); } diff --git a/lsfg-vk-gen/src/shaderchains/extract.cpp b/lsfg-vk-gen/src/shaderchains/extract.cpp index ee1fb00..4f63223 100644 --- a/lsfg-vk-gen/src/shaderchains/extract.cpp +++ b/lsfg-vk-gen/src/shaderchains/extract.cpp @@ -6,7 +6,8 @@ using namespace LSFG::Shaderchains; Extract::Extract(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg1, Core::Image inImg2, - VkExtent2D outExtent) + VkExtent2D outExtent, + size_t genc) : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)) { this->shaderModule = Core::ShaderModule(device, "rsc/shaders/extract.spv", @@ -15,8 +16,14 @@ Extract::Extract(const Core::Device& device, const Core::DescriptorPool& pool, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); this->pipeline = Core::Pipeline(device, this->shaderModule); - this->descriptorSet = Core::DescriptorSet(device, pool, this->shaderModule); - this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + for (size_t i = 0; i < genc; i++) + this->nDescriptorSets.emplace_back(device, pool, + this->shaderModule); + for (size_t i = 0; i < genc; i++) { + auto data = Globals::fgBuffer; + data.timestamp = static_cast(i + 1) / static_cast(genc + 1); + this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + } this->whiteImg = Core::Image(device, outExtent, @@ -30,21 +37,23 @@ Extract::Extract(const Core::Device& device, const Core::DescriptorPool& pool, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT); - this->descriptorSet.update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t i = 0; i < genc; i++) { + this->nDescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) + .build(); + } // clear white image Utils::clearImage(device, this->whiteImg, true); } -void Extract::Dispatch(const Core::CommandBuffer& buf) { +void Extract::Dispatch(const Core::CommandBuffer& buf, uint64_t pass) { auto extent = this->whiteImg.getExtent(); // first pass @@ -59,6 +68,6 @@ void Extract::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipeline.bind(buf); - this->descriptorSet.bind(buf, this->pipeline); + this->nDescriptorSets.at(pass).bind(buf, this->pipeline); buf.dispatch(threadsX, threadsY, 1); } diff --git a/lsfg-vk-gen/src/shaderchains/gamma.cpp b/lsfg-vk-gen/src/shaderchains/gamma.cpp index ad82f10..f469a9c 100644 --- a/lsfg-vk-gen/src/shaderchains/gamma.cpp +++ b/lsfg-vk-gen/src/shaderchains/gamma.cpp @@ -10,7 +10,8 @@ Gamma::Gamma(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg2, std::optional optImg1, // NOLINT std::optional optImg2, - VkExtent2D outExtent) + VkExtent2D outExtent, + size_t genc) : inImgs1_0(std::move(inImgs1_0)), inImgs1_1(std::move(inImgs1_1)), inImgs1_2(std::move(inImgs1_2)), @@ -48,17 +49,28 @@ Gamma::Gamma(const Core::Device& device, const Core::DescriptorPool& pool, for (size_t i = 0; i < 6; i++) { this->pipelines.at(i) = Core::Pipeline(device, this->shaderModules.at(i)); - if (i == 0) continue; // first shader has special logic + if (i == 0 || i >= 4) continue; // first shader has special logic this->descriptorSets.at(i - 1) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); } - for (size_t i = 0; i < 3; i++) - this->specialDescriptorSets.at(i) = Core::DescriptorSet(device, pool, - this->shaderModules.at(0)); - - Globals::FgBuffer data = Globals::fgBuffer; - data.firstIter = !optImg1.has_value(); - this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + for (size_t i = 0; i < genc; i++) + this->n1DescriptorSets.emplace_back(device, pool, + this->shaderModules.at(4)); + for (size_t i = 0; i < genc; i++) + this->n2DescriptorSets.emplace_back(device, pool, + this->shaderModules.at(5)); + for (size_t i = 0; i < genc; i++) { + this->nSpecialDescriptorSets.emplace_back(); + for (size_t j = 0; j < 3; j++) + this->nSpecialDescriptorSets.at(i).at(j) = Core::DescriptorSet(device, pool, + this->shaderModules.at(0)); + } + for (size_t i = 0; i < genc; i++) { + auto data = Globals::fgBuffer; + data.timestamp = static_cast(i + 1) / static_cast(genc + 1); + data.firstIter = !optImg1.has_value(); + this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + } const auto extent = this->inImgs1_0.at(0).getExtent(); @@ -107,18 +119,20 @@ Gamma::Gamma(const Core::Device& device, const Core::DescriptorPool& pool, nextImgs1 = &this->inImgs1_2; prevImgs1 = &this->inImgs1_1; } - this->specialDescriptorSets.at(fc).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *nextImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) - .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)) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t i = 0; i < genc; i++) { + this->nSpecialDescriptorSets.at(i).at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *nextImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) + .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)) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) + .build(); + } } this->descriptorSets.at(0).update(device) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) @@ -137,23 +151,25 @@ Gamma::Gamma(const Core::Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) .build(); - this->descriptorSets.at(3).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); - this->descriptorSets.at(4).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->outImg1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t i = 0; i < genc; i++) { + this->n1DescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) + .build(); + this->n2DescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->outImg1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) + .build(); + } // clear white image and optImg1 if needed Utils::clearImage(device, this->whiteImg, true); @@ -161,7 +177,7 @@ Gamma::Gamma(const Core::Device& device, const Core::DescriptorPool& pool, Utils::clearImage(device, this->optImg1); } -void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { +void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass) { const auto extent = this->tempImgs1.at(0).getExtent(); // first pass @@ -188,7 +204,7 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipelines.at(0).bind(buf); - this->specialDescriptorSets.at(fc % 3).bind(buf, this->pipelines.at(0)); + this->nSpecialDescriptorSets.at(pass).at(fc % 3).bind(buf, this->pipelines.at(0)); buf.dispatch(threadsX, threadsY, 1); // second pass @@ -232,7 +248,7 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipelines.at(4).bind(buf); - this->descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + this->n1DescriptorSets.at(pass).bind(buf, this->pipelines.at(4)); buf.dispatch(threadsX, threadsY, 1); // sixth pass @@ -246,6 +262,6 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipelines.at(5).bind(buf); - this->descriptorSets.at(4).bind(buf, this->pipelines.at(5)); + this->n2DescriptorSets.at(pass).bind(buf, this->pipelines.at(5)); buf.dispatch(threadsX, threadsY, 1); } diff --git a/lsfg-vk-gen/src/shaderchains/magic.cpp b/lsfg-vk-gen/src/shaderchains/magic.cpp index dca66b5..6a89f09 100644 --- a/lsfg-vk-gen/src/shaderchains/magic.cpp +++ b/lsfg-vk-gen/src/shaderchains/magic.cpp @@ -9,7 +9,8 @@ Magic::Magic(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1_2, Core::Image inImg2, Core::Image inImg3, - std::optional optImg) + std::optional optImg, + size_t genc) : inImgs1_0(std::move(inImgs1_0)), inImgs1_1(std::move(inImgs1_1)), inImgs1_2(std::move(inImgs1_2)), @@ -21,12 +22,17 @@ Magic::Magic(const Core::Device& device, const Core::DescriptorPool& pool, { 3+3+2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); this->pipeline = Core::Pipeline(device, this->shaderModule); - for (size_t i = 0; i < 3; i++) - this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModule); - - Globals::FgBuffer data = Globals::fgBuffer; - data.firstIterS = !this->optImg.has_value(); - this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + for (size_t i = 0; i < genc; i++) { + this->nDescriptorSets.emplace_back(); + for (size_t j = 0; j < 3; j++) + this->nDescriptorSets.at(i).at(j) = Core::DescriptorSet(device, pool, this->shaderModule); + } + for (size_t i = 0; i < genc; i++) { + auto data = Globals::fgBuffer; + data.timestamp = static_cast(i + 1) / static_cast(genc + 1); + data.firstIterS = !this->optImg.has_value(); + this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + } auto extent = this->inImgs1_0.at(0).getExtent(); @@ -59,23 +65,25 @@ Magic::Magic(const Core::Device& device, const Core::DescriptorPool& pool, nextImgs1 = &this->inImgs1_2; prevImgs1 = &this->inImgs1_1; } - this->descriptorSets.at(fc).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *nextImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs3) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t i = 0; i < genc; i++) { + this->nDescriptorSets.at(i).at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *nextImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs1) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) + .build(); + } } } -void Magic::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { +void Magic::Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass) { auto extent = this->inImgs1_0.at(0).getExtent(); // first pass @@ -103,6 +111,6 @@ void Magic::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .build(); this->pipeline.bind(buf); - this->descriptorSets.at(fc % 3).bind(buf, this->pipeline); + this->nDescriptorSets.at(pass).at(fc % 3).bind(buf, this->pipeline); buf.dispatch(threadsX, threadsY, 1); } diff --git a/lsfg-vk-gen/src/shaderchains/merge.cpp b/lsfg-vk-gen/src/shaderchains/merge.cpp index 36ff3c3..bd2cb92 100644 --- a/lsfg-vk-gen/src/shaderchains/merge.cpp +++ b/lsfg-vk-gen/src/shaderchains/merge.cpp @@ -9,7 +9,8 @@ Merge::Merge(const Core::Device& device, const Core::DescriptorPool& pool, Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, - int outFd) + const std::vector& outFds, + size_t genc) : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), @@ -21,35 +22,45 @@ Merge::Merge(const Core::Device& device, const Core::DescriptorPool& pool, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); this->pipeline = Core::Pipeline(device, this->shaderModule); - for (size_t i = 0; i < 2; i++) - this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModule); - this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + for (size_t i = 0; i < genc; i++) { + this->nDescriptorSets.emplace_back(); + for (size_t j = 0; j < 2; j++) + this->nDescriptorSets.at(i).at(j) = Core::DescriptorSet(device, pool, this->shaderModule); + } + for (size_t i = 0; i < genc; i++) { + auto data = Globals::fgBuffer; + data.timestamp = static_cast(i + 1) / static_cast(genc + 1); + this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + } auto extent = this->inImg1.getExtent(); - this->outImg = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, - outFd); + for (size_t i = 0; i < genc; i++) + this->outImgs.emplace_back(device, + extent, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, + outFds.at(i)); for (size_t fc = 0; fc < 2; fc++) { - this->descriptorSets.at(fc).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg1 : this->inImg2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg2 : this->inImg1) - .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->outImg) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t i = 0; i < genc; i++) { + this->nDescriptorSets.at(i).at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg1 : this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg2 : this->inImg1) + .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)) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) + .build(); + } } } -void Merge::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { +void Merge::Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass) { auto extent = this->inImg1.getExtent(); // first pass @@ -62,10 +73,10 @@ void Merge::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { .addW2R(this->inImg3) .addW2R(this->inImg4) .addW2R(this->inImg5) - .addR2W(this->outImg) + .addR2W(this->outImgs.at(pass)) .build(); this->pipeline.bind(buf); - this->descriptorSets.at(fc % 2).bind(buf, this->pipeline); + this->nDescriptorSets.at(pass).at(fc % 2).bind(buf, this->pipeline); buf.dispatch(threadsX, threadsY, 1); } diff --git a/lsfg-vk-gen/src/shaderchains/zeta.cpp b/lsfg-vk-gen/src/shaderchains/zeta.cpp index 8b04126..b5b3c99 100644 --- a/lsfg-vk-gen/src/shaderchains/zeta.cpp +++ b/lsfg-vk-gen/src/shaderchains/zeta.cpp @@ -6,7 +6,8 @@ using namespace LSFG::Shaderchains; Zeta::Zeta(const Core::Device& device, const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, - Core::Image inImg3) + Core::Image inImg3, + size_t genc) : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), inImg3(std::move(inImg3)) { @@ -32,10 +33,18 @@ Zeta::Zeta(const Core::Device& device, const Core::DescriptorPool& pool, for (size_t i = 0; i < 4; i++) { this->pipelines.at(i) = Core::Pipeline(device, this->shaderModules.at(i)); + if (i == 3) continue; this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); } - this->buffer = Core::Buffer(device, Globals::fgBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + for (size_t i = 0; i < genc; i++) + this->nDescriptorSets.emplace_back(device, pool, + this->shaderModules.at(3)); + for (size_t i = 0; i < genc; i++) { + auto data = Globals::fgBuffer; + data.timestamp = static_cast(i + 1) / static_cast(genc + 1); + this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + } const auto extent = this->inImgs1.at(0).getExtent(); @@ -73,18 +82,20 @@ Zeta::Zeta(const Core::Device& device, const Core::DescriptorPool& pool, .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) .build(); - this->descriptorSets.at(3).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .build(); + for (size_t i = 0; i < genc; i++) { + this->nDescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i) ) + .build(); + } } -void Zeta::Dispatch(const Core::CommandBuffer& buf) { +void Zeta::Dispatch(const Core::CommandBuffer& buf, uint64_t pass) { const auto extent = this->tempImgs1.at(0).getExtent(); // first pass @@ -129,6 +140,6 @@ void Zeta::Dispatch(const Core::CommandBuffer& buf) { .build(); this->pipelines.at(3).bind(buf); - this->descriptorSets.at(3).bind(buf, this->pipelines.at(3)); + this->nDescriptorSets.at(pass).bind(buf, this->pipelines.at(3)); buf.dispatch(threadsX, threadsY, 1); } From 2fbc37e1ea77c8a6cda7fb11e2c7ded1fee22f1c Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 2 Jul 2025 14:46:38 +0200 Subject: [PATCH 061/253] mfg --- include/application.hpp | 12 +- src/application.cpp | 276 ++++++++++++++++++++++------------------ src/hooks.cpp | 2 +- 3 files changed, 159 insertions(+), 131 deletions(-) diff --git a/include/application.hpp b/include/application.hpp index 5133a04..c011ab7 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -153,14 +153,16 @@ private: // (owned resources) Mini::CommandPool cmdPool; std::array cmdBufs1; - std::array cmdBufs2; + std::array, 8> cmdBufs2; std::array copySemaphores1; // copy current swap to frame std::array copySemaphores2; // (for present) - std::array acquireSemaphores; // acquire new swapchain image - std::array renderSemaphores; // fg is done - std::array presentSemaphores; // copy is done, ready to present + std::array, 8> acquireSemaphores; // acquire new swapchain image + std::array, 8> renderSemaphores; // fg is done + std::array, 8> prevPresentSemaphores; // for inorder fg + std::array, 8> presentSemaphores; // copy is done, ready to present - Mini::Image frame_0, frame_1, out_img; + Mini::Image frame_0, frame_1; + std::vector outImgs; std::shared_ptr lsfgId; uint64_t frameIdx{0}; std::optional deferredIdx; // index of the frame to present next diff --git a/src/application.cpp b/src/application.cpp index 216019a..ba03f7d 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -4,12 +4,13 @@ #include "mini/image.hpp" #include "mini/semaphore.hpp" -#include -#include #include #include +#include #include +const int FRAMEGEN_N = 4; // number of frames to generate + Application::Application(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, uint32_t graphicsQueueFamilyIndex) : device(device), physicalDevice(physicalDevice), @@ -47,15 +48,25 @@ SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapch VK_IMAGE_ASPECT_COLOR_BIT, &frame1fd ); - int outfd{}; - this->out_img = Mini::Image( - app.getDevice(), app.getPhysicalDevice(), - extent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, &outfd - ); + std::vector outFds(FRAMEGEN_N); + for (size_t i = 0; i < FRAMEGEN_N; ++i) { + this->outImgs.emplace_back( + app.getDevice(), app.getPhysicalDevice(), + extent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, &outFds.at(i) + ); + } - auto id = LSFG::createContext(extent.width, extent.height, frame0fd, frame1fd, outfd); + for (size_t i = 0; i < 8; i++) { + this->cmdBufs2.at(i).resize(FRAMEGEN_N); + this->acquireSemaphores.at(i).resize(FRAMEGEN_N); + this->renderSemaphores.at(i).resize(FRAMEGEN_N); + this->prevPresentSemaphores.at(i).resize(FRAMEGEN_N); + this->presentSemaphores.at(i).resize(FRAMEGEN_N); + } + + auto id = LSFG::createContext(extent.width, extent.height, frame0fd, frame1fd, outFds); this->lsfgId = std::shared_ptr( new int32_t(id), [](const int32_t* id) { @@ -99,13 +110,6 @@ void SwapchainContext::present(const Application& app, VkQueue queue, copySemaphore1 = Mini::Semaphore(app.getDevice(), ©Sem); Mini::Semaphore& copySemaphore2 = this->copySemaphores2.at(this->frameIdx % 8); copySemaphore2 = Mini::Semaphore(app.getDevice()); - Mini::Semaphore& acquireSemaphore = this->acquireSemaphores.at(this->frameIdx % 8); - acquireSemaphore = Mini::Semaphore(app.getDevice()); - int renderSem{}; - Mini::Semaphore& renderSemaphore = this->renderSemaphores.at(this->frameIdx % 8); - renderSemaphore = Mini::Semaphore(app.getDevice(), &renderSem); - Mini::Semaphore& presentSemaphore = this->presentSemaphores.at(this->frameIdx % 8); - presentSemaphore = Mini::Semaphore(app.getDevice()); // copy swapchain image to next frame image auto& cmdBuf1 = this->cmdBufs1.at(this->frameIdx % 8); @@ -183,116 +187,138 @@ void SwapchainContext::present(const Application& app, VkQueue queue, cmdBuf1.submit(app.getGraphicsQueue(), semaphores, { copySemaphore1.handle(), copySemaphore2.handle() }); - // render the intermediary frame - try { - LSFG::presentContext(*this->lsfgId, copySem, renderSem); - } catch(std::exception& e) { - std::cerr << "LSFG error: " << e.what() << '\n'; + // render the intermediary frames + std::vector renderSems(FRAMEGEN_N); + auto& renderSemaphores = this->renderSemaphores.at(this->frameIdx % 8); + for (size_t i = 0; i < FRAMEGEN_N; i++) + renderSemaphores.at(i) = Mini::Semaphore(app.getDevice(), &renderSems.at(i)); + LSFG::presentContext(*this->lsfgId, copySem, renderSems); + + auto& acquireSemaphores = this->acquireSemaphores.at(this->frameIdx % 8); + auto& prevPresentSemaphores = this->prevPresentSemaphores.at(this->frameIdx % 8); + auto& presentSemaphores = this->presentSemaphores.at(this->frameIdx % 8); + auto& cmdBufs2 = this->cmdBufs2.at(this->frameIdx % 8); + for (size_t i = 0; i < FRAMEGEN_N; i++) { + // acquire the next swapchain image + auto& acquireSemaphore = acquireSemaphores.at(i); + acquireSemaphore = Mini::Semaphore(app.getDevice()); + + uint32_t newIdx{}; + auto res = vkAcquireNextImageKHR(app.getDevice(), this->swapchain, UINT64_MAX, + acquireSemaphore.handle(), VK_NULL_HANDLE, &newIdx); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image"); + + // copy the output image to the swapchain image + auto& presentSemaphore = presentSemaphores.at(i); + presentSemaphore = Mini::Semaphore(app.getDevice()); + + auto& cmdBuf2 = cmdBufs2.at(i); + cmdBuf2 = Mini::CommandBuffer(app.getDevice(), this->cmdPool); + cmdBuf2.begin(); + + auto& srcImage2 = this->outImgs.at(i); + auto& dstImage2 = this->images.at(newIdx); + + const VkImageMemoryBarrier srcBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .image = srcImage2.handle(), + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + const VkImageMemoryBarrier dstBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = dstImage2, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + const std::vector barriers2 = { srcBarrier2, dstBarrier2 }; + vkCmdPipelineBarrier(cmdBuf2.handle(), + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 2, barriers2.data()); + + const VkImageCopy imageCopy2{ + .srcSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 + }, + .dstSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 + }, + .extent = { + .width = this->extent.width, + .height = this->extent.height, + .depth = 1 + } + }; + vkCmdCopyImage(cmdBuf2.handle(), + srcImage2.handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstImage2, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &imageCopy2); + + const VkImageMemoryBarrier presentBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .image = dstImage2, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + vkCmdPipelineBarrier(cmdBuf2.handle(), + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, 0, nullptr, 0, nullptr, 1, &presentBarrier2); + + cmdBuf2.end(); + + std::vector waitSemaphores; + waitSemaphores.emplace_back(acquireSemaphore.handle()); + if (i != 0) // wait for previous present semaphore + waitSemaphores.emplace_back(prevPresentSemaphores.at(i - 1).handle()); + std::vector signalSemaphores; + signalSemaphores.emplace_back(presentSemaphore.handle()); + if (i != FRAMEGEN_N - 1) { + // signal next present semaphore + prevPresentSemaphores.at(i) = Mini::Semaphore(app.getDevice()); + signalSemaphores.emplace_back(prevPresentSemaphores.at(i).handle()); + } + cmdBuf2.submit(app.getGraphicsQueue(), waitSemaphores, signalSemaphores); + + // present the swapchain image + VkSemaphore presentSemaphoreHandle = presentSemaphore.handle(); + const VkPresentInfoKHR presentInfo = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = i == 0 ? pNext : nullptr, // only set on first present + .waitSemaphoreCount = 1, + .pWaitSemaphores = &presentSemaphoreHandle, + .swapchainCount = 1, + .pSwapchains = &this->swapchain, + .pImageIndices = &newIdx, + }; + res = vkQueuePresentKHR(queue, &presentInfo); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) // FIXME: somehow return VK_SUBOPTIMAL_KHR + throw LSFG::vulkan_error(res, "Failed to present swapchain"); } - // acquire the next swapchain image - uint32_t newIdx{}; - auto res = vkAcquireNextImageKHR(app.getDevice(), this->swapchain, UINT64_MAX, - acquireSemaphore.handle(), VK_NULL_HANDLE, &newIdx); - if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) - throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image"); - - // copy the output image to the swapchain image - auto& cmdBuf2 = this->cmdBufs2.at((this->frameIdx + 1) % 8); - cmdBuf2 = Mini::CommandBuffer(app.getDevice(), this->cmdPool); - cmdBuf2.begin(); - - auto& srcImage2 = this->out_img; - auto& dstImage2 = this->images.at(newIdx); - - const VkImageMemoryBarrier srcBarrier2{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, - .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .image = srcImage2.handle(), - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .levelCount = 1, - .layerCount = 1 - } - }; - const VkImageMemoryBarrier dstBarrier2{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .image = dstImage2, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .levelCount = 1, - .layerCount = 1 - } - }; - const std::vector barriers2 = { srcBarrier2, dstBarrier2 }; - vkCmdPipelineBarrier(cmdBuf2.handle(), - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, nullptr, 0, nullptr, 2, barriers2.data()); - - const VkImageCopy imageCopy2{ - .srcSubresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1 - }, - .dstSubresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1 - }, - .extent = { - .width = this->extent.width, - .height = this->extent.height, - .depth = 1 - } - }; - vkCmdCopyImage(cmdBuf2.handle(), - srcImage2.handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstImage2, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &imageCopy2); - - const VkImageMemoryBarrier presentBarrier2{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, - .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - .image = dstImage2, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .levelCount = 1, - .layerCount = 1 - } - }; - vkCmdPipelineBarrier(cmdBuf2.handle(), - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, 0, nullptr, 0, nullptr, 1, &presentBarrier2); - - cmdBuf2.end(); - cmdBuf2.submit(app.getGraphicsQueue(), - { acquireSemaphore.handle(), renderSemaphore.handle() }, - { presentSemaphore.handle() }); - - // present the swapchain image - VkSemaphore presentSemaphoreHandle = presentSemaphore.handle(); - const VkPresentInfoKHR presentInfo = { - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .pNext = pNext, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &presentSemaphoreHandle, - .swapchainCount = 1, - .pSwapchains = &this->swapchain, - .pImageIndices = &newIdx, - }; - res = vkQueuePresentKHR(queue, &presentInfo); - if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) // FIXME: somehow return VK_SUBOPTIMAL_KHR - throw LSFG::vulkan_error(res, "Failed to present swapchain"); - this->frameIdx++; } diff --git a/src/hooks.cpp b/src/hooks.cpp index 97d78cd..c706f6a 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -121,7 +121,7 @@ namespace { const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain) { VkSwapchainCreateInfoKHR createInfo = *pCreateInfo; - createInfo.minImageCount += 2; + createInfo.minImageCount = 8; createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; auto res = vkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); From 990f45f06d43e88b8a13bd0bc3442960b7fec378 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 2 Jul 2025 17:27:17 +0200 Subject: [PATCH 062/253] bugfixes --- src/application.cpp | 40 +++++++++++++++++++++++++--------------- src/hooks.cpp | 4 +++- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/application.cpp b/src/application.cpp index ba03f7d..684fc52 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -9,7 +9,7 @@ #include #include -const int FRAMEGEN_N = 4; // number of frames to generate +const int FRAMEGEN_N = 2; // number of frames to generate Application::Application(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, uint32_t graphicsQueueFamilyIndex) @@ -87,6 +87,7 @@ void Application::presentSwapchain(VkSwapchainKHR handle, VkQueue queue, void SwapchainContext::present(const Application& app, VkQueue queue, const std::vector& semaphores, uint32_t idx, const void* pNext) { // present deferred frame if any + bool yes = this->deferredIdx.has_value(); if (this->deferredIdx.has_value()) { VkSemaphore deferredSemaphore = this->copySemaphores2.at((this->frameIdx - 1) % 8).handle(); const uint32_t deferredIdx = this->deferredIdx.value(); @@ -184,8 +185,15 @@ void SwapchainContext::present(const Application& app, VkQueue queue, 0, 0, nullptr, 0, nullptr, 1, &presentBarrier); cmdBuf1.end(); + + std::vector semaphores2 = semaphores; + if (yes) { + VkSemaphore prevPresentSemaphore = this->prevPresentSemaphores.at((this->frameIdx - 1) % 8).at(FRAMEGEN_N - 1).handle(); + semaphores2.emplace_back(prevPresentSemaphore); + } + cmdBuf1.submit(app.getGraphicsQueue(), - semaphores, { copySemaphore1.handle(), copySemaphore2.handle() }); + semaphores2, { copySemaphore1.handle(), copySemaphore2.handle() }); // render the intermediary frames std::vector renderSems(FRAMEGEN_N); @@ -280,6 +288,7 @@ void SwapchainContext::present(const Application& app, VkQueue queue, .image = dstImage2, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, .layerCount = 1 } @@ -290,26 +299,27 @@ void SwapchainContext::present(const Application& app, VkQueue queue, cmdBuf2.end(); - std::vector waitSemaphores; - waitSemaphores.emplace_back(acquireSemaphore.handle()); - if (i != 0) // wait for previous present semaphore - waitSemaphores.emplace_back(prevPresentSemaphores.at(i - 1).handle()); + std::vector signalSemaphores; signalSemaphores.emplace_back(presentSemaphore.handle()); - if (i != FRAMEGEN_N - 1) { - // signal next present semaphore - prevPresentSemaphores.at(i) = Mini::Semaphore(app.getDevice()); - signalSemaphores.emplace_back(prevPresentSemaphores.at(i).handle()); - } - cmdBuf2.submit(app.getGraphicsQueue(), waitSemaphores, signalSemaphores); + // signal next present semaphore + prevPresentSemaphores.at(i) = Mini::Semaphore(app.getDevice()); + signalSemaphores.emplace_back(prevPresentSemaphores.at(i).handle()); + auto& renderSemaphore = renderSemaphores.at(i); + cmdBuf2.submit(app.getGraphicsQueue(), + { acquireSemaphore.handle(), renderSemaphore.handle() }, + signalSemaphores); // present the swapchain image - VkSemaphore presentSemaphoreHandle = presentSemaphore.handle(); + std::vector waitSemaphores; + waitSemaphores.emplace_back(presentSemaphore.handle()); + if (i != 0) // wait for previous present semaphore + waitSemaphores.emplace_back(prevPresentSemaphores.at(i - 1).handle()); const VkPresentInfoKHR presentInfo = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .pNext = i == 0 ? pNext : nullptr, // only set on first present - .waitSemaphoreCount = 1, - .pWaitSemaphores = &presentSemaphoreHandle, + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphores.data(), .swapchainCount = 1, .pSwapchains = &this->swapchain, .pImageIndices = &newIdx, diff --git a/src/hooks.cpp b/src/hooks.cpp index c706f6a..bd01b5d 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -4,6 +4,7 @@ #include "application.hpp" #include "log.hpp" +#include #include #include @@ -121,9 +122,10 @@ namespace { const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain) { VkSwapchainCreateInfoKHR createInfo = *pCreateInfo; - createInfo.minImageCount = 8; + createInfo.minImageCount += 3; createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; auto res = vkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); // add the swapchain to the application From d5cfab75d7fed93b992807b56c6106c574338904 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 3 Jul 2025 08:01:36 +0200 Subject: [PATCH 063/253] refactor hooks --- CMakeLists.txt | 1 + include/application.hpp | 171 ------------------ include/context.hpp | 83 +++++++++ include/hooks.hpp | 12 ++ include/mini/image.hpp | 10 -- include/utils.hpp | 55 ++++++ lsfg-vk-gen/src/context.cpp | 1 + src/application.cpp | 346 ------------------------------------ src/context.cpp | 155 ++++++++++++++++ src/hooks.cpp | 295 ++++++++++++------------------ src/mini/commandbuffer.cpp | 3 +- src/mini/commandpool.cpp | 3 +- src/mini/image.cpp | 34 +--- src/mini/semaphore.cpp | 3 +- src/utils.cpp | 142 +++++++++++++++ 15 files changed, 568 insertions(+), 746 deletions(-) delete mode 100644 include/application.hpp create mode 100644 include/context.hpp create mode 100644 include/utils.hpp delete mode 100644 src/application.cpp create mode 100644 src/context.cpp create mode 100644 src/utils.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fb353c..1b21351 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ target_compile_options(lsfg-vk PRIVATE -Wno-switch-default # ignore missing default -Wno-padded # ignore automatic padding -Wno-exit-time-destructors # allow globals + -Wno-global-constructors # allow globals # required for vulkan -Wno-cast-function-type-strict ) diff --git a/include/application.hpp b/include/application.hpp deleted file mode 100644 index c011ab7..0000000 --- a/include/application.hpp +++ /dev/null @@ -1,171 +0,0 @@ -#ifndef APPLICATION_HPP -#define APPLICATION_HPP - -#include "mini/commandbuffer.hpp" -#include "mini/commandpool.hpp" -#include "mini/image.hpp" -#include "mini/semaphore.hpp" -#include -#include -#include -#include - -#include - -class SwapchainContext; - -/// -/// Main application class, wrapping around the Vulkan device. -/// -class Application { -public: - /// - /// Create the application. - /// - /// @param device Vulkan device - /// @param physicalDevice Vulkan physical device - /// @param graphicsQueue Vulkan queue for graphics operations - /// @param graphicsQueueFamilyIndex The family index of the graphics queue - /// - /// @throws LSFG::vulkan_error if any Vulkan call fails. - /// - Application(VkDevice device, VkPhysicalDevice physicalDevice, - VkQueue graphicsQueue, uint32_t graphicsQueueFamilyIndex); - - /// - /// Add a swapchain to the application. - /// - /// @param handle The Vulkan handle of the swapchain. - /// @param format The format of the swapchain. - /// @param extent The extent of the images. - /// @param images The swapchain images. - /// - /// @throws LSFG::vulkan_error if any Vulkan call fails. - /// - void addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, - const std::vector& images); - - /// - /// Present the next frame on a given swapchain. - /// - /// @param handle The Vulkan handle of the swapchain to present on. - /// @param queue The Vulkan queue to present the frame on. - /// @param semaphores The semaphores to wait on before presenting. - /// @param idx The index of the swapchain image to present. - /// @param pNext Pointer to the next structure in a chain, if any. - /// - /// @throws LSFG::vulkan_error if any Vulkan call fails. - /// - void presentSwapchain(VkSwapchainKHR handle, VkQueue queue, - const std::vector& semaphores, uint32_t idx, const void* pNext); - - /// - /// Remove a swapchain from the application. - /// - /// @param handle The Vulkan handle of the swapchain state to remove. - /// @return true if the swapchain was removed, false if it was already retired. - /// - bool removeSwapchain(VkSwapchainKHR handle); - - /// Get the Vulkan device. - [[nodiscard]] VkDevice getDevice() const { return this->device; } - /// Get the Vulkan physical device. - [[nodiscard]] VkPhysicalDevice getPhysicalDevice() const { return this->physicalDevice; } - /// Get the Vulkan graphics queue. - [[nodiscard]] VkQueue getGraphicsQueue() const { return this->graphicsQueue; } - /// Get the graphics queue family index. - [[nodiscard]] uint32_t getGraphicsQueueFamilyIndex() const { return this->graphicsQueueFamilyIndex; } - - // Non-copyable and non-movable - Application(const Application&) = delete; - Application& operator=(const Application&) = delete; - Application(Application&&) = delete; - Application& operator=(Application&&) = delete; - - /// Destructor, cleans up resources. - ~Application(); -private: - // (non-owned resources) - VkDevice device; - VkPhysicalDevice physicalDevice; - VkQueue graphicsQueue; - uint32_t graphicsQueueFamilyIndex; - - // (owned resources) - std::unordered_map swapchains; -}; - -/// -/// An application's Vulkan swapchain and it's associated resources. -/// -class SwapchainContext { -public: - /// - /// Create the swapchain context. - /// - /// @param app The application context to use. - /// @param swapchain The Vulkan swapchain handle. - /// @param format The format of the swapchain images. - /// @param extent The extent of the swapchain images. - /// @param images The swapchain images. - /// - /// @throws LSFG::vulkan_error if any Vulkan call fails. - /// - SwapchainContext(const Application& app, VkSwapchainKHR swapchain, - VkFormat format, VkExtent2D extent, const std::vector& images); - - /// - /// Present the next frame - /// - /// @param app The application context to use - /// @param queue The Vulkan queue to present the frame on. - /// @param semaphores The semaphores to wait on before presenting. - /// @param idx The index of the swapchain image to present. - /// @param pNext Pointer to the next structure in a chain, if any. - /// - /// @throws LSFG::vulkan_error if any Vulkan call fails. - /// - void present(const Application& app, VkQueue queue, - const std::vector& semaphores, uint32_t idx, const void* pNext); - - /// Get the Vulkan swapchain handle. - [[nodiscard]] VkSwapchainKHR handle() const { return this->swapchain; } - /// Get the format of the swapchain images. - [[nodiscard]] VkFormat getFormat() const { return this->format; } - /// Get the extent of the swapchain images. - [[nodiscard]] VkExtent2D getExtent() const { return this->extent; } - /// Get the swapchain images. - [[nodiscard]] const std::vector& getImages() const { return this->images; } - - // Non-copyable, trivially moveable and destructible - SwapchainContext(const SwapchainContext&) = delete; - SwapchainContext& operator=(const SwapchainContext&) = delete; - SwapchainContext(SwapchainContext&&) = default; - SwapchainContext& operator=(SwapchainContext&&) = default; - ~SwapchainContext() = default; -private: - // (non-owned resources) - VkSwapchainKHR swapchain; - VkFormat format; - VkExtent2D extent; - std::vector images; - - // (owned resources) - Mini::CommandPool cmdPool; - std::array cmdBufs1; - std::array, 8> cmdBufs2; - std::array copySemaphores1; // copy current swap to frame - std::array copySemaphores2; // (for present) - std::array, 8> acquireSemaphores; // acquire new swapchain image - std::array, 8> renderSemaphores; // fg is done - std::array, 8> prevPresentSemaphores; // for inorder fg - std::array, 8> presentSemaphores; // copy is done, ready to present - - Mini::Image frame_0, frame_1; - std::vector outImgs; - std::shared_ptr lsfgId; - uint64_t frameIdx{0}; - std::optional deferredIdx; // index of the frame to present next -}; - -#endif // APPLICATION_HPP diff --git a/include/context.hpp b/include/context.hpp new file mode 100644 index 0000000..fbd2481 --- /dev/null +++ b/include/context.hpp @@ -0,0 +1,83 @@ +#ifndef CONTEXT_HPP +#define CONTEXT_HPP + +#include "hooks.hpp" +#include "mini/commandbuffer.hpp" +#include "mini/commandpool.hpp" +#include "mini/image.hpp" +#include "mini/semaphore.hpp" + +#include +#include +#include +#include + +#include + +/// +/// This class is the frame generation context. There should be one instance per swapchain. +/// +class LsContext { +public: + /// + /// Create the swapchain context. + /// + /// @param info The device information to use. + /// @param swapchain The Vulkan swapchain to use. + /// @param extent The extent of the swapchain images. + /// @param swapchainImages The swapchain images to use. + /// + /// @throws LSFG::vulkan_error if any Vulkan call fails. + /// + LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, + VkExtent2D extent, const std::vector& swapchainImages); + + /// + /// Custom present logic. + /// + /// @param info The device information to use. + /// @param pNext Unknown pointer set in the present info structure. + /// @param queue The Vulkan queue to present the frame on. + /// @param gameRenderSemaphores The semaphores to wait on before presenting. + /// @param presentIdx The index of the swapchain image to present. + /// @return The result of the Vulkan present operation, which can be VK_SUCCESS or VK_SUBOPTIMAL_KHR. + /// + /// @throws LSFG::vulkan_error if any Vulkan call fails. + /// + VkResult present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue, + const std::vector& gameRenderSemaphores, uint32_t presentIdx); + + // Non-copyable, trivially moveable and destructible + LsContext(const LsContext&) = delete; + LsContext& operator=(const LsContext&) = delete; + LsContext(LsContext&&) = default; + LsContext& operator=(LsContext&&) = default; + ~LsContext() = default; +private: + VkSwapchainKHR swapchain; + std::vector swapchainImages; + VkExtent2D extent; + + std::shared_ptr lsfgCtxId; // lsfg context id + Mini::Image frame_0, frame_1; // frames shared with lsfg. write to frame_0 when fc % 2 == 0 + std::vector out_n; // output images shared with lsfg, indexed by framegen id + + Mini::CommandPool cmdPool; + uint64_t frameIdx{0}; + + struct RenderPassInfo { + Mini::CommandBuffer preCopyBuf; // copy from swapchain image to frame_0/frame_1 + std::array preCopySemaphores; // signal when preCopyBuf is done + + std::vector renderSemaphores; // signal when lsfg is done with frame n + + std::vector acquireSemaphores; // signal for swapchain image n + + std::vector postCopyBufs; // copy from out_n to swapchain image + std::vector postCopySemaphores; // signal when postCopyBuf is done + std::vector prevPostCopySemaphores; // signal for previous postCopyBuf + }; // data for a single render pass + std::array passInfos; // allocate 8 because why not +}; + +#endif // CONTEXT_HPP diff --git a/include/hooks.hpp b/include/hooks.hpp index f4ba294..0758e21 100644 --- a/include/hooks.hpp +++ b/include/hooks.hpp @@ -1,8 +1,20 @@ #ifndef HOOKS_HPP #define HOOKS_HPP +#include + +#include + namespace Hooks { + /// Vulkan device information structure. + struct DeviceInfo { + VkDevice device; + VkPhysicalDevice physicalDevice; + std::pair queue; // graphics family + uint64_t frameGen; // amount of frames to generate + }; + /// /// Install overrides for hooked Vulkan functions. /// diff --git a/include/mini/image.hpp b/include/mini/image.hpp index ed4d182..a1de0cf 100644 --- a/include/mini/image.hpp +++ b/include/mini/image.hpp @@ -36,8 +36,6 @@ namespace Mini { [[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. @@ -45,11 +43,6 @@ namespace Mini { /// 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; @@ -59,9 +52,6 @@ namespace Mini { private: std::shared_ptr image; std::shared_ptr memory; - std::shared_ptr view; - - std::shared_ptr layout; VkExtent2D extent{}; VkFormat format{}; diff --git a/include/utils.hpp b/include/utils.hpp new file mode 100644 index 0000000..136d50f --- /dev/null +++ b/include/utils.hpp @@ -0,0 +1,55 @@ +#ifndef UTILS_HPP +#define UTILS_HPP + +#include +#include + +#include + +namespace Utils { + + /// + /// Find a queue in the physical device that supports the given queue flags. + /// + /// @param device The Vulkan device to use for queue retrieval. + /// @param physicalDevice The physical device to search in. + /// @param desc The device creation info, used to determine enabled queue families. + /// @param flags The queue flags to search for (e.g., VK_QUEUE_GRAPHICS_BIT). + /// @return Pair of queue family index and queue handle. + /// + /// @throws LSFG::vulkan_error if no suitable queue is found. + /// + std::pair findQueue(VkDevice device, VkPhysicalDevice physicalDevice, + VkDeviceCreateInfo* desc, VkQueueFlags flags); + + /// + /// Ensure a list of extensions is present in the given array. + /// + /// @param extensions The array of extensions to check. + /// @param requiredExtensions The list of required extensions to ensure are present. + /// + std::vector addExtensions(const char* const* extensions, size_t count, + const std::vector& requiredExtensions); + + /// + /// Copy an image from source to destination in a command buffer. + /// + /// @param buf The command buffer to record the copy operation into. + /// @param src The source image to copy from. + /// @param dst The destination image to copy to. + /// @param width The width of the image to copy. + /// @param height The height of the image to copy. + /// @param pre The pipeline stage to wait on. + /// @param post The pipeline stage to provide after the copy. + /// @param makeSrcPresentable If true, the source image will be made presentable after the copy. + /// @param makeDstPresentable If true, the destination image will be made presentable after the copy. + /// + void copyImage(VkCommandBuffer buf, + VkImage src, VkImage dst, + uint32_t width, uint32_t height, + VkPipelineStageFlags pre, VkPipelineStageFlags post, + bool makeSrcPresentable, bool makeDstPresentable); + +} + +#endif // UTILS_HPP diff --git a/lsfg-vk-gen/src/context.cpp b/lsfg-vk-gen/src/context.cpp index fcf6340..6e26566 100644 --- a/lsfg-vk-gen/src/context.cpp +++ b/lsfg-vk-gen/src/context.cpp @@ -5,6 +5,7 @@ #include #include #include +#include using namespace LSFG; diff --git a/src/application.cpp b/src/application.cpp deleted file mode 100644 index 684fc52..0000000 --- a/src/application.cpp +++ /dev/null @@ -1,346 +0,0 @@ -#include "application.hpp" -#include "mini/commandbuffer.hpp" -#include "mini/commandpool.hpp" -#include "mini/image.hpp" -#include "mini/semaphore.hpp" - -#include -#include -#include -#include - -const int FRAMEGEN_N = 2; // number of frames to generate - -Application::Application(VkDevice device, VkPhysicalDevice physicalDevice, - VkQueue graphicsQueue, uint32_t graphicsQueueFamilyIndex) - : device(device), physicalDevice(physicalDevice), - graphicsQueue(graphicsQueue), graphicsQueueFamilyIndex(graphicsQueueFamilyIndex) { - LSFG::initialize(); -} - -void Application::addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, - const std::vector& images) { - auto it = this->swapchains.find(handle); - if (it != this->swapchains.end()) - return; // already added - - this->swapchains.emplace(handle, SwapchainContext(*this, handle, format, extent, images)); -} - -SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapchain, - VkFormat format, VkExtent2D extent, const std::vector& images) - : swapchain(swapchain), format(format), extent(extent), images(images) { - this->cmdPool = Mini::CommandPool(app.getDevice(), app.getGraphicsQueueFamilyIndex()); - - int frame0fd{}; - this->frame_0 = Mini::Image( - app.getDevice(), app.getPhysicalDevice(), - extent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, &frame0fd - ); - - int frame1fd{}; - this->frame_1 = Mini::Image( - app.getDevice(), app.getPhysicalDevice(), - extent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, &frame1fd - ); - - std::vector outFds(FRAMEGEN_N); - for (size_t i = 0; i < FRAMEGEN_N; ++i) { - this->outImgs.emplace_back( - app.getDevice(), app.getPhysicalDevice(), - extent, VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, &outFds.at(i) - ); - } - - for (size_t i = 0; i < 8; i++) { - this->cmdBufs2.at(i).resize(FRAMEGEN_N); - this->acquireSemaphores.at(i).resize(FRAMEGEN_N); - this->renderSemaphores.at(i).resize(FRAMEGEN_N); - this->prevPresentSemaphores.at(i).resize(FRAMEGEN_N); - this->presentSemaphores.at(i).resize(FRAMEGEN_N); - } - - auto id = LSFG::createContext(extent.width, extent.height, frame0fd, frame1fd, outFds); - this->lsfgId = std::shared_ptr( - new int32_t(id), - [](const int32_t* id) { - LSFG::deleteContext(*id); - } - ); -} - -void Application::presentSwapchain(VkSwapchainKHR handle, VkQueue queue, - const std::vector& semaphores, uint32_t idx, const void* pNext) { - auto it = this->swapchains.find(handle); - if (it == this->swapchains.end()) - throw std::logic_error("Swapchain not found"); - - it->second.present(*this, queue, semaphores, idx, pNext); -} - -void SwapchainContext::present(const Application& app, VkQueue queue, - const std::vector& semaphores, uint32_t idx, const void* pNext) { - // present deferred frame if any - bool yes = this->deferredIdx.has_value(); - if (this->deferredIdx.has_value()) { - VkSemaphore deferredSemaphore = this->copySemaphores2.at((this->frameIdx - 1) % 8).handle(); - const uint32_t deferredIdx = this->deferredIdx.value(); - const VkPresentInfoKHR presentInfo{ - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .waitSemaphoreCount = 1, - .pWaitSemaphores = &deferredSemaphore, - .swapchainCount = 1, - .pSwapchains = &this->swapchain, - .pImageIndices = &deferredIdx, - }; - auto res = vkQueuePresentKHR(queue, &presentInfo); - if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) - throw LSFG::vulkan_error(res, "Failed to present deferred swapchain image"); - } - this->deferredIdx.emplace(idx); - - // create semaphores - int copySem{}; - Mini::Semaphore& copySemaphore1 = this->copySemaphores1.at(this->frameIdx % 8); - copySemaphore1 = Mini::Semaphore(app.getDevice(), ©Sem); - Mini::Semaphore& copySemaphore2 = this->copySemaphores2.at(this->frameIdx % 8); - copySemaphore2 = Mini::Semaphore(app.getDevice()); - - // copy swapchain image to next frame image - auto& cmdBuf1 = this->cmdBufs1.at(this->frameIdx % 8); - cmdBuf1 = Mini::CommandBuffer(app.getDevice(), this->cmdPool); - cmdBuf1.begin(); - - auto& srcImage1 = this->images.at(idx); - auto& dstImage1 = (this->frameIdx % 2 == 0) ? this->frame_0 : this->frame_1; - - const VkImageMemoryBarrier srcBarrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .image = srcImage1, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .levelCount = 1, - .layerCount = 1 - } - }; - const VkImageMemoryBarrier dstBarrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .image = dstImage1.handle(), - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .levelCount = 1, - .layerCount = 1 - } - }; - const std::vector barriers = { srcBarrier, dstBarrier }; - vkCmdPipelineBarrier(cmdBuf1.handle(), - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, nullptr, 0, nullptr, 2, barriers.data()); - - const VkImageCopy imageCopy{ - .srcSubresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1 - }, - .dstSubresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1 - }, - .extent = { - .width = this->extent.width, - .height = this->extent.height, - .depth = 1 - } - }; - vkCmdCopyImage(cmdBuf1.handle(), - srcImage1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstImage1.handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &imageCopy); - - const VkImageMemoryBarrier presentBarrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, - .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - .image = dstImage1.handle(), - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .levelCount = 1, - .layerCount = 1 - } - }; - vkCmdPipelineBarrier(cmdBuf1.handle(), - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, 0, nullptr, 0, nullptr, 1, &presentBarrier); - - cmdBuf1.end(); - - std::vector semaphores2 = semaphores; - if (yes) { - VkSemaphore prevPresentSemaphore = this->prevPresentSemaphores.at((this->frameIdx - 1) % 8).at(FRAMEGEN_N - 1).handle(); - semaphores2.emplace_back(prevPresentSemaphore); - } - - cmdBuf1.submit(app.getGraphicsQueue(), - semaphores2, { copySemaphore1.handle(), copySemaphore2.handle() }); - - // render the intermediary frames - std::vector renderSems(FRAMEGEN_N); - auto& renderSemaphores = this->renderSemaphores.at(this->frameIdx % 8); - for (size_t i = 0; i < FRAMEGEN_N; i++) - renderSemaphores.at(i) = Mini::Semaphore(app.getDevice(), &renderSems.at(i)); - LSFG::presentContext(*this->lsfgId, copySem, renderSems); - - auto& acquireSemaphores = this->acquireSemaphores.at(this->frameIdx % 8); - auto& prevPresentSemaphores = this->prevPresentSemaphores.at(this->frameIdx % 8); - auto& presentSemaphores = this->presentSemaphores.at(this->frameIdx % 8); - auto& cmdBufs2 = this->cmdBufs2.at(this->frameIdx % 8); - for (size_t i = 0; i < FRAMEGEN_N; i++) { - // acquire the next swapchain image - auto& acquireSemaphore = acquireSemaphores.at(i); - acquireSemaphore = Mini::Semaphore(app.getDevice()); - - uint32_t newIdx{}; - auto res = vkAcquireNextImageKHR(app.getDevice(), this->swapchain, UINT64_MAX, - acquireSemaphore.handle(), VK_NULL_HANDLE, &newIdx); - if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) - throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image"); - - // copy the output image to the swapchain image - auto& presentSemaphore = presentSemaphores.at(i); - presentSemaphore = Mini::Semaphore(app.getDevice()); - - auto& cmdBuf2 = cmdBufs2.at(i); - cmdBuf2 = Mini::CommandBuffer(app.getDevice(), this->cmdPool); - cmdBuf2.begin(); - - auto& srcImage2 = this->outImgs.at(i); - auto& dstImage2 = this->images.at(newIdx); - - const VkImageMemoryBarrier srcBarrier2{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, - .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - .image = srcImage2.handle(), - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .levelCount = 1, - .layerCount = 1 - } - }; - const VkImageMemoryBarrier dstBarrier2{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .image = dstImage2, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .levelCount = 1, - .layerCount = 1 - } - }; - const std::vector barriers2 = { srcBarrier2, dstBarrier2 }; - vkCmdPipelineBarrier(cmdBuf2.handle(), - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, nullptr, 0, nullptr, 2, barriers2.data()); - - const VkImageCopy imageCopy2{ - .srcSubresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1 - }, - .dstSubresource = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .layerCount = 1 - }, - .extent = { - .width = this->extent.width, - .height = this->extent.height, - .depth = 1 - } - }; - vkCmdCopyImage(cmdBuf2.handle(), - srcImage2.handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - dstImage2, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &imageCopy2); - - const VkImageMemoryBarrier presentBarrier2{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, - .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - .image = dstImage2, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - - .levelCount = 1, - .layerCount = 1 - } - }; - vkCmdPipelineBarrier(cmdBuf2.handle(), - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, 0, nullptr, 0, nullptr, 1, &presentBarrier2); - - cmdBuf2.end(); - - - std::vector signalSemaphores; - signalSemaphores.emplace_back(presentSemaphore.handle()); - // signal next present semaphore - prevPresentSemaphores.at(i) = Mini::Semaphore(app.getDevice()); - signalSemaphores.emplace_back(prevPresentSemaphores.at(i).handle()); - auto& renderSemaphore = renderSemaphores.at(i); - cmdBuf2.submit(app.getGraphicsQueue(), - { acquireSemaphore.handle(), renderSemaphore.handle() }, - signalSemaphores); - - // present the swapchain image - std::vector waitSemaphores; - waitSemaphores.emplace_back(presentSemaphore.handle()); - if (i != 0) // wait for previous present semaphore - waitSemaphores.emplace_back(prevPresentSemaphores.at(i - 1).handle()); - const VkPresentInfoKHR presentInfo = { - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .pNext = i == 0 ? pNext : nullptr, // only set on first present - .waitSemaphoreCount = static_cast(waitSemaphores.size()), - .pWaitSemaphores = waitSemaphores.data(), - .swapchainCount = 1, - .pSwapchains = &this->swapchain, - .pImageIndices = &newIdx, - }; - res = vkQueuePresentKHR(queue, &presentInfo); - if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) // FIXME: somehow return VK_SUBOPTIMAL_KHR - throw LSFG::vulkan_error(res, "Failed to present swapchain"); - } - - this->frameIdx++; -} - -bool Application::removeSwapchain(VkSwapchainKHR handle) { - auto it = this->swapchains.find(handle); - if (it == this->swapchains.end()) - return false; // already retired... hopefully - this->swapchains.erase(it); - return true; -} - -Application::~Application() { - this->swapchains.clear(); - LSFG::finalize(); -} diff --git a/src/context.cpp b/src/context.cpp new file mode 100644 index 0000000..e5c0412 --- /dev/null +++ b/src/context.cpp @@ -0,0 +1,155 @@ +#include "context.hpp" +#include "utils.hpp" + +#include + +#include +#include + +LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, + VkExtent2D extent, const std::vector& swapchainImages) + : swapchain(swapchain), swapchainImages(swapchainImages), + extent(extent) { + // initialize lsfg + int frame_0_fd{}; + this->frame_0 = Mini::Image( + info.device, info.physicalDevice, + extent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, + &frame_0_fd); + + int frame_1_fd{}; + this->frame_1 = Mini::Image( + info.device, info.physicalDevice, + extent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, + &frame_1_fd); + + std::vector out_n_fds(info.frameGen); + for (size_t i = 0; i < info.frameGen; ++i) + this->out_n.emplace_back( + info.device, info.physicalDevice, + extent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, + &out_n_fds.at(i)); + + this->lsfgCtxId = std::shared_ptr( + new int32_t(LSFG::createContext(extent.width, extent.height, + frame_0_fd, frame_1_fd, out_n_fds)), + [](const int32_t* id) { + LSFG::deleteContext(*id); + } + ); + + // prepare render passes + this->cmdPool = Mini::CommandPool(info.device, info.queue.first); + for (size_t i = 0; i < 8; ++i) { + auto& pass = this->passInfos.at(i); + pass.renderSemaphores.resize(info.frameGen); + pass.acquireSemaphores.resize(info.frameGen); + pass.postCopyBufs.resize(info.frameGen); + pass.postCopySemaphores.resize(info.frameGen); + pass.prevPostCopySemaphores.resize(info.frameGen); + } +} + +VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue, + const std::vector& gameRenderSemaphores, uint32_t presentIdx) { + auto& pass = this->passInfos.at(this->frameIdx % 8); + + // 1. copy swapchain image to frame_0/frame_1 + int preCopySemaphoreFd{}; + pass.preCopySemaphores.at(0) = Mini::Semaphore(info.device, &preCopySemaphoreFd); + // pass.preCopySemaphores.at(1) = Mini::Semaphore(info.device); + pass.preCopyBuf = Mini::CommandBuffer(info.device, this->cmdPool); + pass.preCopyBuf.begin(); + + Utils::copyImage(pass.preCopyBuf.handle(), + this->swapchainImages.at(presentIdx), + this->frameIdx % 2 == 0 ? this->frame_0.handle() : this->frame_1.handle(), + this->extent.width, this->extent.height, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + true, false); + + pass.preCopyBuf.end(); + pass.preCopyBuf.submit(info.queue.second, + gameRenderSemaphores, + { pass.preCopySemaphores.at(0).handle() }); + + // 2. render intermediary frames + std::vector renderSemaphoreFds(info.frameGen); + for (size_t i = 0; i < info.frameGen; ++i) + pass.renderSemaphores.at(i) = Mini::Semaphore(info.device, &renderSemaphoreFds.at(i)); + + LSFG::presentContext(*this->lsfgCtxId, + preCopySemaphoreFd, + renderSemaphoreFds); + + for (size_t i = 0; i < info.frameGen; i++) { + // 3. acquire next swapchain image + pass.acquireSemaphores.at(i) = Mini::Semaphore(info.device); + uint32_t imageIdx{}; + auto res = vkAcquireNextImageKHR(info.device, this->swapchain, UINT64_MAX, + pass.acquireSemaphores.at(i).handle(), VK_NULL_HANDLE, &imageIdx); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image"); + + // 4. copy output image to swapchain image + pass.postCopySemaphores.at(i) = Mini::Semaphore(info.device); + pass.prevPostCopySemaphores.at(i) = Mini::Semaphore(info.device); + pass.postCopyBufs.at(i) = Mini::CommandBuffer(info.device, this->cmdPool); + pass.postCopyBufs.at(i).begin(); + + Utils::copyImage(pass.postCopyBufs.at(i).handle(), + this->out_n.at(i).handle(), + this->swapchainImages.at(imageIdx), + this->extent.width, this->extent.height, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + false, true); + + pass.postCopyBufs.at(i).end(); + pass.postCopyBufs.at(i).submit(info.queue.second, + { pass.acquireSemaphores.at(i).handle(), + pass.renderSemaphores.at(i).handle() }, + { pass.postCopySemaphores.at(i).handle(), + pass.prevPostCopySemaphores.at(i).handle() }); + + // 5. present swapchain image + std::vector waitSemaphores{ pass.postCopySemaphores.at(i).handle() }; + if (i != 0) waitSemaphores.emplace_back(pass.prevPostCopySemaphores.at(i - 1).handle()); + + const VkPresentInfoKHR presentInfo{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = i == 0 ? pNext : nullptr, // only set on first present + .waitSemaphoreCount = static_cast(waitSemaphores.size()), + .pWaitSemaphores = waitSemaphores.data(), + .swapchainCount = 1, + .pSwapchains = &this->swapchain, + .pImageIndices = &imageIdx, + }; + res = vkQueuePresentKHR(queue, &presentInfo); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + throw LSFG::vulkan_error(res, "Failed to present swapchain image"); + } + + // 6. present actual next frame + VkSemaphore lastPrevPostCopySemaphore = + pass.prevPostCopySemaphores.at(info.frameGen - 1).handle(); + const VkPresentInfoKHR presentInfo{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &lastPrevPostCopySemaphore, + .swapchainCount = 1, + .pSwapchains = &this->swapchain, + .pImageIndices = &presentIdx, + }; + auto res = vkQueuePresentKHR(queue, &presentInfo); + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) + throw LSFG::vulkan_error(res, "Failed to present swapchain image"); + + this->frameIdx++; + return res; +} diff --git a/src/hooks.cpp b/src/hooks.cpp index bd01b5d..3154b2f 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -1,39 +1,35 @@ -#include "hooks.hpp" #include "loader/dl.hpp" #include "loader/vk.hpp" -#include "application.hpp" +#include "context.hpp" +#include "hooks.hpp" #include "log.hpp" +#include "utils.hpp" -#include #include -#include -#include +#include +#include using namespace Hooks; namespace { - bool initialized{false}; - std::optional application; + + // instance hooks VkResult myvkCreateInstance( const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { - // add extensions - std::vector extensions(pCreateInfo->enabledExtensionCount); - std::copy_n(pCreateInfo->ppEnabledExtensionNames, extensions.size(), extensions.data()); + // create lsfg + LSFG::initialize(); - const std::vector requiredExtensions = { - VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, - VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, - VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME - }; - for (const auto& ext : requiredExtensions) { - auto it = std::ranges::find(extensions, ext); - if (it == extensions.end()) - extensions.push_back(ext); - } + // add extensions + auto extensions = Utils::addExtensions(pCreateInfo->ppEnabledExtensionNames, + pCreateInfo->enabledExtensionCount, { + "VK_KHR_get_physical_device_properties2", + "VK_KHR_external_memory_capabilities", + "VK_KHR_external_semaphore_capabilities" + }); VkInstanceCreateInfo createInfo = *pCreateInfo; createInfo.enabledExtensionCount = static_cast(extensions.size()); @@ -41,108 +37,87 @@ namespace { return vkCreateInstance(&createInfo, pAllocator, pInstance); } + void myvkDestroyInstance( + VkInstance instance, + const VkAllocationCallbacks* pAllocator) { + LSFG::finalize(); // destroy lsfg + vkDestroyInstance(instance, pAllocator); + } + + // device hooks + + std::unordered_map devices; + VkResult myvkCreateDevice( VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { // add extensions - std::vector extensions(pCreateInfo->enabledExtensionCount); - std::copy_n(pCreateInfo->ppEnabledExtensionNames, extensions.size(), extensions.data()); - - const std::vector requiredExtensions = { - VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, - VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, - VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, - VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME - }; - for (const auto& ext : requiredExtensions) { - auto it = std::ranges::find(extensions, ext); - if (it == extensions.end()) - extensions.push_back(ext); - } + auto extensions = Utils::addExtensions(pCreateInfo->ppEnabledExtensionNames, + pCreateInfo->enabledExtensionCount, { + "VK_KHR_external_memory", + "VK_KHR_external_memory_fd", + "VK_KHR_external_semaphore", + "VK_KHR_external_semaphore_fd" + }); VkDeviceCreateInfo createInfo = *pCreateInfo; createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); auto res = vkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice); - // extract graphics and present queues - std::vector queueCreateInfos(pCreateInfo->queueCreateInfoCount); - std::copy_n(pCreateInfo->pQueueCreateInfos, queueCreateInfos.size(), queueCreateInfos.data()); - - uint32_t familyCount{}; - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr); - std::vector families(familyCount); - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, families.data()); - - std::optional graphicsFamilyIdx; - for (uint32_t i = 0; i < families.size(); ++i) { - auto it = std::ranges::find_if(queueCreateInfos, - [i](const VkDeviceQueueCreateInfo& info) { - return info.queueFamilyIndex == i; - }) ; - if (it == queueCreateInfos.end()) - continue; // skip if this family is not used by the device - if (families.at(i).queueFlags & VK_QUEUE_GRAPHICS_BIT) - graphicsFamilyIdx.emplace(i); - } - if (!graphicsFamilyIdx.has_value()) { - Log::error("No suitable queue family found for graphics or present"); - exit(EXIT_FAILURE); - } - - VkQueue graphicsQueue{}; - vkGetDeviceQueue(*pDevice, *graphicsFamilyIdx, 0, &graphicsQueue); - - // create the main application - if (application.has_value()) { - Log::error("Application already initialized, are you trying to create a second device?"); - exit(EXIT_FAILURE); - } - + // store device info try { - application.emplace(*pDevice, physicalDevice, graphicsQueue, *graphicsFamilyIdx); - Log::info("lsfg-vk(hooks): Application created successfully"); - } catch (const LSFG::vulkan_error& e) { - Log::error("Encountered Vulkan error {:x} while creating application: {}", - static_cast(e.error()), e.what()); - exit(EXIT_FAILURE); + const char* frameGen = std::getenv("LSFG_MULTIPLIER"); + if (!frameGen) frameGen = "1"; + devices.emplace(*pDevice, DeviceInfo { + .device = *pDevice, + .physicalDevice = physicalDevice, + .queue = Utils::findQueue(*pDevice, physicalDevice, &createInfo, + VK_QUEUE_GRAPHICS_BIT), + .frameGen = std::stoul(frameGen) + }); } catch (const std::exception& e) { - Log::error("Encountered error while creating application: {}", e.what()); - exit(EXIT_FAILURE); + Log::error("Failed to create device info: {}", e.what()); + return VK_ERROR_INITIALIZATION_FAILED; } - return res; } + void myvkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { + devices.erase(device); // erase device info + vkDestroyDevice(device, pAllocator); + } + + // swapchain hooks + + std::unordered_map swapchains; + std::unordered_map swapchainToDeviceTable; + VkResult myvkCreateSwapchainKHR( VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain) { - VkSwapchainCreateInfoKHR createInfo = *pCreateInfo; - createInfo.minImageCount += 3; - createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; - createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; - auto res = vkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); + auto& deviceInfo = devices.at(device); - // add the swapchain to the application - if (!application.has_value()) { - Log::error("Application not initialized, cannot create swapchain"); - exit(EXIT_FAILURE); + // update swapchain create info + VkSwapchainCreateInfoKHR createInfo = *pCreateInfo; + createInfo.minImageCount += 1 + deviceInfo.frameGen; // 1 deferred + N framegen, FIXME: check hardware max + createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; // allow copy from/to images + createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; // force vsync + auto res = vkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); + if (res != VK_SUCCESS) { + Log::error("Failed to create swapchain: {:x}", static_cast(res)); + return res; } try { - if (pCreateInfo->oldSwapchain) { - if (!application->removeSwapchain(pCreateInfo->oldSwapchain)) - throw std::runtime_error("Failed to remove old swapchain"); - Log::info("lsfg-vk(hooks): Swapchain retired successfully"); - } - + // get swapchain images uint32_t imageCount{}; - auto res = vkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr); + res = vkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr); if (res != VK_SUCCESS || imageCount == 0) throw LSFG::vulkan_error(res, "Failed to get swapchain images count"); @@ -151,17 +126,20 @@ namespace { if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Failed to get swapchain images"); - application->addSwapchain(*pSwapchain, - pCreateInfo->imageFormat, pCreateInfo->imageExtent, swapchainImages); - Log::info("lsfg-vk(hooks): Swapchain created successfully with {} images", - swapchainImages.size()); + // create swapchain context + swapchains.emplace(*pSwapchain, LsContext( + deviceInfo, *pSwapchain, pCreateInfo->imageExtent, + swapchainImages + )); + + swapchainToDeviceTable.emplace(*pSwapchain, device); } catch (const LSFG::vulkan_error& e) { Log::error("Encountered Vulkan error {:x} while creating swapchain: {}", static_cast(e.error()), e.what()); - exit(EXIT_FAILURE); + return e.error(); } catch (const std::exception& e) { Log::error("Encountered error while creating swapchain: {}", e.what()); - exit(EXIT_FAILURE); + return VK_ERROR_INITIALIZATION_FAILED; } return res; @@ -170,67 +148,36 @@ namespace { VkResult myvkQueuePresentKHR( VkQueue queue, const VkPresentInfoKHR* pPresentInfo) { - if (!application.has_value()) { - Log::error("Application not initialized, cannot present frame"); - return VK_ERROR_INITIALIZATION_FAILED; - } + auto& deviceInfo = devices.at(swapchainToDeviceTable.at(*pPresentInfo->pSwapchains)); + auto& swapchain = swapchains.at(*pPresentInfo->pSwapchains); - // present the next frame try { std::vector waitSemaphores(pPresentInfo->waitSemaphoreCount); std::copy_n(pPresentInfo->pWaitSemaphores, waitSemaphores.size(), waitSemaphores.data()); - application->presentSwapchain(*pPresentInfo->pSwapchains, - queue, waitSemaphores, *pPresentInfo->pImageIndices, pPresentInfo->pNext); - - Log::info("lsfg-vk(hooks): Frame presented successfully"); + // present the next frame + return swapchain.present(deviceInfo, pPresentInfo->pNext, + queue, waitSemaphores, *pPresentInfo->pImageIndices); } catch (const LSFG::vulkan_error& e) { Log::error("Encountered Vulkan error {:x} while presenting: {}", static_cast(e.error()), e.what()); - return e.error(); // do not exit + return e.error(); } catch (const std::exception& e) { Log::error("Encountered error while creating presenting: {}", e.what()); - exit(EXIT_FAILURE); + return VK_ERROR_INITIALIZATION_FAILED; } - - return VK_SUCCESS; } void myvkDestroySwapchainKHR( VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* pAllocator) { - if (!application.has_value()) { - Log::error("Application not initialized, cannot destroy swapchain"); - exit(EXIT_FAILURE); - } - - // remove the swapchain from the application - try { - if (application->removeSwapchain(swapchain)) - Log::info("lsfg-vk(hooks): Swapchain retired successfully"); - } catch (const std::exception& e) { - Log::error("Encountered error while removing swapchain: {}", e.what()); - exit(EXIT_FAILURE); - } - + swapchains.erase(swapchain); // erase swapchain context + swapchainToDeviceTable.erase(swapchain); vkDestroySwapchainKHR(device, swapchain, pAllocator); } - void myvkDestroyDevice( - VkDevice device, - const VkAllocationCallbacks* pAllocator) { - // destroy the main application - if (application.has_value()) { - application.reset(); - Log::info("lsfg-vk(hooks): Application destroyed successfully"); - } else { - Log::warn("lsfg-vk(hooks): No application to destroy, continuing"); - } - - vkDestroyDevice(device, pAllocator); - } - + bool initialized{false}; } void Hooks::initialize() { @@ -239,49 +186,29 @@ void Hooks::initialize() { return; } - // register hooks to vulkan loader - Loader::VK::registerSymbol("vkCreateInstance", - reinterpret_cast(myvkCreateInstance)); - Loader::VK::registerSymbol("vkCreateDevice", - reinterpret_cast(myvkCreateDevice)); - Loader::VK::registerSymbol("vkDestroyDevice", - reinterpret_cast(myvkDestroyDevice)); - Loader::VK::registerSymbol("vkCreateSwapchainKHR", - reinterpret_cast(myvkCreateSwapchainKHR)); - Loader::VK::registerSymbol("vkDestroySwapchainKHR", - reinterpret_cast(myvkDestroySwapchainKHR)); - Loader::VK::registerSymbol("vkQueuePresentKHR", - reinterpret_cast(myvkQueuePresentKHR)); + // list of hooks to register + const std::vector> hooks = { + { "vkCreateInstance", reinterpret_cast(myvkCreateInstance) }, + { "vkDestroyInstance", reinterpret_cast(myvkDestroyInstance) }, + { "vkCreateDevice", reinterpret_cast(myvkCreateDevice) }, + { "vkDestroyDevice", reinterpret_cast(myvkDestroyDevice) }, + { "vkCreateSwapchainKHR", reinterpret_cast(myvkCreateSwapchainKHR) }, + { "vkQueuePresentKHR", reinterpret_cast(myvkQueuePresentKHR) }, + { "vkDestroySwapchainKHR", reinterpret_cast(myvkDestroySwapchainKHR) } + }; - // register hooks to dynamic loader under libvulkan.so.1 - Loader::DL::File vk1("libvulkan.so.1"); - vk1.defineSymbol("vkCreateInstance", - reinterpret_cast(myvkCreateInstance)); - vk1.defineSymbol("vkCreateDevice", - reinterpret_cast(myvkCreateDevice)); - vk1.defineSymbol("vkDestroyDevice", - reinterpret_cast(myvkDestroyDevice)); - vk1.defineSymbol("vkCreateSwapchainKHR", - reinterpret_cast(myvkCreateSwapchainKHR)); - vk1.defineSymbol("vkDestroySwapchainKHR", - reinterpret_cast(myvkDestroySwapchainKHR)); - vk1.defineSymbol("vkQueuePresentKHR", - reinterpret_cast(myvkQueuePresentKHR)); - Loader::DL::registerFile(vk1); + // register hooks to Vulkan loader + for (const auto& hook : hooks) + Loader::VK::registerSymbol(hook.first, hook.second); - // register hooks to dynamic loader under libvulkan.so - Loader::DL::File vk2("libvulkan.so"); - vk2.defineSymbol("vkCreateInstance", - reinterpret_cast(myvkCreateInstance)); - vk2.defineSymbol("vkCreateDevice", - reinterpret_cast(myvkCreateDevice)); - vk2.defineSymbol("vkDestroyDevice", - reinterpret_cast(myvkDestroyDevice)); - vk2.defineSymbol("vkCreateSwapchainKHR", - reinterpret_cast(myvkCreateSwapchainKHR)); - vk2.defineSymbol("vkDestroySwapchainKHR", - reinterpret_cast(myvkDestroySwapchainKHR)); - vk2.defineSymbol("vkQueuePresentKHR", - reinterpret_cast(myvkQueuePresentKHR)); - Loader::DL::registerFile(vk2); + // register hooks to dynamic loader under libvulkan.so.1 and libvulkan.so + for (const char* libName : {"libvulkan.so.1", "libvulkan.so"}) { + Loader::DL::File vkLib(libName); + for (const auto& hook : hooks) + vkLib.defineSymbol(hook.first, hook.second); + Loader::DL::registerFile(vkLib); + } + + initialized = true; + Log::info("Vulkan hooks initialized successfully"); } diff --git a/src/mini/commandbuffer.cpp b/src/mini/commandbuffer.cpp index 2014ca5..8a8ac32 100644 --- a/src/mini/commandbuffer.cpp +++ b/src/mini/commandbuffer.cpp @@ -1,5 +1,6 @@ #include "mini/commandbuffer.hpp" -#include "lsfg.hpp" + +#include using namespace Mini; diff --git a/src/mini/commandpool.cpp b/src/mini/commandpool.cpp index d0a6b8e..3407410 100644 --- a/src/mini/commandpool.cpp +++ b/src/mini/commandpool.cpp @@ -1,5 +1,6 @@ #include "mini/commandpool.hpp" -#include "lsfg.hpp" + +#include using namespace Mini; diff --git a/src/mini/image.cpp b/src/mini/image.cpp index 2ec53a7..4fc8dcc 100644 --- a/src/mini/image.cpp +++ b/src/mini/image.cpp @@ -1,5 +1,6 @@ #include "mini/image.hpp" -#include "lsfg.hpp" + +#include #include @@ -81,30 +82,6 @@ Image::Image(VkDevice device, VkPhysicalDevice physicalDevice, 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, &viewDesc, nullptr, &viewHandle); - if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE) - throw LSFG::vulkan_error(res, "Failed to create image view"); - // obtain the sharing fd auto vkGetMemoryFdKHR = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetMemoryFdKHR")); @@ -119,7 +96,6 @@ Image::Image(VkDevice device, VkPhysicalDevice physicalDevice, throw LSFG::vulkan_error(res, "Failed to obtain sharing fd for Vulkan image"); // store objects in shared ptr - this->layout = std::make_shared(VK_IMAGE_LAYOUT_UNDEFINED); this->image = std::shared_ptr( new VkImage(imageHandle), [dev = device](VkImage* img) { @@ -132,10 +108,4 @@ Image::Image(VkDevice device, VkPhysicalDevice physicalDevice, vkFreeMemory(dev, *mem, nullptr); } ); - this->view = std::shared_ptr( - new VkImageView(viewHandle), - [dev = device](VkImageView* imgView) { - vkDestroyImageView(dev, *imgView, nullptr); - } - ); } diff --git a/src/mini/semaphore.cpp b/src/mini/semaphore.cpp index 6d803bc..f4b2225 100644 --- a/src/mini/semaphore.cpp +++ b/src/mini/semaphore.cpp @@ -1,5 +1,6 @@ #include "mini/semaphore.hpp" -#include "lsfg.hpp" + +#include using namespace Mini; diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..0bb0a0a --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,142 @@ +#include "utils.hpp" + +#include + +#include +#include + +using namespace Utils; + +std::pair Utils::findQueue(VkDevice device, VkPhysicalDevice physicalDevice, + VkDeviceCreateInfo* desc, VkQueueFlags flags) { + std::vector enabledQueues(desc->queueCreateInfoCount); + std::copy_n(desc->pQueueCreateInfos, enabledQueues.size(), enabledQueues.data()); + + uint32_t familyCount{}; + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr); + std::vector families(familyCount); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, families.data()); + + std::optional idx; + for (const auto& queueInfo : enabledQueues) { + if ((queueInfo.queueFamilyIndex < families.size()) && + (families[queueInfo.queueFamilyIndex].queueFlags & flags)) { + idx = queueInfo.queueFamilyIndex; + break; + } + } + if (!idx.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No suitable queue found"); + + VkQueue queue{}; + vkGetDeviceQueue(device, *idx, 0, &queue); + + return { *idx, queue }; +} + +std::vector Utils::addExtensions(const char* const* extensions, size_t count, + const std::vector& requiredExtensions) { + std::vector ext(count); + std::copy_n(extensions, count, ext.data()); + + for (const auto& e : requiredExtensions) { + auto it = std::ranges::find(ext, e); + if (it == ext.end()) + ext.push_back(e); + } + + return ext; +} + +void Utils::copyImage(VkCommandBuffer buf, + VkImage src, VkImage dst, + uint32_t width, uint32_t height, + VkPipelineStageFlags pre, VkPipelineStageFlags post, + bool makeSrcPresentable, bool makeDstPresentable) { + const VkImageMemoryBarrier srcBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .image = src, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + const VkImageMemoryBarrier dstBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .image = dst, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + const std::vector barriers = { srcBarrier, dstBarrier }; + vkCmdPipelineBarrier(buf, + pre, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 0, nullptr, 0, nullptr, + static_cast(barriers.size()), barriers.data()); + + const VkImageCopy imageCopy{ + .srcSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 + }, + .dstSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1 + }, + .extent = { + .width = width, + .height = height, + .depth = 1 + } + }; + vkCmdCopyImage(buf, + src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, &imageCopy); + + if (makeSrcPresentable) { + const VkImageMemoryBarrier presentBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .image = src, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + vkCmdPipelineBarrier(buf, + VK_PIPELINE_STAGE_TRANSFER_BIT, post, 0, + 0, nullptr, 0, nullptr, + 1, &presentBarrier); + } + + if (makeDstPresentable) { + const VkImageMemoryBarrier presentBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .image = dst, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1 + } + }; + vkCmdPipelineBarrier(buf, + VK_PIPELINE_STAGE_TRANSFER_BIT, post, 0, + 0, nullptr, 0, nullptr, + 1, &presentBarrier); + } + +} From 3689390c56a5085b30eb390e95932f0b0d9c412c Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 3 Jul 2025 09:19:22 +0200 Subject: [PATCH 064/253] fix semaphore deletion --- lsfg-vk-gen/include/context.hpp | 2 ++ lsfg-vk-gen/src/context.cpp | 18 +++++++++++++++--- lsfg-vk-gen/src/lsfg.cpp | 6 ++++-- src/context.cpp | 12 +++++++++--- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/lsfg-vk-gen/include/context.hpp b/lsfg-vk-gen/include/context.hpp index 41fa127..e29d07f 100644 --- a/lsfg-vk-gen/include/context.hpp +++ b/lsfg-vk-gen/include/context.hpp @@ -4,6 +4,7 @@ #include "core/commandbuffer.hpp" #include "core/commandpool.hpp" #include "core/descriptorpool.hpp" +#include "core/fence.hpp" #include "core/image.hpp" #include "core/semaphore.hpp" #include "shaderchains/alpha.hpp" @@ -67,6 +68,7 @@ namespace LSFG { std::vector> outSemaphores; std::array cmdBuffers1; std::vector> cmdBuffers2; + std::array>, 8> doneFences; uint64_t fc{0}; Shaderchains::Downsample downsampleChain; diff --git a/lsfg-vk-gen/src/context.cpp b/lsfg-vk-gen/src/context.cpp index 6e26566..db11e72 100644 --- a/lsfg-vk-gen/src/context.cpp +++ b/lsfg-vk-gen/src/context.cpp @@ -1,4 +1,5 @@ #include "context.hpp" +#include "core/fence.hpp" #include "core/semaphore.hpp" #include "lsfg.hpp" @@ -28,8 +29,10 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in this->cmdPool = Core::CommandPool(device); // prepare vectors - for (size_t i = 0; i < 8; i++) + for (size_t i = 0; i < 8; i++) { this->internalSemaphores.at(i).resize(outN.size()); + this->doneFences.at(i).resize(outN.size()); + } for (size_t i = 0; i < outN.size(); i++) { this->outSemaphores.emplace_back(); this->cmdBuffers2.emplace_back(); @@ -114,6 +117,14 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in void Context::present(const Core::Device& device, int inSem, const std::vector& outSem) { + auto& doneFences = this->doneFences.at(this->fc % 8); + for (auto& fenceOptional : doneFences) { + if (!fenceOptional.has_value()) + continue; + if (!fenceOptional->wait(device, UINT64_MAX)) + throw vulkan_error(VK_ERROR_DEVICE_LOST, "Fence wait timed out"); + } + auto& inSemaphore = this->inSemaphores.at(this->fc % 8); inSemaphore = Core::Semaphore(device, inSem); auto& internalSemaphores = this->internalSemaphores.at(this->fc % 8); @@ -134,10 +145,11 @@ void Context::present(const Core::Device& device, int inSem, { inSemaphore }, std::nullopt, internalSemaphores, std::nullopt); - for (size_t pass = 0; pass < outSem.size(); pass++) { auto& outSemaphore = this->outSemaphores.at(pass).at(this->fc % 8); outSemaphore = Core::Semaphore(device, outSem.at(pass)); + auto& outFenceOptional = this->doneFences.at(fc % 8).at(pass); + outFenceOptional.emplace(Core::Fence(device)); auto& cmdBuffer2 = this->cmdBuffers2.at(pass).at(this->fc % 8); cmdBuffer2 = Core::CommandBuffer(device, this->cmdPool); @@ -158,7 +170,7 @@ void Context::present(const Core::Device& device, int inSem, cmdBuffer2.end(); - cmdBuffer2.submit(device.getComputeQueue(), std::nullopt, + cmdBuffer2.submit(device.getComputeQueue(), outFenceOptional, { internalSemaphores.at(pass) }, std::nullopt, { outSemaphore }, std::nullopt); } diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp index 7519d5a..816c4ff 100644 --- a/lsfg-vk-gen/src/lsfg.cpp +++ b/lsfg-vk-gen/src/lsfg.cpp @@ -58,15 +58,17 @@ void LSFG::deleteContext(int32_t id) { if (it == contexts.end()) throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context"); + vkDeviceWaitIdle(device->handle()); contexts.erase(it); } void LSFG::finalize() { - if (!instance.has_value() && !device.has_value()) + if (!instance.has_value() || !device.has_value()) return; Globals::uninitializeGlobals(); - instance.reset(); + vkDeviceWaitIdle(device->handle()); device.reset(); + instance.reset(); } diff --git a/src/context.cpp b/src/context.cpp index e5c0412..56ca13b 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -63,7 +63,7 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk // 1. copy swapchain image to frame_0/frame_1 int preCopySemaphoreFd{}; pass.preCopySemaphores.at(0) = Mini::Semaphore(info.device, &preCopySemaphoreFd); - // pass.preCopySemaphores.at(1) = Mini::Semaphore(info.device); + pass.preCopySemaphores.at(1) = Mini::Semaphore(info.device); pass.preCopyBuf = Mini::CommandBuffer(info.device, this->cmdPool); pass.preCopyBuf.begin(); @@ -75,9 +75,15 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk true, false); pass.preCopyBuf.end(); + + std::vector gameRenderSemaphores2 = gameRenderSemaphores; + if (this->frameIdx > 0) + gameRenderSemaphores2.emplace_back(this->passInfos.at((this->frameIdx - 1) % 8) + .preCopySemaphores.at(1).handle()); pass.preCopyBuf.submit(info.queue.second, - gameRenderSemaphores, - { pass.preCopySemaphores.at(0).handle() }); + gameRenderSemaphores2, + { pass.preCopySemaphores.at(0).handle(), + pass.preCopySemaphores.at(1).handle() }); // 2. render intermediary frames std::vector renderSemaphoreFds(info.frameGen); From 448229255b90e3ef65a4a543b262e583cc003256 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 3 Jul 2025 10:14:55 +0200 Subject: [PATCH 065/253] cleanup fg context logic --- lsfg-vk-gen/include/context.hpp | 18 +++++--- lsfg-vk-gen/src/context.cpp | 77 +++++++++++++++------------------ src/context.cpp | 2 +- 3 files changed, 47 insertions(+), 50 deletions(-) diff --git a/lsfg-vk-gen/include/context.hpp b/lsfg-vk-gen/include/context.hpp index e29d07f..e511d98 100644 --- a/lsfg-vk-gen/include/context.hpp +++ b/lsfg-vk-gen/include/context.hpp @@ -63,13 +63,17 @@ namespace LSFG { Core::CommandPool cmdPool; Core::Image inImg_0, inImg_1; // inImg_0 is next (inImg_1 prev) when fc % 2 == 0 - std::array inSemaphores; - std::array, 8> internalSemaphores; - std::vector> outSemaphores; - std::array cmdBuffers1; - std::vector> cmdBuffers2; - std::array>, 8> doneFences; - uint64_t fc{0}; + uint64_t frameIdx{0}; + + struct RenderInfo { + Core::Semaphore inSemaphore; // wait for copy + Core::CommandBuffer cmdBuffer1; + std::vector internalSemaphores; // first step output + std::vector cmdBuffers2; // second step output + std::vector outSemaphores; // signal when done with each pass + std::optional> completionFences; + }; // data for a single render + std::array renderInfos; // 8 passes, why not Shaderchains::Downsample downsampleChain; std::array alphaChains; diff --git a/lsfg-vk-gen/src/context.cpp b/lsfg-vk-gen/src/context.cpp index db11e72..0c9ff67 100644 --- a/lsfg-vk-gen/src/context.cpp +++ b/lsfg-vk-gen/src/context.cpp @@ -3,10 +3,10 @@ #include "core/semaphore.hpp" #include "lsfg.hpp" -#include +#include + #include #include -#include using namespace LSFG; @@ -24,18 +24,14 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in VK_IMAGE_ASPECT_COLOR_BIT, in1); - // create pools + // prepare render infos this->descPool = Core::DescriptorPool(device); this->cmdPool = Core::CommandPool(device); - - // prepare vectors for (size_t i = 0; i < 8; i++) { - this->internalSemaphores.at(i).resize(outN.size()); - this->doneFences.at(i).resize(outN.size()); - } - for (size_t i = 0; i < outN.size(); i++) { - this->outSemaphores.emplace_back(); - this->cmdBuffers2.emplace_back(); + auto& info = this->renderInfos.at(i); + info.internalSemaphores.resize(outN.size()); + info.cmdBuffers2.resize(outN.size()); + info.outSemaphores.resize(outN.size()); } // create shader chains @@ -117,65 +113,62 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in void Context::present(const Core::Device& device, int inSem, const std::vector& outSem) { - auto& doneFences = this->doneFences.at(this->fc % 8); - for (auto& fenceOptional : doneFences) { - if (!fenceOptional.has_value()) - continue; - if (!fenceOptional->wait(device, UINT64_MAX)) + auto& info = this->renderInfos.at(this->frameIdx % 8); + + // 3. wait for completion of previous frame in this slot + for (auto& fence : info.completionFences.value_or({})) + if (!fence.wait(device, UINT64_MAX)) // should not take any time throw vulkan_error(VK_ERROR_DEVICE_LOST, "Fence wait timed out"); - } - auto& inSemaphore = this->inSemaphores.at(this->fc % 8); - inSemaphore = Core::Semaphore(device, inSem); - auto& internalSemaphores = this->internalSemaphores.at(this->fc % 8); + // 1. downsample and process input image + info.inSemaphore = Core::Semaphore(device, inSem); for (size_t i = 0; i < outSem.size(); i++) - internalSemaphores.at(i) = Core::Semaphore(device); + info.internalSemaphores.at(i) = Core::Semaphore(device); - auto& cmdBuffer1 = this->cmdBuffers1.at(this->fc % 8); - cmdBuffer1 = Core::CommandBuffer(device, this->cmdPool); - cmdBuffer1.begin(); + info.cmdBuffer1 = Core::CommandBuffer(device, this->cmdPool); + info.cmdBuffer1.begin(); - this->downsampleChain.Dispatch(cmdBuffer1, fc); + this->downsampleChain.Dispatch(info.cmdBuffer1, this->frameIdx); for (size_t i = 0; i < 7; i++) - this->alphaChains.at(6 - i).Dispatch(cmdBuffer1, fc); + this->alphaChains.at(6 - i).Dispatch(info.cmdBuffer1, this->frameIdx); - cmdBuffer1.end(); - - cmdBuffer1.submit(device.getComputeQueue(), std::nullopt, - { inSemaphore }, std::nullopt, - internalSemaphores, std::nullopt); + info.cmdBuffer1.end(); + info.cmdBuffer1.submit(device.getComputeQueue(), std::nullopt, + { info.inSemaphore }, std::nullopt, + info.internalSemaphores, std::nullopt); + // 2. generate intermediary frames + info.completionFences.emplace(); for (size_t pass = 0; pass < outSem.size(); pass++) { - auto& outSemaphore = this->outSemaphores.at(pass).at(this->fc % 8); + auto& completionFence = info.completionFences->emplace_back(device); + auto& outSemaphore = info.outSemaphores.at(pass); outSemaphore = Core::Semaphore(device, outSem.at(pass)); - auto& outFenceOptional = this->doneFences.at(fc % 8).at(pass); - outFenceOptional.emplace(Core::Fence(device)); - auto& cmdBuffer2 = this->cmdBuffers2.at(pass).at(this->fc % 8); + auto& cmdBuffer2 = info.cmdBuffers2.at(pass); cmdBuffer2 = Core::CommandBuffer(device, this->cmdPool); cmdBuffer2.begin(); - this->betaChain.Dispatch(cmdBuffer2, fc, pass); + this->betaChain.Dispatch(cmdBuffer2, this->frameIdx, pass); for (size_t i = 0; i < 4; i++) - this->gammaChains.at(i).Dispatch(cmdBuffer2, fc, pass); + this->gammaChains.at(i).Dispatch(cmdBuffer2, this->frameIdx, pass); for (size_t i = 0; i < 3; i++) { - this->magicChains.at(i).Dispatch(cmdBuffer2, fc, pass); + this->magicChains.at(i).Dispatch(cmdBuffer2, this->frameIdx, pass); this->deltaChains.at(i).Dispatch(cmdBuffer2, pass); this->epsilonChains.at(i).Dispatch(cmdBuffer2, pass); this->zetaChains.at(i).Dispatch(cmdBuffer2, pass); if (i < 2) this->extractChains.at(i).Dispatch(cmdBuffer2, pass); } - this->mergeChain.Dispatch(cmdBuffer2, fc, pass); + this->mergeChain.Dispatch(cmdBuffer2, this->frameIdx, pass); cmdBuffer2.end(); - cmdBuffer2.submit(device.getComputeQueue(), outFenceOptional, - { internalSemaphores.at(pass) }, std::nullopt, + cmdBuffer2.submit(device.getComputeQueue(), completionFence, + { info.internalSemaphores.at(pass) }, std::nullopt, { outSemaphore }, std::nullopt); } - fc++; + this->frameIdx++; } vulkan_error::vulkan_error(VkResult result, const std::string& message) diff --git a/src/context.cpp b/src/context.cpp index 56ca13b..d4b2c39 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -46,7 +46,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, // prepare render passes this->cmdPool = Mini::CommandPool(info.device, info.queue.first); - for (size_t i = 0; i < 8; ++i) { + for (size_t i = 0; i < 8; i++) { auto& pass = this->passInfos.at(i); pass.renderSemaphores.resize(info.frameGen); pass.acquireSemaphores.resize(info.frameGen); From 11778e507fd045c65ab77d5a7fe375a1340ba35b Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 3 Jul 2025 10:21:42 +0200 Subject: [PATCH 066/253] remove forced layers --- lsfg-vk-gen/src/core/instance.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lsfg-vk-gen/src/core/instance.cpp b/lsfg-vk-gen/src/core/instance.cpp index 83a7a24..c0e670f 100644 --- a/lsfg-vk-gen/src/core/instance.cpp +++ b/lsfg-vk-gen/src/core/instance.cpp @@ -9,10 +9,6 @@ const std::vector requiredExtensions = { }; -const std::vector requiredLayers = { - "VK_LAYER_KHRONOS_validation" -}; - Instance::Instance() { // create Vulkan instance const VkApplicationInfo appInfo{ @@ -26,8 +22,6 @@ Instance::Instance() { const VkInstanceCreateInfo createInfo{ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &appInfo, - .enabledLayerCount = static_cast(requiredLayers.size()), - .ppEnabledLayerNames = requiredLayers.data(), .enabledExtensionCount = static_cast(requiredExtensions.size()), .ppEnabledExtensionNames = requiredExtensions.data() }; From b34f66e38dca8a19a0cb5c3f1960fd6b8501525f Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 4 Jul 2025 13:32:41 +0200 Subject: [PATCH 067/253] extract shaders from Lossless.dll --- .gitattributes | 1 - .gitignore | 3 - CMakeLists.txt | 5 +- lsfg-vk-gen/CMakeLists.txt | 74 +++++++++- lsfg-vk-gen/include/context.hpp | 5 +- lsfg-vk-gen/include/core/shadermodule.hpp | 6 +- lsfg-vk-gen/include/pool/extract.hpp | 86 ++++++++++++ lsfg-vk-gen/include/pool/shaderpool.hpp | 48 +++++++ lsfg-vk-gen/include/shaderchains/alpha.hpp | 5 +- lsfg-vk-gen/include/shaderchains/beta.hpp | 5 +- lsfg-vk-gen/include/shaderchains/delta.hpp | 5 +- .../include/shaderchains/downsample.hpp | 5 +- lsfg-vk-gen/include/shaderchains/epsilon.hpp | 5 +- lsfg-vk-gen/include/shaderchains/extract.hpp | 5 +- lsfg-vk-gen/include/shaderchains/gamma.hpp | 5 +- lsfg-vk-gen/include/shaderchains/magic.hpp | 5 +- lsfg-vk-gen/include/shaderchains/merge.hpp | 5 +- lsfg-vk-gen/include/shaderchains/zeta.hpp | 5 +- lsfg-vk-gen/src/context.cpp | 24 ++-- lsfg-vk-gen/src/core/shadermodule.cpp | 18 +-- lsfg-vk-gen/src/lsfg.cpp | 19 ++- lsfg-vk-gen/src/pool/extract.cpp | 127 ++++++++++++++++++ lsfg-vk-gen/src/pool/shaderpool.cpp | 76 +++++++++++ lsfg-vk-gen/src/shaderchains/alpha.cpp | 11 +- lsfg-vk-gen/src/shaderchains/beta.cpp | 13 +- lsfg-vk-gen/src/shaderchains/delta.cpp | 11 +- lsfg-vk-gen/src/shaderchains/downsample.cpp | 5 +- lsfg-vk-gen/src/shaderchains/epsilon.cpp | 11 +- lsfg-vk-gen/src/shaderchains/extract.cpp | 5 +- lsfg-vk-gen/src/shaderchains/gamma.cpp | 15 ++- lsfg-vk-gen/src/shaderchains/magic.cpp | 5 +- lsfg-vk-gen/src/shaderchains/merge.cpp | 5 +- lsfg-vk-gen/src/shaderchains/zeta.cpp | 11 +- src/hooks.cpp | 7 +- 34 files changed, 544 insertions(+), 97 deletions(-) create mode 100644 lsfg-vk-gen/include/pool/extract.hpp create mode 100644 lsfg-vk-gen/include/pool/shaderpool.hpp create mode 100644 lsfg-vk-gen/src/pool/extract.cpp create mode 100644 lsfg-vk-gen/src/pool/shaderpool.cpp diff --git a/.gitattributes b/.gitattributes index 5f24d27..8d476d4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ *.cpp diff=cpp eol=lf *.hpp diff=cpp eol=lf *.md diff=markdown eol=lf -*.cs binary diff --git a/.gitignore b/.gitignore index 483bf36..eb55056 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,3 @@ /.clangd /.cache /.ccls - -# private resources -/rsc diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b21351..8199b0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,6 @@ cmake_minimum_required(VERSION 3.29) 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) @@ -30,6 +29,8 @@ target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk PRIVATE lsfg-vk-gen vulkan) +set_target_properties(lsfg-vk + PROPERTIES CXX_CLANG_TIDY clang-tidy) target_compile_options(lsfg-vk PRIVATE -Weverything # disable compat c++ flags @@ -47,4 +48,6 @@ target_compile_options(lsfg-vk PRIVATE -Wno-global-constructors # allow globals # required for vulkan -Wno-cast-function-type-strict + # required for dxvk (yeah don't worry about it) + -Qunused-arguments -Wl,--unresolved-symbols=ignore-all ) diff --git a/lsfg-vk-gen/CMakeLists.txt b/lsfg-vk-gen/CMakeLists.txt index b4a7c95..52ef4dd 100644 --- a/lsfg-vk-gen/CMakeLists.txt +++ b/lsfg-vk-gen/CMakeLists.txt @@ -1,3 +1,68 @@ +# dxvk subproject + +include(ExternalProject) + +ExternalProject_Add(dxvk_git + PREFIX ${CMAKE_BINARY_DIR}/dxvk + GIT_REPOSITORY "https://github.com/doitsujin/dxvk" + GIT_TAG "v2.6.2" + UPDATE_DISCONNECTED true + USES_TERMINAL_CONFIGURE true + USES_TERMINAL_BUILD true + CONFIGURE_COMMAND + cd ${CMAKE_BINARY_DIR}/dxvk/src/dxvk_git && + sed -i s/private:// + src/dxvk/dxvk_device.h && + CFLAGS=-w CXXFLAGS=-w CC=clang CXX=clang++ meson setup + --buildtype "release" + --prefix "${CMAKE_BINARY_DIR}/dxvk/native/usr" + --strip + -Dbuild_id=false + --force-fallback-for=libdisplay-info + --wipe + ${CMAKE_BINARY_DIR}/dxvk/build + BUILD_COMMAND + cd ${CMAKE_BINARY_DIR}/dxvk && + ninja -C build install && # sorry cursed lol + echo "clang -shared -o libdxvkinternals.so $(find build/src/{dxbc,wsi,vulkan,dxvk,dxgi,spirv,util}/*.p -type f -name \\*.o)" > archive.sh && + bash archive.sh + INSTALL_COMMAND "" +) + +add_library(dxvk INTERFACE) +add_dependencies(dxvk dxvk_git) + +target_link_directories(dxvk + INTERFACE ${CMAKE_BINARY_DIR}/dxvk/native/usr/lib + INTERFACE ${CMAKE_BINARY_DIR}/dxvk) +target_include_directories(dxvk SYSTEM + INTERFACE ${CMAKE_BINARY_DIR}/dxvk/native/usr/include/dxvk + INTERFACE ${CMAKE_BINARY_DIR}/dxvk/src/dxvk_git/src + INTERFACE ${CMAKE_BINARY_DIR}/dxvk/src/dxvk_git/include/spirv/include) +target_link_libraries(dxvk INTERFACE + dxvk_d3d11 dxvk_dxgi display-info dxvkinternals) + +# pe-parse subproject + +include(FetchContent) +FetchContent_Declare(peparse_git + GIT_REPOSITORY "https://github.com/trailofbits/pe-parse" + GIT_TAG "v2.1.1" +) +FetchContent_MakeAvailable(peparse_git) + +add_library(peparse INTERFACE) +add_dependencies(peparse peparse_git) + +target_link_directories(peparse + INTERFACE ${peparse_SOURCE_DIR}/pe-parser-library/include) +target_include_directories(peparse SYSTEM + INTERFACE ${peparse_BUILD_DIR}) +target_link_libraries(peparse + INTERFACE pe-parse) + +# main project + project(lsfg-vk-gen VERSION 0.0.1 DESCRIPTION "lsfg-vk-gen: LSFG on Linux through Vulkan (backend)" @@ -5,6 +70,7 @@ project(lsfg-vk-gen file(GLOB BACKEND_SOURCES "src/core/*.cpp" + "src/pool/*.cpp" "src/shaderchains/*.cpp" "src/*.cpp" ) @@ -15,7 +81,9 @@ target_include_directories(lsfg-vk-gen PRIVATE include PUBLIC public) target_link_libraries(lsfg-vk-gen - PUBLIC vulkan) + PUBLIC vulkan peparse crypto dxvk) +set_target_properties(lsfg-vk-gen + PROPERTIES CXX_CLANG_TIDY clang-tidy) target_compile_options(lsfg-vk-gen PRIVATE -Weverything # disable compat c++ flags @@ -33,4 +101,8 @@ target_compile_options(lsfg-vk-gen PRIVATE -Wno-global-constructors # required for vulkan -Wno-cast-function-type-strict + # required for peparse + -Wno-unused-template + # required for dxvk (yeah don't worry about it) + -Qunused-arguments -Wl,--unresolved-symbols=ignore-all ) diff --git a/lsfg-vk-gen/include/context.hpp b/lsfg-vk-gen/include/context.hpp index e511d98..d4120c2 100644 --- a/lsfg-vk-gen/include/context.hpp +++ b/lsfg-vk-gen/include/context.hpp @@ -1,6 +1,7 @@ #ifndef CONTEXT_HPP #define CONTEXT_HPP +#include "pool/shaderpool.hpp" #include "core/commandbuffer.hpp" #include "core/commandpool.hpp" #include "core/descriptorpool.hpp" @@ -29,6 +30,7 @@ namespace LSFG { /// Create a generator instance. /// /// @param device The Vulkan device to use. + /// @param shaderpool The shader pool to use. /// @param width Width of the input images. /// @param height Height of the input images. /// @param in0 File descriptor for the first input image. @@ -37,7 +39,8 @@ namespace LSFG { /// /// @throws LSFG::vulkan_error if the generator fails to initialize. /// - Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1, + Context(const Core::Device& device, Pool::ShaderPool& shaderpool, + uint32_t width, uint32_t height, int in0, int in1, const std::vector& outN); /// diff --git a/lsfg-vk-gen/include/core/shadermodule.hpp b/lsfg-vk-gen/include/core/shadermodule.hpp index 81d8c10..d71e7e4 100644 --- a/lsfg-vk-gen/include/core/shadermodule.hpp +++ b/lsfg-vk-gen/include/core/shadermodule.hpp @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -25,13 +24,12 @@ namespace LSFG::Core { /// Create the shader module. /// /// @param device Vulkan device - /// @param path Path to the shader file. + /// @param code SPIR-V bytecode for the shader. /// @param descriptorTypes Descriptor types used in the shader. /// - /// @throws std::system_error if the shader file cannot be opened or read. /// @throws LSFG::vulkan_error if object creation fails. /// - ShaderModule(const Core::Device& device, const std::string& path, + ShaderModule(const Core::Device& device, const std::vector& code, const std::vector>& descriptorTypes); /// Get the Vulkan handle. diff --git a/lsfg-vk-gen/include/pool/extract.hpp b/lsfg-vk-gen/include/pool/extract.hpp new file mode 100644 index 0000000..eaa0824 --- /dev/null +++ b/lsfg-vk-gen/include/pool/extract.hpp @@ -0,0 +1,86 @@ +#ifndef RESOURCES_HPP +#define RESOURCES_HPP + +#include +#include + +#include +#include +#include +#include + +namespace LSFG::Pool { + + /// + /// DLL resource extractor class. + /// + class Extractor { + public: + Extractor() noexcept = default; + + /// + /// Create a new extractor. + /// + /// @param path Path to the DLL file. + /// + /// @throws std::runtime_error if the file cannot be parsed. + /// + Extractor(const std::string& path); + + /// + /// Get a resource by its hash. + /// + /// @param hash Hash of the resource. + /// @return Resource data + /// + /// @throws std::runtime_error if the resource is not found. + /// + [[nodiscard]] std::vector getResource(const std::string& hash) const; + + // Trivially copyable, moveable and destructible + Extractor(const Extractor&) = delete; + Extractor& operator=(const Extractor&) = delete; + Extractor(Extractor&&) = default; + Extractor& operator=(Extractor&&) = default; + ~Extractor() = default; + private: + std::unordered_map> resources; + }; + + /// + /// DirectX bytecode translator class. + /// + class Translator { + public: + /// + /// Create a new translator. + /// + /// @throws std::runtime_error if the initialization fails. + /// + Translator(); + + /// + /// Translate DXBC into SPIR-V. + /// + /// @param dxbc Bytecode to translate. + /// @return Translated SPIR-V bytecode. + /// + /// @throws std::runtime_error if the translation fails. + /// + [[nodiscard]] std::vector translate(const std::vector& dxbc) const; + + // Trivially copyable, moveable and destructible + Translator(const Translator&) = delete; + Translator& operator=(const Translator&) = delete; + Translator(Translator&&) = default; + Translator& operator=(Translator&&) = default; + ~Translator() = default; + private: + std::shared_ptr device; + std::shared_ptr context; + }; + +} + + +#endif // RESOURCES_HPP diff --git a/lsfg-vk-gen/include/pool/shaderpool.hpp b/lsfg-vk-gen/include/pool/shaderpool.hpp new file mode 100644 index 0000000..0612829 --- /dev/null +++ b/lsfg-vk-gen/include/pool/shaderpool.hpp @@ -0,0 +1,48 @@ +#ifndef SHADERPOOL_HPP +#define SHADERPOOL_HPP + +#include "core/device.hpp" +#include "core/shadermodule.hpp" + +#include +#include + +namespace LSFG::Pool { + + /// + /// Shader pool for each Vulkan device. + /// + class ShaderPool { + public: + ShaderPool() noexcept = default; + + /// + /// Create the shader pool. + /// + /// @param path Path to the shader dll + /// + /// @throws std::runtime_error if the shader pool cannot be created. + /// + ShaderPool(const std::string& path); + + /// + /// Retrieve a shader module by name or create it. + /// + /// @param device Vulkan device + /// @param name Name of the shader module + /// @param types Descriptor types for the shader module + /// @return Shader module or empty + /// + /// @throws LSFG::vulkan_error if the shader module cannot be created. + /// + Core::ShaderModule getShader( + const Core::Device& device, const std::string& name, + const std::vector>& types); + private: + std::unordered_map> shaderBytecodes; + std::unordered_map shaders; + }; + +} + +#endif // SHADERPOOL_HPP diff --git a/lsfg-vk-gen/include/shaderchains/alpha.hpp b/lsfg-vk-gen/include/shaderchains/alpha.hpp index a38e40a..b40a8ea 100644 --- a/lsfg-vk-gen/include/shaderchains/alpha.hpp +++ b/lsfg-vk-gen/include/shaderchains/alpha.hpp @@ -1,6 +1,7 @@ #ifndef ALPHA_HPP #define ALPHA_HPP +#include "pool/shaderpool.hpp" #include "core/commandbuffer.hpp" #include "core/descriptorpool.hpp" #include "core/descriptorset.hpp" @@ -26,12 +27,14 @@ namespace LSFG::Shaderchains { /// Initialize the shaderchain. /// /// @param device The Vulkan device to create the resources on. + /// @param shaderpool The shader pool to use for shader modules. /// @param pool The descriptor pool to allocate in. /// @param inImg The input image to process /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Alpha(const Core::Device& device, const Core::DescriptorPool& pool, + Alpha(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, Core::Image inImg); /// diff --git a/lsfg-vk-gen/include/shaderchains/beta.hpp b/lsfg-vk-gen/include/shaderchains/beta.hpp index 8b39c85..f39b824 100644 --- a/lsfg-vk-gen/include/shaderchains/beta.hpp +++ b/lsfg-vk-gen/include/shaderchains/beta.hpp @@ -1,6 +1,7 @@ #ifndef BETA_HPP #define BETA_HPP +#include "pool/shaderpool.hpp" #include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/descriptorpool.hpp" @@ -28,6 +29,7 @@ namespace LSFG::Shaderchains { /// Initialize the shaderchain. /// /// @param device The Vulkan device to create the resources on. + /// @param shaderpool The shader pool to use for shader modules. /// @param pool The descriptor pool to allocate in. /// @param inImgs_0 The next input images to process (when fc % 3 == 0) /// @param inImgs_1 The prev input images to process (when fc % 3 == 0) @@ -36,7 +38,8 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Beta(const Core::Device& device, const Core::DescriptorPool& pool, + Beta(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs_0, std::array inImgs_1, std::array inImgs_2, diff --git a/lsfg-vk-gen/include/shaderchains/delta.hpp b/lsfg-vk-gen/include/shaderchains/delta.hpp index 90f7631..42220cc 100644 --- a/lsfg-vk-gen/include/shaderchains/delta.hpp +++ b/lsfg-vk-gen/include/shaderchains/delta.hpp @@ -1,6 +1,7 @@ #ifndef DELTA_HPP #define DELTA_HPP +#include "pool/shaderpool.hpp" #include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/descriptorpool.hpp" @@ -28,6 +29,7 @@ namespace LSFG::Shaderchains { /// Initialize the shaderchain. /// /// @param device The Vulkan device to create the resources on. + /// @param shaderpool The shader pool to use for shader modules. /// @param pool The descriptor pool to allocate in. /// @param inImgs The input images to process. /// @param optImg An optional additional input from the previous pass. @@ -35,7 +37,8 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Delta(const Core::Device& device, const Core::DescriptorPool& pool, + Delta(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs, std::optional optImg, size_t genc); diff --git a/lsfg-vk-gen/include/shaderchains/downsample.hpp b/lsfg-vk-gen/include/shaderchains/downsample.hpp index f136b69..bcf1410 100644 --- a/lsfg-vk-gen/include/shaderchains/downsample.hpp +++ b/lsfg-vk-gen/include/shaderchains/downsample.hpp @@ -1,6 +1,7 @@ #ifndef DOWNSAMPLE_HPP #define DOWNSAMPLE_HPP +#include "pool/shaderpool.hpp" #include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/descriptorpool.hpp" @@ -27,6 +28,7 @@ namespace LSFG::Shaderchains { /// Initialize the shaderchain. /// /// @param device The Vulkan device to create the resources on. + /// @param shaderpool The shader pool to use for shader modules. /// @param pool The descriptor pool to allocate in. /// @param inImg_0 The next full image to downsample (when fc % 2 == 0) /// @param inImg_1 The next full image to downsample (when fc % 2 == 1) @@ -34,7 +36,8 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Downsample(const Core::Device& device, const Core::DescriptorPool& pool, + Downsample(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, Core::Image inImg_0, Core::Image inImg_1, size_t genc); diff --git a/lsfg-vk-gen/include/shaderchains/epsilon.hpp b/lsfg-vk-gen/include/shaderchains/epsilon.hpp index 2559726..d1c1c61 100644 --- a/lsfg-vk-gen/include/shaderchains/epsilon.hpp +++ b/lsfg-vk-gen/include/shaderchains/epsilon.hpp @@ -1,6 +1,7 @@ #ifndef EPSILON_HPP #define EPSILON_HPP +#include "pool/shaderpool.hpp" #include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/descriptorpool.hpp" @@ -28,6 +29,7 @@ namespace LSFG::Shaderchains { /// Initialize the shaderchain. /// /// @param device The Vulkan device to create the resources on. + /// @param shaderpool The shader pool to use for shader modules. /// @param pool The descriptor pool to use for descriptor sets. /// @param inImgs1 The first set of input images to process. /// @param inImg2 The second type image to process. @@ -36,7 +38,8 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Epsilon(const Core::Device& device, const Core::DescriptorPool& pool, + Epsilon(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, std::optional optImg, diff --git a/lsfg-vk-gen/include/shaderchains/extract.hpp b/lsfg-vk-gen/include/shaderchains/extract.hpp index 92d0bf3..1fdc5ec 100644 --- a/lsfg-vk-gen/include/shaderchains/extract.hpp +++ b/lsfg-vk-gen/include/shaderchains/extract.hpp @@ -1,6 +1,7 @@ #ifndef EXTRACT_HPP #define EXTRACT_HPP +#include "pool/shaderpool.hpp" #include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/descriptorpool.hpp" @@ -26,6 +27,7 @@ namespace LSFG::Shaderchains { /// Initialize the shaderchain. /// /// @param device The Vulkan device to create the resources on. + /// @param shaderpool The shader pool to use for shader modules. /// @param pool The descriptor pool to use for descriptor sets. /// @param inImg1 The first set of input images to process. /// @param inImg2 The second type image to process. @@ -34,7 +36,8 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Extract(const Core::Device& device, const Core::DescriptorPool& pool, + Extract(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, Core::Image inImg1, Core::Image inImg2, VkExtent2D outExtent, diff --git a/lsfg-vk-gen/include/shaderchains/gamma.hpp b/lsfg-vk-gen/include/shaderchains/gamma.hpp index 5041e30..8ae424b 100644 --- a/lsfg-vk-gen/include/shaderchains/gamma.hpp +++ b/lsfg-vk-gen/include/shaderchains/gamma.hpp @@ -1,6 +1,7 @@ #ifndef GAMMA_HPP #define GAMMA_HPP +#include "pool/shaderpool.hpp" #include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/descriptorpool.hpp" @@ -30,6 +31,7 @@ namespace LSFG::Shaderchains { /// Initialize the shaderchain. /// /// @param device The Vulkan device to create the resources on. + /// @param shaderpool The shader pool to use for shader modules. /// @param pool The descriptor pool to allocate in. /// @param inImgs1_0 The next input images to process (when fc % 3 == 0). /// @param inImgs1_1 The prev input images to process (when fc % 3 == 0). @@ -42,7 +44,8 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Gamma(const Core::Device& device, const Core::DescriptorPool& pool, + Gamma(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs1_0, std::array inImgs1_1, std::array inImgs1_2, diff --git a/lsfg-vk-gen/include/shaderchains/magic.hpp b/lsfg-vk-gen/include/shaderchains/magic.hpp index 00ecf27..545285a 100644 --- a/lsfg-vk-gen/include/shaderchains/magic.hpp +++ b/lsfg-vk-gen/include/shaderchains/magic.hpp @@ -1,6 +1,7 @@ #ifndef MAGIC_HPP #define MAGIC_HPP +#include "pool/shaderpool.hpp" #include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/descriptorpool.hpp" @@ -28,6 +29,7 @@ namespace LSFG::Shaderchains { /// Initialize the shaderchain. /// /// @param device The Vulkan device to create the resources on. + /// @param shaderpool The shader pool to use for shader modules. /// @param pool The descriptor pool to use for descriptor sets. /// @param inImgs1_0 The next input images to process (when fc % 3 == 0). /// @param inImgs1_1 The prev input images to process (when fc % 3 == 0). @@ -39,7 +41,8 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Magic(const Core::Device& device, const Core::DescriptorPool& pool, + Magic(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs1_0, std::array inImgs1_1, std::array inImgs1_2, diff --git a/lsfg-vk-gen/include/shaderchains/merge.hpp b/lsfg-vk-gen/include/shaderchains/merge.hpp index bdfb5e3..3ef81bc 100644 --- a/lsfg-vk-gen/include/shaderchains/merge.hpp +++ b/lsfg-vk-gen/include/shaderchains/merge.hpp @@ -1,6 +1,7 @@ #ifndef MERGE_HPP #define MERGE_HPP +#include "pool/shaderpool.hpp" #include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/descriptorpool.hpp" @@ -28,6 +29,7 @@ namespace LSFG::Shaderchains { /// Initialize the shaderchain. /// /// @param device The Vulkan device to create the resources on. + /// @param shaderpool The shader pool to use for shader modules. /// @param pool The descriptor pool to use for descriptor sets. /// @param inImg1 The prev full image when fc % 2 == 0 /// @param inImg2 The next full image when fc % 2 == 0 @@ -39,7 +41,8 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Merge(const Core::Device& device, const Core::DescriptorPool& pool, + Merge(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, Core::Image inImg1, Core::Image inImg2, Core::Image inImg3, diff --git a/lsfg-vk-gen/include/shaderchains/zeta.hpp b/lsfg-vk-gen/include/shaderchains/zeta.hpp index bf08c30..642a6d9 100644 --- a/lsfg-vk-gen/include/shaderchains/zeta.hpp +++ b/lsfg-vk-gen/include/shaderchains/zeta.hpp @@ -1,6 +1,7 @@ #ifndef ZETA_HPP #define ZETA_HPP +#include "pool/shaderpool.hpp" #include "core/buffer.hpp" #include "core/commandbuffer.hpp" #include "core/descriptorpool.hpp" @@ -28,6 +29,7 @@ namespace LSFG::Shaderchains { /// Initialize the shaderchain. /// /// @param device The Vulkan device to create the resources on. + /// @param shaderpool The shader pool to use for shader modules. /// @param pool The descriptor pool to use for descriptor sets. /// @param inImgs1 The first set of input images to process. /// @param inImg2 The second type image to process. @@ -36,7 +38,8 @@ namespace LSFG::Shaderchains { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Zeta(const Core::Device& device, const Core::DescriptorPool& pool, + Zeta(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, Core::Image inImg3, diff --git a/lsfg-vk-gen/src/context.cpp b/lsfg-vk-gen/src/context.cpp index 0c9ff67..f7d1f82 100644 --- a/lsfg-vk-gen/src/context.cpp +++ b/lsfg-vk-gen/src/context.cpp @@ -1,6 +1,7 @@ #include "context.hpp" #include "core/fence.hpp" #include "core/semaphore.hpp" +#include "pool/shaderpool.hpp" #include "lsfg.hpp" #include @@ -10,7 +11,8 @@ using namespace LSFG; -Context::Context(const Core::Device& device, uint32_t width, uint32_t height, int in0, int in1, +Context::Context(const Core::Device& device, Pool::ShaderPool& shaderpool, + uint32_t width, uint32_t height, int in0, int in1, const std::vector& outN) { // import images this->inImg_0 = Core::Image(device, { width, height }, @@ -35,18 +37,18 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in } // create shader chains - this->downsampleChain = Shaderchains::Downsample(device, this->descPool, + this->downsampleChain = Shaderchains::Downsample(device, shaderpool, this->descPool, this->inImg_0, this->inImg_1, outN.size()); for (size_t i = 0; i < 7; i++) - this->alphaChains.at(i) = Shaderchains::Alpha(device, this->descPool, + this->alphaChains.at(i) = Shaderchains::Alpha(device, shaderpool, this->descPool, this->downsampleChain.getOutImages().at(i)); - this->betaChain = Shaderchains::Beta(device, this->descPool, + this->betaChain = Shaderchains::Beta(device, shaderpool, this->descPool, this->alphaChains.at(0).getOutImages0(), this->alphaChains.at(0).getOutImages1(), this->alphaChains.at(0).getOutImages2(), outN.size()); for (size_t i = 0; i < 7; i++) { if (i < 4) { - this->gammaChains.at(i) = Shaderchains::Gamma(device, this->descPool, + this->gammaChains.at(i) = Shaderchains::Gamma(device, shaderpool, this->descPool, this->alphaChains.at(6 - i).getOutImages0(), this->alphaChains.at(6 - i).getOutImages1(), this->alphaChains.at(6 - i).getOutImages2(), @@ -59,7 +61,7 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in outN.size() ); } else { - this->magicChains.at(i - 4) = Shaderchains::Magic(device, this->descPool, + this->magicChains.at(i - 4) = Shaderchains::Magic(device, shaderpool, this->descPool, this->alphaChains.at(6 - i).getOutImages0(), this->alphaChains.at(6 - i).getOutImages1(), this->alphaChains.at(6 - i).getOutImages2(), @@ -70,20 +72,20 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()}, outN.size() ); - this->deltaChains.at(i - 4) = Shaderchains::Delta(device, this->descPool, + this->deltaChains.at(i - 4) = Shaderchains::Delta(device, shaderpool, this->descPool, this->magicChains.at(i - 4).getOutImages1(), i == 4 ? std::nullopt : std::optional{this->deltaChains.at(i - 5).getOutImage()}, outN.size() ); - this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(device, this->descPool, + this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(device, shaderpool, this->descPool, this->magicChains.at(i - 4).getOutImages2(), this->betaChain.getOutImages().at(6 - i), i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()}, outN.size() ); - this->zetaChains.at(i - 4) = Shaderchains::Zeta(device, this->descPool, + this->zetaChains.at(i - 4) = Shaderchains::Zeta(device, shaderpool, this->descPool, this->magicChains.at(i - 4).getOutImages3(), i == 4 ? this->gammaChains.at(i - 1).getOutImage1() : this->zetaChains.at(i - 5).getOutImage(), @@ -92,7 +94,7 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in ); if (i >= 6) continue; // no extract for i >= 6 - this->extractChains.at(i - 4) = Shaderchains::Extract(device, this->descPool, + this->extractChains.at(i - 4) = Shaderchains::Extract(device, shaderpool, this->descPool, this->zetaChains.at(i - 4).getOutImage(), this->epsilonChains.at(i - 4).getOutImage(), this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent(), @@ -100,7 +102,7 @@ Context::Context(const Core::Device& device, uint32_t width, uint32_t height, in ); } } - this->mergeChain = Shaderchains::Merge(device, this->descPool, + this->mergeChain = Shaderchains::Merge(device, shaderpool, this->descPool, this->inImg_1, this->inImg_0, this->zetaChains.at(2).getOutImage(), diff --git a/lsfg-vk-gen/src/core/shadermodule.cpp b/lsfg-vk-gen/src/core/shadermodule.cpp index 3c8e98f..df9c2dd 100644 --- a/lsfg-vk-gen/src/core/shadermodule.cpp +++ b/lsfg-vk-gen/src/core/shadermodule.cpp @@ -1,26 +1,10 @@ #include "core/shadermodule.hpp" #include "lsfg.hpp" -#include - using namespace LSFG::Core; -ShaderModule::ShaderModule(const Core::Device& device, const std::string& path, +ShaderModule::ShaderModule(const Core::Device& device, const std::vector& code, const std::vector>& descriptorTypes) { - // read shader bytecode - std::ifstream file(path, std::ios::ate | std::ios::binary); - if (!file) - throw std::system_error(errno, std::generic_category(), "Failed to open shader file: " + path); - - const std::streamsize size = file.tellg(); - std::vector code(static_cast(size)); - - file.seekg(0, std::ios::beg); - if (!file.read(reinterpret_cast(code.data()), size)) - throw std::system_error(errno, std::generic_category(), "Failed to read shader file: " + path); - - file.close(); - // create shader module const uint8_t* data_ptr = code.data(); const VkShaderModuleCreateInfo createInfo{ diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp index 816c4ff..f3fcab9 100644 --- a/lsfg-vk-gen/src/lsfg.cpp +++ b/lsfg-vk-gen/src/lsfg.cpp @@ -2,10 +2,13 @@ #include "core/device.hpp" #include "core/instance.hpp" #include "context.hpp" +#include "pool/shaderpool.hpp" #include "utils.hpp" +#include #include #include +#include #include using namespace LSFG; @@ -13,6 +16,7 @@ using namespace LSFG; namespace { std::optional instance; std::optional device; + std::optional pool; std::unordered_map contexts; } @@ -20,8 +24,12 @@ void LSFG::initialize() { if (instance.has_value() || device.has_value()) return; + char* dllPath = getenv("LSFG_DLL_PATH"); + const std::string dllPathStr = dllPath ? std::string(dllPath) : "Lossless.dll"; + instance.emplace(); device.emplace(*instance); + pool.emplace(dllPathStr); Globals::initializeGlobals(*device); @@ -30,16 +38,16 @@ void LSFG::initialize() { int32_t LSFG::createContext(uint32_t width, uint32_t height, int in0, int in1, const std::vector& outN) { - if (!instance.has_value() || !device.has_value()) + if (!instance.has_value() || !device.has_value() || !pool.has_value()) throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); auto id = std::rand(); - contexts.emplace(id, Context(*device, width, height, in0, in1, outN)); + contexts.emplace(id, Context(*device, *pool, width, height, in0, in1, outN)); return id; } void LSFG::presentContext(int32_t id, int inSem, const std::vector& outSem) { - if (!instance.has_value() || !device.has_value()) + if (!instance.has_value() || !device.has_value() || !pool.has_value()) throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); auto it = contexts.find(id); @@ -51,7 +59,7 @@ void LSFG::presentContext(int32_t id, int inSem, const std::vector& outSem) } void LSFG::deleteContext(int32_t id) { - if (!instance.has_value() || !device.has_value()) + if (!instance.has_value() || !device.has_value() || !pool.has_value()) throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); auto it = contexts.find(id); @@ -63,12 +71,13 @@ void LSFG::deleteContext(int32_t id) { } void LSFG::finalize() { - if (!instance.has_value() || !device.has_value()) + if (!instance.has_value() || !device.has_value() || !pool.has_value()) return; Globals::uninitializeGlobals(); vkDeviceWaitIdle(device->handle()); + pool.reset(); device.reset(); instance.reset(); } diff --git a/lsfg-vk-gen/src/pool/extract.cpp b/lsfg-vk-gen/src/pool/extract.cpp new file mode 100644 index 0000000..2fa5698 --- /dev/null +++ b/lsfg-vk-gen/src/pool/extract.cpp @@ -0,0 +1,127 @@ +#include "pool/extract.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace LSFG::Pool; + +namespace { + + using ResourceMap = std::unordered_map>; + + /// Callback function for each resource. + int on_resource(void* data, const peparse::resource& res) { + if (res.type != peparse::RT_RCDATA || res.buf == nullptr || res.buf->bufLen <= 0) + return 0; + + // hash the resource data + std::array hash{}; + SHA256(res.buf->buf, res.buf->bufLen, hash.data()); + + std::array base64{}; + const int base64_len = EVP_EncodeBlock(base64.data(), hash.data(), hash.size()); + const std::string hash_str(reinterpret_cast(base64.data()), + static_cast(base64_len)); + + // store the resource + std::vector resource_data(res.buf->bufLen); + std::copy_n(res.buf->buf, res.buf->bufLen, resource_data.data()); + + auto* map = reinterpret_cast(data); + (*map)[hash_str] = resource_data; + + return 0; + } + +} + +Extractor::Extractor(const std::string& path) { + peparse::parsed_pe* pe = peparse::ParsePEFromFile(path.c_str()); + if (!pe) + throw std::runtime_error("Unable to parse PE file: " + path); + + peparse::IterRsrc(pe, on_resource, &this->resources); + peparse::DestructParsedPE(pe); +} + +std::vector Extractor::getResource(const std::string& hash) const { + auto it = this->resources.find(hash); + if (it != this->resources.end()) + return it->second; + throw std::runtime_error("Resource not found: " + hash); +} + +Translator::Translator() { + setenv("DXVK_WSI_DRIVER", "SDL3", 0); + setenv("DXVK_LOG_LEVEL", "error", 0); + setenv("DXVK_LOG_PATH", "none", 0); + + // create d3d11 device + ID3D11Device* device{}; + ID3D11DeviceContext* context{}; + const D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_1; + D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE::D3D_DRIVER_TYPE_HARDWARE, + nullptr, + 0, + &featureLevel, 1, + D3D11_SDK_VERSION, + &device, nullptr, &context + ); + if (!device || !context) + throw std::runtime_error("Failed to create D3D11 device"); + + // store device in shared ptr + this->device = std::shared_ptr( + new ID3D11Device*(device), + [](ID3D11Device** dev) { + (*dev)->Release(); + } + ); + this->context = std::shared_ptr( + new ID3D11DeviceContext*(context), + [](ID3D11DeviceContext** ctx) { + (*ctx)->Release(); + } + ); +} + +std::vector Translator::translate(const std::vector& dxbc) const { + // create compute shader and pipeline + ID3D11ComputeShader* shader = nullptr; + (*this->device)->CreateComputeShader(dxbc.data(), dxbc.size(), nullptr, &shader); + if (!shader) + throw std::runtime_error("Failed to create compute shader from DXBC"); + + auto* dxvk_shader = reinterpret_cast(shader); + auto* dxvk_device = reinterpret_cast(*this->device); + + auto* pipeline = dxvk_device->GetDXVKDevice()->m_objects.pipelineManager().createComputePipeline({ + .cs = dxvk_shader->GetCommonShader()->GetShader() + }); + + // extract spir-v from d3d11 shader + auto code = dxvk_shader->GetCommonShader()->GetShader()->getCode( + pipeline->getBindings(), dxvk::DxvkShaderModuleCreateInfo()); + std::vector spirv(code.size()); + std::copy_n(reinterpret_cast(code.data()), + code.size(), spirv.data()); + + // cleanup-ish (i think the pipeline will linger) + shader->Release(); + return spirv; +} diff --git a/lsfg-vk-gen/src/pool/shaderpool.cpp b/lsfg-vk-gen/src/pool/shaderpool.cpp new file mode 100644 index 0000000..292ed23 --- /dev/null +++ b/lsfg-vk-gen/src/pool/shaderpool.cpp @@ -0,0 +1,76 @@ +#include "pool/shaderpool.hpp" + +#include "pool/extract.hpp" + +using namespace LSFG; +using namespace LSFG::Pool; + +const std::unordered_map SHADERS = { + { "alpha/0.spv", "lilEUr7nBgA8P6VSqCms09t9b+DZH6dVlcefVuFHlc8=" }, + { "alpha/1.spv", "2TRNidol3BNs/aeLl2/Om7z8bAlpehkOPVtmMao1q84=" }, + { "alpha/2.spv", "tP6qIJZhd4pGr1pop1e9ztW1gwp97ufQa2GaBZBYZJE=" }, + { "alpha/3.spv", "gA4ZejNp+RwtqjtTzGdGf5D/CjSGlwFB2nOgDAIv91k=" }, + { "beta/0.spv", "uQ/xsBMKRuJhbxstBukWMhXYuppPAYygxkb/3kNu4vI=" }, + { "beta/1.spv", "BUbrL9fZREXLlg1lmlTYD6n8DwpzHkho5bI3RLbfNJg=" }, + { "beta/2.spv", "bz0lxQjMYp6HLqw12X3jfV7H0SOZKrqUhgtw17WgTx4=" }, + { "beta/3.spv", "JA5/8p7yiiiCxmuiTsOR9Fb/z1qp8KlyU2wo9Wfpbcc=" }, + { "beta/4.spv", "/I+iYEwzOFylXZJWWNQ/oUT6SeLVnpotNXGV8y/FUVk=" }, + { "delta/0.spv", "gtBWy1WtP8NO+Z1sSPMgOJ75NaPEKvthc7imNGzJkGI=" }, + { "delta/1.spv", "JiqZZIoHay/uS1ptzlz3EWKUPct/jQHoFtN0qlEtVUU=" }, + { "delta/2.spv", "zkBa37GvAG8izeIv4o/3OowpxiobfOdNmPyVWl2BSWY=" }, + { "delta/3.spv", "neIMl/PCPonXqjtZykMb9tR4yW7JkZfMTqZPGOmJQUg=" }, + { "downsample.spv", "F9BppS+ytDjO3aoMEY7deMzLaSUhX8EuI+eH8654Fpw=" }, + { "epsilon/0.spv", "YHECg9LrgTCM8lABFOXkC5qTKoHsIMWnZ6ST3dexD08=" }, + { "epsilon/1.spv", "Uv7CfTi6x69c9Exuc16UqA7fvLTUGicHZVi5jhHKo0w=" }, + { "epsilon/2.spv", "0vmxxGRa6gbl5dqmKTBO9x/ZM2oEUJ5JtGfqcHhvouQ=" }, + { "epsilon/3.spv", "Sa/gkbCCDyCyUh8BSOa882t4qDc51oeP6+Kj3O3EaxM=" }, + { "extract.spv", "xKUdoEwFJDsc/kX/aY1FyzlMlOaJX4iHQLlthe2MvBs=" }, + { "gamma/0.spv", "AJuuF/X9NnypgBd89GbXMKcXC2meysYayiZQZwu3WhU=" }, + { "gamma/1.spv", "LLr/3D+whLd6XuKkBS7rlaNN+r8qB/Khr4ii+M7KSxY=" }, + { "gamma/2.spv", "KjHXdawBR8AMK7Kud/vXJmJTddXFKppREEpsykjwZDc=" }, + { "gamma/3.spv", "zAnAC73i76AJjv0o1To3bBu2jnIWXzX3NlSMvU3Lgxw=" }, + { "gamma/4.spv", "ivQ7ltprazBOXb46yxul9HJ5ByJk2LbG034cC6NkEpk=" }, + { "gamma/5.spv", "lHYgyCpWnMIB74HL22BKQyoqUGvUjgR79W4vXFXzXe4=" }, + { "magic.spv", "ZdoTjEhrlbAxq0MtaJyk6jQ5+hrySEsnvn+jseukAuI=" }, + { "merge.spv", "dnluf4IHKNaqz6WvH7qodn+fZ56ORx+w3MUOwH7huok=" }, + { "zeta/0.spv", "LLr/3D+whLd6XuKkBS7rlaNN+r8qB/Khr4ii+M7KSxY=" }, + { "zeta/1.spv", "KjHXdawBR8AMK7Kud/vXJmJTddXFKppREEpsykjwZDc=" }, + { "zeta/2.spv", "zAnAC73i76AJjv0o1To3bBu2jnIWXzX3NlSMvU3Lgxw=" }, + { "zeta/3.spv", "ivQ7ltprazBOXb46yxul9HJ5ByJk2LbG034cC6NkEpk=" }, + { "zeta/4.spv", "lHYgyCpWnMIB74HL22BKQyoqUGvUjgR79W4vXFXzXe4=" } +}; + +ShaderPool::ShaderPool(const std::string& path) { + const Extractor extractor(path); + const Translator translator; + + for (const auto& [name, hash] : SHADERS) { + auto data = extractor.getResource(hash); + if (data.empty()) + throw std::runtime_error("Shader code is empty: " + name); + + auto code = translator.translate(data); + if (code.empty()) + throw std::runtime_error("Shader code translation failed: " + name); + + shaderBytecodes[name] = std::move(code); + } +} + +Core::ShaderModule ShaderPool::getShader( + const Core::Device& device, const std::string& name, + const std::vector>& types) { + auto it = shaders.find(name); + if (it != shaders.end()) + return it->second; + + // create the shader module + auto cit = shaderBytecodes.find(name); + if (cit == shaderBytecodes.end()) + throw std::runtime_error("Shader code translation failed: " + name); + auto code = cit->second; + + Core::ShaderModule shader(device, code, types); + shaders[name] = shader; + return shader; +} diff --git a/lsfg-vk-gen/src/shaderchains/alpha.cpp b/lsfg-vk-gen/src/shaderchains/alpha.cpp index 52c55aa..8f525ca 100644 --- a/lsfg-vk-gen/src/shaderchains/alpha.cpp +++ b/lsfg-vk-gen/src/shaderchains/alpha.cpp @@ -3,23 +3,24 @@ using namespace LSFG::Shaderchains; -Alpha::Alpha(const Core::Device& device, const Core::DescriptorPool& pool, +Alpha::Alpha(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, Core::Image inImg) : inImg(std::move(inImg)) { this->shaderModules = {{ - Core::ShaderModule(device, "rsc/shaders/alpha/0.spv", + shaderpool.getShader(device, "alpha/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/alpha/1.spv", + shaderpool.getShader(device, "alpha/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/alpha/2.spv", + shaderpool.getShader(device, "alpha/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/alpha/3.spv", + shaderpool.getShader(device, "alpha/3.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) diff --git a/lsfg-vk-gen/src/shaderchains/beta.cpp b/lsfg-vk-gen/src/shaderchains/beta.cpp index 19f0cef..23346de 100644 --- a/lsfg-vk-gen/src/shaderchains/beta.cpp +++ b/lsfg-vk-gen/src/shaderchains/beta.cpp @@ -3,7 +3,8 @@ using namespace LSFG::Shaderchains; -Beta::Beta(const Core::Device& device, const Core::DescriptorPool& pool, +Beta::Beta(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs_0, std::array inImgs_1, std::array inImgs_2, @@ -12,23 +13,23 @@ Beta::Beta(const Core::Device& device, const Core::DescriptorPool& pool, inImgs_1(std::move(inImgs_1)), inImgs_2(std::move(inImgs_2)) { this->shaderModules = {{ - Core::ShaderModule(device, "rsc/shaders/beta/0.spv", + shaderpool.getShader(device, "beta/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 8+4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/beta/1.spv", + shaderpool.getShader(device, "beta/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/beta/2.spv", + shaderpool.getShader(device, "beta/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/beta/3.spv", + shaderpool.getShader(device, "beta/3.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/beta/4.spv", + shaderpool.getShader(device, "beta/4.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, diff --git a/lsfg-vk-gen/src/shaderchains/delta.cpp b/lsfg-vk-gen/src/shaderchains/delta.cpp index 7503729..63e463e 100644 --- a/lsfg-vk-gen/src/shaderchains/delta.cpp +++ b/lsfg-vk-gen/src/shaderchains/delta.cpp @@ -3,26 +3,27 @@ using namespace LSFG::Shaderchains; -Delta::Delta(const Core::Device& device, const Core::DescriptorPool& pool, +Delta::Delta(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs, std::optional optImg, size_t genc) : inImgs(std::move(inImgs)), optImg(std::move(optImg)) { this->shaderModules = {{ - Core::ShaderModule(device, "rsc/shaders/delta/0.spv", + shaderpool.getShader(device, "delta/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/delta/1.spv", + shaderpool.getShader(device, "delta/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/delta/2.spv", + shaderpool.getShader(device, "delta/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/delta/3.spv", + shaderpool.getShader(device, "delta/3.spv", { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, diff --git a/lsfg-vk-gen/src/shaderchains/downsample.cpp b/lsfg-vk-gen/src/shaderchains/downsample.cpp index f06dd59..c7e4ef2 100644 --- a/lsfg-vk-gen/src/shaderchains/downsample.cpp +++ b/lsfg-vk-gen/src/shaderchains/downsample.cpp @@ -3,12 +3,13 @@ using namespace LSFG::Shaderchains; -Downsample::Downsample(const Core::Device& device, const Core::DescriptorPool& pool, +Downsample::Downsample(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, Core::Image inImg_0, Core::Image inImg_1, size_t genc) : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) { - this->shaderModule = Core::ShaderModule(device, "rsc/shaders/downsample.spv", + this->shaderModule = shaderpool.getShader(device, "downsample.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, diff --git a/lsfg-vk-gen/src/shaderchains/epsilon.cpp b/lsfg-vk-gen/src/shaderchains/epsilon.cpp index 2093623..931596c 100644 --- a/lsfg-vk-gen/src/shaderchains/epsilon.cpp +++ b/lsfg-vk-gen/src/shaderchains/epsilon.cpp @@ -3,7 +3,8 @@ using namespace LSFG::Shaderchains; -Epsilon::Epsilon(const Core::Device& device, const Core::DescriptorPool& pool, +Epsilon::Epsilon(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, std::optional optImg, @@ -12,19 +13,19 @@ Epsilon::Epsilon(const Core::Device& device, const Core::DescriptorPool& pool, inImg2(std::move(inImg2)), optImg(std::move(optImg)) { this->shaderModules = {{ - Core::ShaderModule(device, "rsc/shaders/epsilon/0.spv", + shaderpool.getShader(device, "epsilon/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/epsilon/1.spv", + shaderpool.getShader(device, "epsilon/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/epsilon/2.spv", + shaderpool.getShader(device, "epsilon/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/epsilon/3.spv", + shaderpool.getShader(device, "epsilon/3.spv", { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, diff --git a/lsfg-vk-gen/src/shaderchains/extract.cpp b/lsfg-vk-gen/src/shaderchains/extract.cpp index 4f63223..8526f8d 100644 --- a/lsfg-vk-gen/src/shaderchains/extract.cpp +++ b/lsfg-vk-gen/src/shaderchains/extract.cpp @@ -3,14 +3,15 @@ using namespace LSFG::Shaderchains; -Extract::Extract(const Core::Device& device, const Core::DescriptorPool& pool, +Extract::Extract(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, Core::Image inImg1, Core::Image inImg2, VkExtent2D outExtent, size_t genc) : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)) { - this->shaderModule = Core::ShaderModule(device, "rsc/shaders/extract.spv", + this->shaderModule = shaderpool.getShader(device, "extract.spv", { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, diff --git a/lsfg-vk-gen/src/shaderchains/gamma.cpp b/lsfg-vk-gen/src/shaderchains/gamma.cpp index f469a9c..f7e746a 100644 --- a/lsfg-vk-gen/src/shaderchains/gamma.cpp +++ b/lsfg-vk-gen/src/shaderchains/gamma.cpp @@ -3,7 +3,8 @@ using namespace LSFG::Shaderchains; -Gamma::Gamma(const Core::Device& device, const Core::DescriptorPool& pool, +Gamma::Gamma(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs1_0, std::array inImgs1_1, std::array inImgs1_2, @@ -18,29 +19,29 @@ Gamma::Gamma(const Core::Device& device, const Core::DescriptorPool& pool, inImg2(std::move(inImg2)), optImg2(std::move(optImg2)) { this->shaderModules = {{ - Core::ShaderModule(device, "rsc/shaders/gamma/0.spv", + shaderpool.getShader(device, "gamma/0.spv", { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 10, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), - Core::ShaderModule(device, "rsc/shaders/gamma/1.spv", + shaderpool.getShader(device, "gamma/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/gamma/2.spv", + shaderpool.getShader(device, "gamma/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/gamma/3.spv", + shaderpool.getShader(device, "gamma/3.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/gamma/4.spv", + shaderpool.getShader(device, "gamma/4.spv", { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), - Core::ShaderModule(device, "rsc/shaders/gamma/5.spv", + shaderpool.getShader(device, "gamma/5.spv", { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, diff --git a/lsfg-vk-gen/src/shaderchains/magic.cpp b/lsfg-vk-gen/src/shaderchains/magic.cpp index 6a89f09..a4e8d4b 100644 --- a/lsfg-vk-gen/src/shaderchains/magic.cpp +++ b/lsfg-vk-gen/src/shaderchains/magic.cpp @@ -3,7 +3,8 @@ using namespace LSFG::Shaderchains; -Magic::Magic(const Core::Device& device, const Core::DescriptorPool& pool, +Magic::Magic(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs1_0, std::array inImgs1_1, std::array inImgs1_2, @@ -16,7 +17,7 @@ Magic::Magic(const Core::Device& device, const Core::DescriptorPool& pool, inImgs1_2(std::move(inImgs1_2)), inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), optImg(std::move(optImg)) { - this->shaderModule = Core::ShaderModule(device, "rsc/shaders/magic.spv", + this->shaderModule = shaderpool.getShader(device, "magic.spv", { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4+4+2+1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 3+3+2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, diff --git a/lsfg-vk-gen/src/shaderchains/merge.cpp b/lsfg-vk-gen/src/shaderchains/merge.cpp index bd2cb92..e9866d2 100644 --- a/lsfg-vk-gen/src/shaderchains/merge.cpp +++ b/lsfg-vk-gen/src/shaderchains/merge.cpp @@ -3,7 +3,8 @@ using namespace LSFG::Shaderchains; -Merge::Merge(const Core::Device& device, const Core::DescriptorPool& pool, +Merge::Merge(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, Core::Image inImg1, Core::Image inImg2, Core::Image inImg3, @@ -16,7 +17,7 @@ Merge::Merge(const Core::Device& device, const Core::DescriptorPool& pool, inImg3(std::move(inImg3)), inImg4(std::move(inImg4)), inImg5(std::move(inImg5)) { - this->shaderModule = Core::ShaderModule(device, "rsc/shaders/merge.spv", + this->shaderModule = shaderpool.getShader(device, "merge.spv", { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, diff --git a/lsfg-vk-gen/src/shaderchains/zeta.cpp b/lsfg-vk-gen/src/shaderchains/zeta.cpp index b5b3c99..1824bb4 100644 --- a/lsfg-vk-gen/src/shaderchains/zeta.cpp +++ b/lsfg-vk-gen/src/shaderchains/zeta.cpp @@ -3,7 +3,8 @@ using namespace LSFG::Shaderchains; -Zeta::Zeta(const Core::Device& device, const Core::DescriptorPool& pool, +Zeta::Zeta(const Core::Device& device, Pool::ShaderPool& shaderpool, + const Core::DescriptorPool& pool, std::array inImgs1, Core::Image inImg2, Core::Image inImg3, @@ -12,19 +13,19 @@ Zeta::Zeta(const Core::Device& device, const Core::DescriptorPool& pool, inImg2(std::move(inImg2)), inImg3(std::move(inImg3)) { this->shaderModules = {{ - Core::ShaderModule(device, "rsc/shaders/zeta/0.spv", + shaderpool.getShader(device, "zeta/0.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/zeta/1.spv", + shaderpool.getShader(device, "zeta/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/zeta/2.spv", + shaderpool.getShader(device, "zeta/2.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - Core::ShaderModule(device, "rsc/shaders/zeta/3.spv", + shaderpool.getShader(device, "zeta/3.spv", { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, diff --git a/src/hooks.cpp b/src/hooks.cpp index 3154b2f..f7530c0 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -21,7 +21,9 @@ namespace { const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { // create lsfg + Loader::DL::disableHooks(); LSFG::initialize(); + Loader::DL::enableHooks(); // add extensions auto extensions = Utils::addExtensions(pCreateInfo->ppEnabledExtensionNames, @@ -70,13 +72,13 @@ namespace { // store device info try { const char* frameGen = std::getenv("LSFG_MULTIPLIER"); - if (!frameGen) frameGen = "1"; + if (!frameGen) frameGen = "2"; devices.emplace(*pDevice, DeviceInfo { .device = *pDevice, .physicalDevice = physicalDevice, .queue = Utils::findQueue(*pDevice, physicalDevice, &createInfo, VK_QUEUE_GRAPHICS_BIT), - .frameGen = std::stoul(frameGen) + .frameGen = std::max(1, std::stoul(frameGen) - 1) }); } catch (const std::exception& e) { Log::error("Failed to create device info: {}", e.what()); @@ -133,6 +135,7 @@ namespace { )); swapchainToDeviceTable.emplace(*pSwapchain, device); + Log::debug("Created swapchain with {} images", imageCount); } catch (const LSFG::vulkan_error& e) { Log::error("Encountered Vulkan error {:x} while creating swapchain: {}", static_cast(e.error()), e.what()); From e03fcaf4b0bb17d5ba305ed67fe9f4cbb84e5dc1 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 4 Jul 2025 14:38:42 +0200 Subject: [PATCH 068/253] install cmake logic --- CMakeLists.txt | 11 +++++++++++ lsfg-vk-gen/CMakeLists.txt | 24 ++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8199b0c..5f9edb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,10 @@ set(CMAKE_CXX_COMPILER clang++) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_SKIP_BUILD_RPATH FALSE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +set(CMAKE_INSTALL_RPATH "$\{ORIGIN\}") + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # main project @@ -51,3 +55,10 @@ target_compile_options(lsfg-vk PRIVATE # required for dxvk (yeah don't worry about it) -Qunused-arguments -Wl,--unresolved-symbols=ignore-all ) + +install(FILES "${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library/build/libpe-parse.so" DESTINATION lib) +install(FILES "${CMAKE_BINARY_DIR}/dxvk/native/usr/lib/libdxvk_d3d11.so.0.20602" DESTINATION lib RENAME libdxvk_d3d11.so.0) +install(FILES "${CMAKE_BINARY_DIR}/dxvk/native/usr/lib/libdxvk_dxgi.so.0.20602" DESTINATION lib RENAME libdxvk_dxgi.so.0) +install(FILES "${CMAKE_BINARY_DIR}/dxvk/libdxvkinternals.so" DESTINATION lib) +install(FILES "${CMAKE_BINARY_DIR}/liblsfg-vk.so" DESTINATION lib) +install(FILES "${CMAKE_BINARY_DIR}/lsfg-vk-gen/liblsfg-vk-gen.so" DESTINATION lib) diff --git a/lsfg-vk-gen/CMakeLists.txt b/lsfg-vk-gen/CMakeLists.txt index 52ef4dd..4445665 100644 --- a/lsfg-vk-gen/CMakeLists.txt +++ b/lsfg-vk-gen/CMakeLists.txt @@ -44,20 +44,32 @@ target_link_libraries(dxvk INTERFACE # pe-parse subproject -include(FetchContent) -FetchContent_Declare(peparse_git +ExternalProject_Add(peparse_git + PREFIX ${CMAKE_BINARY_DIR}/peparse GIT_REPOSITORY "https://github.com/trailofbits/pe-parse" GIT_TAG "v2.1.1" + UPDATE_DISCONNECTED true + USES_TERMINAL_CONFIGURE true + USES_TERMINAL_BUILD true + CONFIGURE_COMMAND + cd ${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library && + CC=clang CXX=clang++ CXXFLAGS=-Wno-deprecated-declarations + cmake -B build -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DBUILD_SHARED_LIBS=1 + BUILD_COMMAND + cd ${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library && + ninja -C build + INSTALL_COMMAND "" ) -FetchContent_MakeAvailable(peparse_git) add_library(peparse INTERFACE) add_dependencies(peparse peparse_git) target_link_directories(peparse - INTERFACE ${peparse_SOURCE_DIR}/pe-parser-library/include) + INTERFACE ${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library/build) target_include_directories(peparse SYSTEM - INTERFACE ${peparse_BUILD_DIR}) + INTERFACE ${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library/include) target_link_libraries(peparse INTERFACE pe-parse) @@ -104,5 +116,5 @@ target_compile_options(lsfg-vk-gen PRIVATE # required for peparse -Wno-unused-template # required for dxvk (yeah don't worry about it) - -Qunused-arguments -Wl,--unresolved-symbols=ignore-all + -Qunused-arguments -Wl,--unresolved-symbols=ignore-all1 ) From 533cc97a05cbfce15f0504eb5b566c3cd8e9dc1f Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 4 Jul 2025 15:16:45 +0200 Subject: [PATCH 069/253] docs: readme & license --- LICENSE.md | 21 +++++++++++++++++++++ README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 LICENSE.md create mode 100644 README.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b5c8a3e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +## MIT License + +Copyright (c) 2025 lsfg-vk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..51a7e0a --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# lsfg-vk +This project brings [Lossless Scaling's Frame Generation](https://store.steampowered.com/app/993090/Lossless_Scaling/) to Linux! + +## How does it work? +LSFG is primarily written in DirectX 11 compute shaders, which means we can use DXVK to translate it into SPIR-V. The surrounding parts have been rewritten in plain Vulkan code in order to make LSFG run natively on Linux. +By specifying an `LD_PRELOAD`, lsfg-vk can place itself inbetween your game and Vulkan. That way it can fetch frames from the game and insert its own frames without any problems. (Beware of anticheats please!) + +## Building, Installing and Running +In order to compile LSFG, make sure you have the following components installed on your system: +- Traditional build tools (+ bash, sed, git) +- Clang compiler (this project does NOT compile easily with GCC) +- Vulkan and SPIR-V header files +- CMake build system +- Meson build system (for DXVK) +- Ninja build system (backend for CMake) +- openssl's libcrypto + +Compiling lsfg-vk is relatively straight forward, as everything is neatly integrated into CMake: +```bash +$ CC=clang CXX=clang++ cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=absolute-install-path-here +$ cmake --build build +$ cmake --install build +``` +(Make sure you change `absolute-install-path-here` to the path you'd like to install this project to) + +Next, you'll need to download Lossless Scaling from Steam. Switch to the `legacy_3.1` branch or download the corresponding depot. +Copy or note down the path of "Lossless.dll" from the game files. + +Finally, let's actually start a program with frame generation enabled. I'm going to be using `vkcube` for this example: +```bash +LD_PRELOAD="/home/pancake/.lsfg-vk/liblsfg-vk.so" LSFG_DLL_PATH="/home/pancake/games/Lossless Scaling/Lossless.dll" LSFG_MULTIPLIER=4 vkcube +``` +Make sure you adjust the paths. Let's examine each one: +- `LD_PRELOAD`: This is how you let the loader know that you want to load lsfg-vk before anything else. This HAS to be either an ABSOLUTE path, or a path starting with `./` to make it relative. Specify `liblsfg-vk.so` here. +- `LSFG_DLL_PATH`: Here you specify the Lossless.dll you downloaded from Steam. This is either an absolute path, or a relative path from where you are running the app. +- `LSFG_MULTIPLIER`: This is the multiplier you should be familiar with. Specify `2` for doubling the framerate, etc. + +>[!WARNING] +> Unlike on Windows, LSFG_MULTIPLIER is heavily limited here (at the moment!). If your hardware can create 8 swapchain images, then setting LSFG_MULTIPLIER to 4 occupies 4 of those, leaving only 4 to the game. If the game requested 5 or more, it will crash. From bc5a63b8b762beb25c30bf973cb76b0bcff89910 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 4 Jul 2025 15:28:09 +0200 Subject: [PATCH 070/253] docs: correct branch name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 51a7e0a..ba3455f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ $ cmake --install build ``` (Make sure you change `absolute-install-path-here` to the path you'd like to install this project to) -Next, you'll need to download Lossless Scaling from Steam. Switch to the `legacy_3.1` branch or download the corresponding depot. +Next, you'll need to download Lossless Scaling from Steam. Switch to the `legacy_2.13` branch or download the corresponding depot. Copy or note down the path of "Lossless.dll" from the game files. Finally, let's actually start a program with frame generation enabled. I'm going to be using `vkcube` for this example: From 929c0e6fa612629c7bc3d0ec2fd1e85cb35ea565 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 4 Jul 2025 17:55:28 +0200 Subject: [PATCH 071/253] fix: use a readable log color --- include/log.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/log.hpp b/include/log.hpp index 7327eba..c249833 100644 --- a/include/log.hpp +++ b/include/log.hpp @@ -9,7 +9,7 @@ namespace Log { const std::string_view WHITE = "\033[1;37m"; const std::string_view YELLOW = "\033[1;33m"; const std::string_view RED = "\033[1;31m"; - const std::string_view GRAY = "\033[1;30m"; + const std::string_view GRAY = "\033[1;90m"; const std::string_view RESET = "\033[0m"; template From e3eae11e3264dbd0617c50ba8fd92420b4fd59e4 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 4 Jul 2025 17:55:42 +0200 Subject: [PATCH 072/253] fix: quick fix for non-dedicated gpus --- lsfg-vk-gen/src/core/device.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lsfg-vk-gen/src/core/device.cpp b/lsfg-vk-gen/src/core/device.cpp index 6c147bb..ebfbd3f 100644 --- a/lsfg-vk-gen/src/core/device.cpp +++ b/lsfg-vk-gen/src/core/device.cpp @@ -29,11 +29,10 @@ Device::Device(const Instance& instance) { for (const auto& device : devices) { VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(device, &properties); + physicalDevice = device; - if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { - physicalDevice = device; - break; - } + if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) + break; // dedicated will always work } if (!physicalDevice) throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No discrete GPU found"); From c8ed0750c759921cd774781c5d1095e1292db604 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 4 Jul 2025 17:57:13 +0200 Subject: [PATCH 073/253] fix: do not discard content on copy --- src/utils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils.cpp b/src/utils.cpp index 0bb0a0a..45e8b0e 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -56,6 +56,7 @@ void Utils::copyImage(VkCommandBuffer buf, const VkImageMemoryBarrier srcBarrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, .image = src, .subresourceRange = { From 9611a70ff9435c2776a6d3e312c006d21a4d8997 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 4 Jul 2025 19:10:32 +0200 Subject: [PATCH 074/253] fix: weird steam deck compiling thing --- lsfg-vk-gen/src/context.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lsfg-vk-gen/src/context.cpp b/lsfg-vk-gen/src/context.cpp index f7d1f82..b19e187 100644 --- a/lsfg-vk-gen/src/context.cpp +++ b/lsfg-vk-gen/src/context.cpp @@ -118,9 +118,10 @@ void Context::present(const Core::Device& device, int inSem, auto& info = this->renderInfos.at(this->frameIdx % 8); // 3. wait for completion of previous frame in this slot - for (auto& fence : info.completionFences.value_or({})) - if (!fence.wait(device, UINT64_MAX)) // should not take any time - throw vulkan_error(VK_ERROR_DEVICE_LOST, "Fence wait timed out"); + if (info.completionFences.has_value()) + for (auto& fence : *info.completionFences) + if (!fence.wait(device, UINT64_MAX)) // should not take any time + throw vulkan_error(VK_ERROR_DEVICE_LOST, "Fence wait timed out"); // 1. downsample and process input image info.inSemaphore = Core::Semaphore(device, inSem); From ccf71234c1a66a937762dadcabd32c577166dc50 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 4 Jul 2025 23:32:01 +0200 Subject: [PATCH 075/253] get rid of d3d11 device hopefully fixing steam deck compat while also not breaking anything --- CMakeLists.txt | 4 +- lsfg-vk-gen/CMakeLists.txt | 11 ++- lsfg-vk-gen/include/pool/extract.hpp | 37 ++-------- lsfg-vk-gen/include/pool/shaderpool.hpp | 5 +- lsfg-vk-gen/src/pool/extract.cpp | 80 +++++++-------------- lsfg-vk-gen/src/pool/shaderpool.cpp | 38 ++++------ lsfg-vk-gen/src/shaderchains/beta.cpp | 8 +-- lsfg-vk-gen/src/shaderchains/delta.cpp | 8 +-- lsfg-vk-gen/src/shaderchains/downsample.cpp | 8 +-- lsfg-vk-gen/src/shaderchains/epsilon.cpp | 8 +-- lsfg-vk-gen/src/shaderchains/extract.cpp | 8 +-- lsfg-vk-gen/src/shaderchains/gamma.cpp | 24 +++---- lsfg-vk-gen/src/shaderchains/magic.cpp | 8 +-- lsfg-vk-gen/src/shaderchains/merge.cpp | 8 +-- lsfg-vk-gen/src/shaderchains/zeta.cpp | 8 +-- 15 files changed, 99 insertions(+), 164 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f9edb9..533ff70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,13 +52,11 @@ target_compile_options(lsfg-vk PRIVATE -Wno-global-constructors # allow globals # required for vulkan -Wno-cast-function-type-strict - # required for dxvk (yeah don't worry about it) - -Qunused-arguments -Wl,--unresolved-symbols=ignore-all ) install(FILES "${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library/build/libpe-parse.so" DESTINATION lib) install(FILES "${CMAKE_BINARY_DIR}/dxvk/native/usr/lib/libdxvk_d3d11.so.0.20602" DESTINATION lib RENAME libdxvk_d3d11.so.0) install(FILES "${CMAKE_BINARY_DIR}/dxvk/native/usr/lib/libdxvk_dxgi.so.0.20602" DESTINATION lib RENAME libdxvk_dxgi.so.0) -install(FILES "${CMAKE_BINARY_DIR}/dxvk/libdxvkinternals.so" DESTINATION lib) +install(FILES "${CMAKE_BINARY_DIR}/dxvk/libdxbc.so" DESTINATION lib) install(FILES "${CMAKE_BINARY_DIR}/liblsfg-vk.so" DESTINATION lib) install(FILES "${CMAKE_BINARY_DIR}/lsfg-vk-gen/liblsfg-vk-gen.so" DESTINATION lib) diff --git a/lsfg-vk-gen/CMakeLists.txt b/lsfg-vk-gen/CMakeLists.txt index 4445665..0fb0a64 100644 --- a/lsfg-vk-gen/CMakeLists.txt +++ b/lsfg-vk-gen/CMakeLists.txt @@ -11,8 +11,8 @@ ExternalProject_Add(dxvk_git USES_TERMINAL_BUILD true CONFIGURE_COMMAND cd ${CMAKE_BINARY_DIR}/dxvk/src/dxvk_git && - sed -i s/private:// - src/dxvk/dxvk_device.h && + sed -i s/private://g + src/dxvk/dxvk_shader.h && CFLAGS=-w CXXFLAGS=-w CC=clang CXX=clang++ meson setup --buildtype "release" --prefix "${CMAKE_BINARY_DIR}/dxvk/native/usr" @@ -24,7 +24,7 @@ ExternalProject_Add(dxvk_git BUILD_COMMAND cd ${CMAKE_BINARY_DIR}/dxvk && ninja -C build install && # sorry cursed lol - echo "clang -shared -o libdxvkinternals.so $(find build/src/{dxbc,wsi,vulkan,dxvk,dxgi,spirv,util}/*.p -type f -name \\*.o)" > archive.sh && + echo "clang -shared -o libdxbc.so $(find build/src/{dxbc,dxvk,dxgi,spirv,util}/*.p -type f -name \\*.o)" > archive.sh && bash archive.sh INSTALL_COMMAND "" ) @@ -33,14 +33,13 @@ add_library(dxvk INTERFACE) add_dependencies(dxvk dxvk_git) target_link_directories(dxvk - INTERFACE ${CMAKE_BINARY_DIR}/dxvk/native/usr/lib INTERFACE ${CMAKE_BINARY_DIR}/dxvk) target_include_directories(dxvk SYSTEM INTERFACE ${CMAKE_BINARY_DIR}/dxvk/native/usr/include/dxvk INTERFACE ${CMAKE_BINARY_DIR}/dxvk/src/dxvk_git/src INTERFACE ${CMAKE_BINARY_DIR}/dxvk/src/dxvk_git/include/spirv/include) target_link_libraries(dxvk INTERFACE - dxvk_d3d11 dxvk_dxgi display-info dxvkinternals) + dxbc) # pe-parse subproject @@ -115,6 +114,4 @@ target_compile_options(lsfg-vk-gen PRIVATE -Wno-cast-function-type-strict # required for peparse -Wno-unused-template - # required for dxvk (yeah don't worry about it) - -Qunused-arguments -Wl,--unresolved-symbols=ignore-all1 ) diff --git a/lsfg-vk-gen/include/pool/extract.hpp b/lsfg-vk-gen/include/pool/extract.hpp index eaa0824..1a4a76a 100644 --- a/lsfg-vk-gen/include/pool/extract.hpp +++ b/lsfg-vk-gen/include/pool/extract.hpp @@ -48,37 +48,14 @@ namespace LSFG::Pool { }; /// - /// DirectX bytecode translator class. + /// Translate DXBC into SPIR-V. /// - class Translator { - public: - /// - /// Create a new translator. - /// - /// @throws std::runtime_error if the initialization fails. - /// - Translator(); - - /// - /// Translate DXBC into SPIR-V. - /// - /// @param dxbc Bytecode to translate. - /// @return Translated SPIR-V bytecode. - /// - /// @throws std::runtime_error if the translation fails. - /// - [[nodiscard]] std::vector translate(const std::vector& dxbc) const; - - // Trivially copyable, moveable and destructible - Translator(const Translator&) = delete; - Translator& operator=(const Translator&) = delete; - Translator(Translator&&) = default; - Translator& operator=(Translator&&) = default; - ~Translator() = default; - private: - std::shared_ptr device; - std::shared_ptr context; - }; + /// @param dxbc Bytecode to translate. + /// @return Translated SPIR-V bytecode. + /// + /// @throws std::runtime_error if the translation fails. + /// + [[nodiscard]] std::vector dxbcToSpirv(const std::vector& dxbc); } diff --git a/lsfg-vk-gen/include/pool/shaderpool.hpp b/lsfg-vk-gen/include/pool/shaderpool.hpp index 0612829..aeb5ba2 100644 --- a/lsfg-vk-gen/include/pool/shaderpool.hpp +++ b/lsfg-vk-gen/include/pool/shaderpool.hpp @@ -3,6 +3,7 @@ #include "core/device.hpp" #include "core/shadermodule.hpp" +#include "pool/extract.hpp" #include #include @@ -23,7 +24,7 @@ namespace LSFG::Pool { /// /// @throws std::runtime_error if the shader pool cannot be created. /// - ShaderPool(const std::string& path); + ShaderPool(const std::string& path) : extractor(path) {} /// /// Retrieve a shader module by name or create it. @@ -39,7 +40,7 @@ namespace LSFG::Pool { const Core::Device& device, const std::string& name, const std::vector>& types); private: - std::unordered_map> shaderBytecodes; + Extractor extractor; std::unordered_map shaders; }; diff --git a/lsfg-vk-gen/src/pool/extract.cpp b/lsfg-vk-gen/src/pool/extract.cpp index 2fa5698..84b0c70 100644 --- a/lsfg-vk-gen/src/pool/extract.cpp +++ b/lsfg-vk-gen/src/pool/extract.cpp @@ -1,7 +1,7 @@ #include "pool/extract.hpp" -#include -#include +#include +#include #include #include #include @@ -16,6 +16,7 @@ #include #include +using namespace LSFG; using namespace LSFG::Pool; namespace { @@ -64,64 +65,33 @@ std::vector Extractor::getResource(const std::string& hash) const { throw std::runtime_error("Resource not found: " + hash); } -Translator::Translator() { - setenv("DXVK_WSI_DRIVER", "SDL3", 0); - setenv("DXVK_LOG_LEVEL", "error", 0); - setenv("DXVK_LOG_PATH", "none", 0); +std::vector Pool::dxbcToSpirv(const std::vector& dxbc) { + // create sha1 hash of the dxbc data + std::array hash{}; + SHA1(dxbc.data(), dxbc.size(), hash.data()); + std::stringstream ss; + for (const auto& byte : hash) + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); + const std::string hash_str = ss.str(); - // create d3d11 device - ID3D11Device* device{}; - ID3D11DeviceContext* context{}; - const D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_1; - D3D11CreateDevice( - nullptr, - D3D_DRIVER_TYPE::D3D_DRIVER_TYPE_HARDWARE, - nullptr, - 0, - &featureLevel, 1, - D3D11_SDK_VERSION, - &device, nullptr, &context - ); - if (!device || !context) - throw std::runtime_error("Failed to create D3D11 device"); - - // store device in shared ptr - this->device = std::shared_ptr( - new ID3D11Device*(device), - [](ID3D11Device** dev) { - (*dev)->Release(); - } - ); - this->context = std::shared_ptr( - new ID3D11DeviceContext*(context), - [](ID3D11DeviceContext** ctx) { - (*ctx)->Release(); - } - ); -} - -std::vector Translator::translate(const std::vector& dxbc) const { - // create compute shader and pipeline - ID3D11ComputeShader* shader = nullptr; - (*this->device)->CreateComputeShader(dxbc.data(), dxbc.size(), nullptr, &shader); - if (!shader) - throw std::runtime_error("Failed to create compute shader from DXBC"); - - auto* dxvk_shader = reinterpret_cast(shader); - auto* dxvk_device = reinterpret_cast(*this->device); - - auto* pipeline = dxvk_device->GetDXVKDevice()->m_objects.pipelineManager().createComputePipeline({ - .cs = dxvk_shader->GetCommonShader()->GetShader() - }); + // compile the shader + dxvk::DxbcReader reader(reinterpret_cast(dxbc.data()), dxbc.size()); + dxvk::DxbcModule module(reader); + const dxvk::DxbcModuleInfo info{}; + auto shader = module.compile(info, "CS_" + hash_str); // extract spir-v from d3d11 shader - auto code = dxvk_shader->GetCommonShader()->GetShader()->getCode( - pipeline->getBindings(), dxvk::DxvkShaderModuleCreateInfo()); + auto code = shader->getRawCode(); + + // patch binding offsets +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + for (size_t i = 0; i < shader->m_bindingOffsets.size(); i++) + code.data()[shader->m_bindingOffsets.at(i).bindingOffset] = static_cast(i); // NOLINT +#pragma clang diagnostic pop + std::vector spirv(code.size()); std::copy_n(reinterpret_cast(code.data()), code.size(), spirv.data()); - - // cleanup-ish (i think the pipeline will linger) - shader->Release(); return spirv; } diff --git a/lsfg-vk-gen/src/pool/shaderpool.cpp b/lsfg-vk-gen/src/pool/shaderpool.cpp index 292ed23..d7fd22e 100644 --- a/lsfg-vk-gen/src/pool/shaderpool.cpp +++ b/lsfg-vk-gen/src/pool/shaderpool.cpp @@ -40,23 +40,6 @@ const std::unordered_map SHADERS = { { "zeta/4.spv", "lHYgyCpWnMIB74HL22BKQyoqUGvUjgR79W4vXFXzXe4=" } }; -ShaderPool::ShaderPool(const std::string& path) { - const Extractor extractor(path); - const Translator translator; - - for (const auto& [name, hash] : SHADERS) { - auto data = extractor.getResource(hash); - if (data.empty()) - throw std::runtime_error("Shader code is empty: " + name); - - auto code = translator.translate(data); - if (code.empty()) - throw std::runtime_error("Shader code translation failed: " + name); - - shaderBytecodes[name] = std::move(code); - } -} - Core::ShaderModule ShaderPool::getShader( const Core::Device& device, const std::string& name, const std::vector>& types) { @@ -64,13 +47,22 @@ Core::ShaderModule ShaderPool::getShader( if (it != shaders.end()) return it->second; - // create the shader module - auto cit = shaderBytecodes.find(name); - if (cit == shaderBytecodes.end()) - throw std::runtime_error("Shader code translation failed: " + name); - auto code = cit->second; + // grab the shader + auto hit = SHADERS.find(name); + if (hit == SHADERS.end()) + throw std::runtime_error("Shader not found: " + name); + auto hash = hit->second; - Core::ShaderModule shader(device, code, types); + auto dxbc = this->extractor.getResource(hash); + if (dxbc.empty()) + throw std::runtime_error("Shader code is empty: " + name); + + // create the translated shader module + auto spirv = dxbcToSpirv(dxbc); + if (spirv.empty()) + throw std::runtime_error("Shader code translation failed: " + name); + + Core::ShaderModule shader(device, spirv, types); shaders[name] = shader; return shader; } diff --git a/lsfg-vk-gen/src/shaderchains/beta.cpp b/lsfg-vk-gen/src/shaderchains/beta.cpp index 23346de..48294df 100644 --- a/lsfg-vk-gen/src/shaderchains/beta.cpp +++ b/lsfg-vk-gen/src/shaderchains/beta.cpp @@ -30,10 +30,10 @@ Beta::Beta(const Core::Device& device, Pool::ShaderPool& shaderpool, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), shaderpool.getShader(device, "beta/4.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) + { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; for (size_t i = 0; i < 5; i++) { this->pipelines.at(i) = Core::Pipeline(device, @@ -115,10 +115,10 @@ Beta::Beta(const Core::Device& device, Pool::ShaderPool& shaderpool, .build(); for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .build(); } } diff --git a/lsfg-vk-gen/src/shaderchains/delta.cpp b/lsfg-vk-gen/src/shaderchains/delta.cpp index 63e463e..efceb6d 100644 --- a/lsfg-vk-gen/src/shaderchains/delta.cpp +++ b/lsfg-vk-gen/src/shaderchains/delta.cpp @@ -24,10 +24,10 @@ Delta::Delta(const Core::Device& device, Pool::ShaderPool& shaderpool, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), shaderpool.getShader(device, "delta/3.spv", - { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; for (size_t i = 0; i < 4; i++) { this->pipelines.at(i) = Core::Pipeline(device, @@ -84,12 +84,12 @@ Delta::Delta(const Core::Device& device, Pool::ShaderPool& shaderpool, .build(); for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .build(); } } diff --git a/lsfg-vk-gen/src/shaderchains/downsample.cpp b/lsfg-vk-gen/src/shaderchains/downsample.cpp index c7e4ef2..acd028d 100644 --- a/lsfg-vk-gen/src/shaderchains/downsample.cpp +++ b/lsfg-vk-gen/src/shaderchains/downsample.cpp @@ -10,10 +10,10 @@ Downsample::Downsample(const Core::Device& device, Pool::ShaderPool& shaderpool, : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) { this->shaderModule = shaderpool.getShader(device, "downsample.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); + { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); this->pipeline = Core::Pipeline(device, this->shaderModule); for (size_t i = 0; i < 2; i++) this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModule); @@ -33,10 +33,10 @@ Downsample::Downsample(const Core::Device& device, Pool::ShaderPool& shaderpool, for (size_t fc = 0; fc < 2; fc++) { auto& inImg = (fc % 2 == 0) ? this->inImg_0 : this->inImg_1; this->descriptorSets.at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inImg) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) .build(); } } diff --git a/lsfg-vk-gen/src/shaderchains/epsilon.cpp b/lsfg-vk-gen/src/shaderchains/epsilon.cpp index 931596c..461d0e1 100644 --- a/lsfg-vk-gen/src/shaderchains/epsilon.cpp +++ b/lsfg-vk-gen/src/shaderchains/epsilon.cpp @@ -26,10 +26,10 @@ Epsilon::Epsilon(const Core::Device& device, Pool::ShaderPool& shaderpool, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), shaderpool.getShader(device, "epsilon/3.spv", - { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; for (size_t i = 0; i < 4; i++) { this->pipelines.at(i) = Core::Pipeline(device, @@ -85,13 +85,13 @@ Epsilon::Epsilon(const Core::Device& device, Pool::ShaderPool& shaderpool, .build(); for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .build(); } } diff --git a/lsfg-vk-gen/src/shaderchains/extract.cpp b/lsfg-vk-gen/src/shaderchains/extract.cpp index 8526f8d..56c7e0b 100644 --- a/lsfg-vk-gen/src/shaderchains/extract.cpp +++ b/lsfg-vk-gen/src/shaderchains/extract.cpp @@ -12,10 +12,10 @@ Extract::Extract(const Core::Device& device, Pool::ShaderPool& shaderpool, : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)) { this->shaderModule = shaderpool.getShader(device, "extract.spv", - { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); this->pipeline = Core::Pipeline(device, this->shaderModule); for (size_t i = 0; i < genc; i++) this->nDescriptorSets.emplace_back(device, pool, @@ -40,13 +40,13 @@ Extract::Extract(const Core::Device& device, Pool::ShaderPool& shaderpool, for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .build(); } diff --git a/lsfg-vk-gen/src/shaderchains/gamma.cpp b/lsfg-vk-gen/src/shaderchains/gamma.cpp index f7e746a..b60ed43 100644 --- a/lsfg-vk-gen/src/shaderchains/gamma.cpp +++ b/lsfg-vk-gen/src/shaderchains/gamma.cpp @@ -20,10 +20,10 @@ Gamma::Gamma(const Core::Device& device, Pool::ShaderPool& shaderpool, optImg2(std::move(optImg2)) { this->shaderModules = {{ shaderpool.getShader(device, "gamma/0.spv", - { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 10, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), shaderpool.getShader(device, "gamma/1.spv", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, @@ -37,15 +37,15 @@ Gamma::Gamma(const Core::Device& device, Pool::ShaderPool& shaderpool, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), shaderpool.getShader(device, "gamma/4.spv", - { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }), + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), shaderpool.getShader(device, "gamma/5.spv", - { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; for (size_t i = 0; i < 6; i++) { this->pipelines.at(i) = Core::Pipeline(device, @@ -122,6 +122,7 @@ Gamma::Gamma(const Core::Device& device, Pool::ShaderPool& shaderpool, } for (size_t i = 0; i < genc; i++) { this->nSpecialDescriptorSets.at(i).at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs1) @@ -131,7 +132,6 @@ Gamma::Gamma(const Core::Device& device, Pool::ShaderPool& shaderpool, .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)) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .build(); } } @@ -154,21 +154,21 @@ Gamma::Gamma(const Core::Device& device, Pool::ShaderPool& shaderpool, .build(); for (size_t i = 0; i < genc; i++) { this->n1DescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .build(); this->n2DescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->outImg1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .build(); } diff --git a/lsfg-vk-gen/src/shaderchains/magic.cpp b/lsfg-vk-gen/src/shaderchains/magic.cpp index a4e8d4b..f3ccd00 100644 --- a/lsfg-vk-gen/src/shaderchains/magic.cpp +++ b/lsfg-vk-gen/src/shaderchains/magic.cpp @@ -18,10 +18,10 @@ Magic::Magic(const Core::Device& device, Pool::ShaderPool& shaderpool, inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), optImg(std::move(optImg)) { this->shaderModule = shaderpool.getShader(device, "magic.spv", - { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4+4+2+1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 3+3+2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); + { 3+3+2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); this->pipeline = Core::Pipeline(device, this->shaderModule); for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.emplace_back(); @@ -68,6 +68,7 @@ Magic::Magic(const Core::Device& device, Pool::ShaderPool& shaderpool, } for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.at(i).at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs1) @@ -78,7 +79,6 @@ Magic::Magic(const Core::Device& device, Pool::ShaderPool& shaderpool, .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs3) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs2) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs1) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .build(); } } diff --git a/lsfg-vk-gen/src/shaderchains/merge.cpp b/lsfg-vk-gen/src/shaderchains/merge.cpp index e9866d2..10c9208 100644 --- a/lsfg-vk-gen/src/shaderchains/merge.cpp +++ b/lsfg-vk-gen/src/shaderchains/merge.cpp @@ -18,10 +18,10 @@ Merge::Merge(const Core::Device& device, Pool::ShaderPool& shaderpool, inImg4(std::move(inImg4)), inImg5(std::move(inImg5)) { this->shaderModule = shaderpool.getShader(device, "merge.spv", - { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }); + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); this->pipeline = Core::Pipeline(device, this->shaderModule); for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.emplace_back(); @@ -47,6 +47,7 @@ Merge::Merge(const Core::Device& device, Pool::ShaderPool& shaderpool, for (size_t fc = 0; fc < 2; fc++) { for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.at(i).at(fc).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg1 : this->inImg2) @@ -55,7 +56,6 @@ Merge::Merge(const Core::Device& device, Pool::ShaderPool& shaderpool, .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)) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) .build(); } } diff --git a/lsfg-vk-gen/src/shaderchains/zeta.cpp b/lsfg-vk-gen/src/shaderchains/zeta.cpp index 1824bb4..f33b85f 100644 --- a/lsfg-vk-gen/src/shaderchains/zeta.cpp +++ b/lsfg-vk-gen/src/shaderchains/zeta.cpp @@ -26,10 +26,10 @@ Zeta::Zeta(const Core::Device& device, Pool::ShaderPool& shaderpool, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), shaderpool.getShader(device, "zeta/3.spv", - { { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER } }) + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; for (size_t i = 0; i < 4; i++) { this->pipelines.at(i) = Core::Pipeline(device, @@ -85,13 +85,13 @@ Zeta::Zeta(const Core::Device& device, Pool::ShaderPool& shaderpool, .build(); for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.at(i).update(device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i) ) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i) ) .build(); } } From 9e207021808fd1d5b43359d484b13e9f2e9ad4c2 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 5 Jul 2025 00:22:06 +0200 Subject: [PATCH 076/253] get rid of libcrypto --- README.md | 1 - lsfg-vk-gen/CMakeLists.txt | 2 +- lsfg-vk-gen/include/pool/extract.hpp | 4 +- lsfg-vk-gen/src/pool/extract.cpp | 42 +++++++---------- lsfg-vk-gen/src/pool/shaderpool.cpp | 67 +++++++++++++--------------- 5 files changed, 52 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index ba3455f..b2d8e4a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ In order to compile LSFG, make sure you have the following components installed - CMake build system - Meson build system (for DXVK) - Ninja build system (backend for CMake) -- openssl's libcrypto Compiling lsfg-vk is relatively straight forward, as everything is neatly integrated into CMake: ```bash diff --git a/lsfg-vk-gen/CMakeLists.txt b/lsfg-vk-gen/CMakeLists.txt index 0fb0a64..21f5bb3 100644 --- a/lsfg-vk-gen/CMakeLists.txt +++ b/lsfg-vk-gen/CMakeLists.txt @@ -92,7 +92,7 @@ target_include_directories(lsfg-vk-gen PRIVATE include PUBLIC public) target_link_libraries(lsfg-vk-gen - PUBLIC vulkan peparse crypto dxvk) + PUBLIC vulkan peparse dxvk) set_target_properties(lsfg-vk-gen PROPERTIES CXX_CLANG_TIDY clang-tidy) target_compile_options(lsfg-vk-gen PRIVATE diff --git a/lsfg-vk-gen/include/pool/extract.hpp b/lsfg-vk-gen/include/pool/extract.hpp index 1a4a76a..53e4b67 100644 --- a/lsfg-vk-gen/include/pool/extract.hpp +++ b/lsfg-vk-gen/include/pool/extract.hpp @@ -35,7 +35,7 @@ namespace LSFG::Pool { /// /// @throws std::runtime_error if the resource is not found. /// - [[nodiscard]] std::vector getResource(const std::string& hash) const; + [[nodiscard]] std::vector getResource(uint32_t hash) const; // Trivially copyable, moveable and destructible Extractor(const Extractor&) = delete; @@ -44,7 +44,7 @@ namespace LSFG::Pool { Extractor& operator=(Extractor&&) = default; ~Extractor() = default; private: - std::unordered_map> resources; + std::unordered_map> resources; }; /// diff --git a/lsfg-vk-gen/src/pool/extract.cpp b/lsfg-vk-gen/src/pool/extract.cpp index 84b0c70..a7dc114 100644 --- a/lsfg-vk-gen/src/pool/extract.cpp +++ b/lsfg-vk-gen/src/pool/extract.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -21,28 +20,29 @@ using namespace LSFG::Pool; namespace { - using ResourceMap = std::unordered_map>; + using ResourceMap = std::unordered_map>; + + uint32_t fnv1a_hash(const std::vector& data) { + // does not need be secure + uint32_t hash = 0x811C9DC5; + for (auto byte : data) { + hash ^= byte; + hash *= 0x01000193; + } + return hash; + } /// Callback function for each resource. int on_resource(void* data, const peparse::resource& res) { if (res.type != peparse::RT_RCDATA || res.buf == nullptr || res.buf->bufLen <= 0) return 0; - - // hash the resource data - std::array hash{}; - SHA256(res.buf->buf, res.buf->bufLen, hash.data()); - - std::array base64{}; - const int base64_len = EVP_EncodeBlock(base64.data(), hash.data(), hash.size()); - const std::string hash_str(reinterpret_cast(base64.data()), - static_cast(base64_len)); - - // store the resource std::vector resource_data(res.buf->bufLen); std::copy_n(res.buf->buf, res.buf->bufLen, resource_data.data()); + const uint32_t hash = fnv1a_hash(resource_data); + auto* map = reinterpret_cast(data); - (*map)[hash_str] = resource_data; + (*map)[hash] = resource_data; return 0; } @@ -58,27 +58,19 @@ Extractor::Extractor(const std::string& path) { peparse::DestructParsedPE(pe); } -std::vector Extractor::getResource(const std::string& hash) const { +std::vector Extractor::getResource(uint32_t hash) const { auto it = this->resources.find(hash); if (it != this->resources.end()) return it->second; - throw std::runtime_error("Resource not found: " + hash); + throw std::runtime_error("Resource not found."); } std::vector Pool::dxbcToSpirv(const std::vector& dxbc) { - // create sha1 hash of the dxbc data - std::array hash{}; - SHA1(dxbc.data(), dxbc.size(), hash.data()); - std::stringstream ss; - for (const auto& byte : hash) - ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); - const std::string hash_str = ss.str(); - // compile the shader dxvk::DxbcReader reader(reinterpret_cast(dxbc.data()), dxbc.size()); dxvk::DxbcModule module(reader); const dxvk::DxbcModuleInfo info{}; - auto shader = module.compile(info, "CS_" + hash_str); + auto shader = module.compile(info, "CS"); // extract spir-v from d3d11 shader auto code = shader->getRawCode(); diff --git a/lsfg-vk-gen/src/pool/shaderpool.cpp b/lsfg-vk-gen/src/pool/shaderpool.cpp index d7fd22e..46dd7c1 100644 --- a/lsfg-vk-gen/src/pool/shaderpool.cpp +++ b/lsfg-vk-gen/src/pool/shaderpool.cpp @@ -1,43 +1,40 @@ #include "pool/shaderpool.hpp" -#include "pool/extract.hpp" - using namespace LSFG; using namespace LSFG::Pool; -const std::unordered_map SHADERS = { - { "alpha/0.spv", "lilEUr7nBgA8P6VSqCms09t9b+DZH6dVlcefVuFHlc8=" }, - { "alpha/1.spv", "2TRNidol3BNs/aeLl2/Om7z8bAlpehkOPVtmMao1q84=" }, - { "alpha/2.spv", "tP6qIJZhd4pGr1pop1e9ztW1gwp97ufQa2GaBZBYZJE=" }, - { "alpha/3.spv", "gA4ZejNp+RwtqjtTzGdGf5D/CjSGlwFB2nOgDAIv91k=" }, - { "beta/0.spv", "uQ/xsBMKRuJhbxstBukWMhXYuppPAYygxkb/3kNu4vI=" }, - { "beta/1.spv", "BUbrL9fZREXLlg1lmlTYD6n8DwpzHkho5bI3RLbfNJg=" }, - { "beta/2.spv", "bz0lxQjMYp6HLqw12X3jfV7H0SOZKrqUhgtw17WgTx4=" }, - { "beta/3.spv", "JA5/8p7yiiiCxmuiTsOR9Fb/z1qp8KlyU2wo9Wfpbcc=" }, - { "beta/4.spv", "/I+iYEwzOFylXZJWWNQ/oUT6SeLVnpotNXGV8y/FUVk=" }, - { "delta/0.spv", "gtBWy1WtP8NO+Z1sSPMgOJ75NaPEKvthc7imNGzJkGI=" }, - { "delta/1.spv", "JiqZZIoHay/uS1ptzlz3EWKUPct/jQHoFtN0qlEtVUU=" }, - { "delta/2.spv", "zkBa37GvAG8izeIv4o/3OowpxiobfOdNmPyVWl2BSWY=" }, - { "delta/3.spv", "neIMl/PCPonXqjtZykMb9tR4yW7JkZfMTqZPGOmJQUg=" }, - { "downsample.spv", "F9BppS+ytDjO3aoMEY7deMzLaSUhX8EuI+eH8654Fpw=" }, - { "epsilon/0.spv", "YHECg9LrgTCM8lABFOXkC5qTKoHsIMWnZ6ST3dexD08=" }, - { "epsilon/1.spv", "Uv7CfTi6x69c9Exuc16UqA7fvLTUGicHZVi5jhHKo0w=" }, - { "epsilon/2.spv", "0vmxxGRa6gbl5dqmKTBO9x/ZM2oEUJ5JtGfqcHhvouQ=" }, - { "epsilon/3.spv", "Sa/gkbCCDyCyUh8BSOa882t4qDc51oeP6+Kj3O3EaxM=" }, - { "extract.spv", "xKUdoEwFJDsc/kX/aY1FyzlMlOaJX4iHQLlthe2MvBs=" }, - { "gamma/0.spv", "AJuuF/X9NnypgBd89GbXMKcXC2meysYayiZQZwu3WhU=" }, - { "gamma/1.spv", "LLr/3D+whLd6XuKkBS7rlaNN+r8qB/Khr4ii+M7KSxY=" }, - { "gamma/2.spv", "KjHXdawBR8AMK7Kud/vXJmJTddXFKppREEpsykjwZDc=" }, - { "gamma/3.spv", "zAnAC73i76AJjv0o1To3bBu2jnIWXzX3NlSMvU3Lgxw=" }, - { "gamma/4.spv", "ivQ7ltprazBOXb46yxul9HJ5ByJk2LbG034cC6NkEpk=" }, - { "gamma/5.spv", "lHYgyCpWnMIB74HL22BKQyoqUGvUjgR79W4vXFXzXe4=" }, - { "magic.spv", "ZdoTjEhrlbAxq0MtaJyk6jQ5+hrySEsnvn+jseukAuI=" }, - { "merge.spv", "dnluf4IHKNaqz6WvH7qodn+fZ56ORx+w3MUOwH7huok=" }, - { "zeta/0.spv", "LLr/3D+whLd6XuKkBS7rlaNN+r8qB/Khr4ii+M7KSxY=" }, - { "zeta/1.spv", "KjHXdawBR8AMK7Kud/vXJmJTddXFKppREEpsykjwZDc=" }, - { "zeta/2.spv", "zAnAC73i76AJjv0o1To3bBu2jnIWXzX3NlSMvU3Lgxw=" }, - { "zeta/3.spv", "ivQ7ltprazBOXb46yxul9HJ5ByJk2LbG034cC6NkEpk=" }, - { "zeta/4.spv", "lHYgyCpWnMIB74HL22BKQyoqUGvUjgR79W4vXFXzXe4=" } +const std::unordered_map SHADERS = { + { "downsample.spv", 0xe365474d }, + { "alpha/0.spv", 0x35f63c83 }, + { "alpha/1.spv", 0x83e5240d }, + { "alpha/2.spv", 0x5d64d9f1 }, + { "alpha/3.spv", 0xad77afe1 }, + { "beta/0.spv", 0xa986ccbb }, + { "beta/1.spv", 0x60944cf5 }, + { "beta/2.spv", 0xb1c8f69b }, + { "beta/3.spv", 0x87cbe880 }, + { "beta/4.spv", 0xc2c5507d }, + { "gamma/0.spv", 0xccce9dab }, + { "gamma/1.spv", 0x7719e229 }, + { "gamma/2.spv", 0xfb1a7643 }, + { "gamma/3.spv", 0xe0553cd8 }, + { "gamma/4.spv", 0xf73c136f }, + { "gamma/5.spv", 0xa34959c }, + { "magic.spv", 0x443ea7a1 }, + { "delta/0.spv", 0x141daaac }, + { "delta/1.spv", 0x2a0ed691 }, + { "delta/2.spv", 0x23bdc583 }, + { "delta/3.spv", 0x52bc5e0f }, + { "epsilon/0.spv", 0x128eb7d7 }, + { "epsilon/1.spv", 0xbab811ad }, + { "epsilon/2.spv", 0x1d4b902d }, + { "epsilon/3.spv", 0x91236549 }, + { "zeta/0.spv", 0x7719e229 }, + { "zeta/1.spv", 0xfb1a7643 }, + { "zeta/2.spv", 0xe0553cd8 }, + { "zeta/3.spv", 0xf73c136f }, + { "extract.spv", 0xb6cb084a }, + { "merge.spv", 0xfc0aedfa } }; Core::ShaderModule ShaderPool::getShader( From b2e0f2cf11246ba0963ebfcc13be55a8a9c39547 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 5 Jul 2025 09:49:02 +0200 Subject: [PATCH 077/253] build mostly static and organize build files --- .gitignore | 1 + CMakeLists.txt | 19 +++------ README.md | 2 +- cmake/FetchDXVK.cmake | 74 +++++++++++++++++++++++++++++++++ cmake/FetchPeParse.cmake | 32 ++++++++++++++ lsfg-vk-gen/CMakeLists.txt | 85 ++------------------------------------ 6 files changed, 117 insertions(+), 96 deletions(-) create mode 100644 cmake/FetchDXVK.cmake create mode 100644 cmake/FetchPeParse.cmake diff --git a/.gitignore b/.gitignore index eb55056..43ab8ae 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /build # ide/lsp files +/.zed /.vscode /.clangd /.cache diff --git a/CMakeLists.txt b/CMakeLists.txt index 533ff70..7d2d495 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,26 +1,24 @@ cmake_minimum_required(VERSION 3.29) # cmake options - set(CMAKE_CXX_COMPILER clang++) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_SKIP_BUILD_RPATH FALSE) -set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) -set(CMAKE_INSTALL_RPATH "$\{ORIGIN\}") - set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -# main project +# subprojects +include(cmake/FetchDXVK.cmake) +include(cmake/FetchPeParse.cmake) +add_subdirectory(lsfg-vk-gen) + +# main project project(lsfg-vk VERSION 0.0.1 DESCRIPTION "lsfg-vk: LSFG on Linux through Vulkan" LANGUAGES CXX) -add_subdirectory(lsfg-vk-gen) - file(GLOB SOURCES "src/loader/*.cpp" "src/mini/*.cpp" @@ -54,9 +52,4 @@ target_compile_options(lsfg-vk PRIVATE -Wno-cast-function-type-strict ) -install(FILES "${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library/build/libpe-parse.so" DESTINATION lib) -install(FILES "${CMAKE_BINARY_DIR}/dxvk/native/usr/lib/libdxvk_d3d11.so.0.20602" DESTINATION lib RENAME libdxvk_d3d11.so.0) -install(FILES "${CMAKE_BINARY_DIR}/dxvk/native/usr/lib/libdxvk_dxgi.so.0.20602" DESTINATION lib RENAME libdxvk_dxgi.so.0) -install(FILES "${CMAKE_BINARY_DIR}/dxvk/libdxbc.so" DESTINATION lib) install(FILES "${CMAKE_BINARY_DIR}/liblsfg-vk.so" DESTINATION lib) -install(FILES "${CMAKE_BINARY_DIR}/lsfg-vk-gen/liblsfg-vk-gen.so" DESTINATION lib) diff --git a/README.md b/README.md index b2d8e4a..7bd5540 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ By specifying an `LD_PRELOAD`, lsfg-vk can place itself inbetween your game and In order to compile LSFG, make sure you have the following components installed on your system: - Traditional build tools (+ bash, sed, git) - Clang compiler (this project does NOT compile easily with GCC) -- Vulkan and SPIR-V header files +- Vulkan header files - CMake build system - Meson build system (for DXVK) - Ninja build system (backend for CMake) diff --git a/cmake/FetchDXVK.cmake b/cmake/FetchDXVK.cmake new file mode 100644 index 0000000..d26938f --- /dev/null +++ b/cmake/FetchDXVK.cmake @@ -0,0 +1,74 @@ +include(ExternalProject) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set(BUILD_TYPE "release") + set(STRIP_FLAG "--strip") +else() + set(BUILD_TYPE "debug") + set(STRIP_FLAG "") +endif() + +ExternalProject_Add(dxvk_git + GIT_REPOSITORY "https://github.com/doitsujin/dxvk" + GIT_TAG "v2.6.2" + UPDATE_DISCONNECTED true + USES_TERMINAL_CONFIGURE true + USES_TERMINAL_BUILD true + BUILD_IN_SOURCE true + CONFIGURE_COMMAND + sed -i s/private://g + src/dxvk/dxvk_shader.h && + CC=clang CXX=clang++ CFLAGS=-w CXXFLAGS=-w + meson setup + --buildtype ${BUILD_TYPE} + --prefix /build-native + ${STRIP_FLAG} + -Dbuild_id=false + --force-fallback-for=libdisplay-info + --wipe + build + BUILD_COMMAND + ninja -C build install && + mv build/src/dxvk/libdxvk.a build/src/dxvk/libldxvk.a && + cd build/src/dxgi/libdxvk_dxgi.so.0.20602.p && + ar rcs -o ../libdxgi.a + dxgi_adapter.cpp.o dxgi_enums.cpp.o dxgi_factory.cpp.o + dxgi_format.cpp.o dxgi_main.cpp.o dxgi_monitor.cpp.o + dxgi_options.cpp.o dxgi_output.cpp.o dxgi_surface.cpp.o + dxgi_swapchain.cpp.o && + cd ../../d3d11/libdxvk_d3d11.so.0.20602.p && + ar rcs -o ../libd3d11.a + d3d11_annotation.cpp.o d3d11_blend.cpp.o d3d11_buffer.cpp.o + d3d11_class_linkage.cpp.o d3d11_cmdlist.cpp.o d3d11_context.cpp.o + d3d11_context_def.cpp.o d3d11_context_ext.cpp.o d3d11_context_imm.cpp.o + d3d11_cuda.cpp.o d3d11_depth_stencil.cpp.o d3d11_device.cpp.o + d3d11_enums.cpp.o d3d11_features.cpp.o d3d11_fence.cpp.o + d3d11_gdi.cpp.o d3d11_initializer.cpp.o d3d11_input_layout.cpp.o + d3d11_interop.cpp.o d3d11_main.cpp.o d3d11_on_12.cpp.o + d3d11_options.cpp.o d3d11_query.cpp.o d3d11_rasterizer.cpp.o + d3d11_resource.cpp.o d3d11_sampler.cpp.o d3d11_shader.cpp.o + d3d11_state.cpp.o d3d11_state_object.cpp.o d3d11_swapchain.cpp.o + d3d11_texture.cpp.o d3d11_util.cpp.o d3d11_video.cpp.o + d3d11_view_dsv.cpp.o d3d11_view_rtv.cpp.o d3d11_view_srv.cpp.o + d3d11_view_uav.cpp.o + INSTALL_COMMAND "" +) + +ExternalProject_Get_Property(dxvk_git SOURCE_DIR) + +add_library(dxvk INTERFACE) +add_dependencies(dxvk dxvk_git) + +target_link_directories(dxvk + INTERFACE ${SOURCE_DIR}/build/src/dxvk + INTERFACE ${SOURCE_DIR}/build/src/dxbc + INTERFACE ${SOURCE_DIR}/build/src/dxgi + INTERFACE ${SOURCE_DIR}/build/src/d3d11 + INTERFACE ${SOURCE_DIR}/build/src/spirv + INTERFACE ${SOURCE_DIR}/build/src/util) +target_include_directories(dxvk SYSTEM + INTERFACE ${SOURCE_DIR}/build-native/include/dxvk + INTERFACE ${SOURCE_DIR}/src + INTERFACE ${SOURCE_DIR}/include/spirv/include) +target_link_libraries(dxvk INTERFACE + -Wl,--start-group dxgi d3d11 util ldxvk dxbc spirv -Wl,--end-group) diff --git a/cmake/FetchPeParse.cmake b/cmake/FetchPeParse.cmake new file mode 100644 index 0000000..df7879b --- /dev/null +++ b/cmake/FetchPeParse.cmake @@ -0,0 +1,32 @@ +include(ExternalProject) + +ExternalProject_Add(peparse_git + GIT_REPOSITORY "https://github.com/trailofbits/pe-parse" + GIT_TAG "v2.1.1" + UPDATE_DISCONNECTED true + USES_TERMINAL_CONFIGURE true + USES_TERMINAL_BUILD true + BUILD_IN_SOURCE true + CONFIGURE_COMMAND + cmake -S pe-parser-library -B build -G Ninja + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_C_COMPILER=clang + -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_C_FLAGS=-fPIC + -DCMAKE_CXX_FLAGS=-fPIC + BUILD_COMMAND + ninja -C build + INSTALL_COMMAND "" +) + +ExternalProject_Get_Property(peparse_git SOURCE_DIR) + +add_library(peparse INTERFACE) +add_dependencies(peparse peparse_git) + +target_link_directories(peparse + INTERFACE ${SOURCE_DIR}/build) +target_include_directories(peparse SYSTEM + INTERFACE ${SOURCE_DIR}/pe-parser-library/include) +target_link_libraries(peparse + INTERFACE pe-parse) diff --git a/lsfg-vk-gen/CMakeLists.txt b/lsfg-vk-gen/CMakeLists.txt index 21f5bb3..3c6faa1 100644 --- a/lsfg-vk-gen/CMakeLists.txt +++ b/lsfg-vk-gen/CMakeLists.txt @@ -1,83 +1,4 @@ -# dxvk subproject - -include(ExternalProject) - -ExternalProject_Add(dxvk_git - PREFIX ${CMAKE_BINARY_DIR}/dxvk - GIT_REPOSITORY "https://github.com/doitsujin/dxvk" - GIT_TAG "v2.6.2" - UPDATE_DISCONNECTED true - USES_TERMINAL_CONFIGURE true - USES_TERMINAL_BUILD true - CONFIGURE_COMMAND - cd ${CMAKE_BINARY_DIR}/dxvk/src/dxvk_git && - sed -i s/private://g - src/dxvk/dxvk_shader.h && - CFLAGS=-w CXXFLAGS=-w CC=clang CXX=clang++ meson setup - --buildtype "release" - --prefix "${CMAKE_BINARY_DIR}/dxvk/native/usr" - --strip - -Dbuild_id=false - --force-fallback-for=libdisplay-info - --wipe - ${CMAKE_BINARY_DIR}/dxvk/build - BUILD_COMMAND - cd ${CMAKE_BINARY_DIR}/dxvk && - ninja -C build install && # sorry cursed lol - echo "clang -shared -o libdxbc.so $(find build/src/{dxbc,dxvk,dxgi,spirv,util}/*.p -type f -name \\*.o)" > archive.sh && - bash archive.sh - INSTALL_COMMAND "" -) - -add_library(dxvk INTERFACE) -add_dependencies(dxvk dxvk_git) - -target_link_directories(dxvk - INTERFACE ${CMAKE_BINARY_DIR}/dxvk) -target_include_directories(dxvk SYSTEM - INTERFACE ${CMAKE_BINARY_DIR}/dxvk/native/usr/include/dxvk - INTERFACE ${CMAKE_BINARY_DIR}/dxvk/src/dxvk_git/src - INTERFACE ${CMAKE_BINARY_DIR}/dxvk/src/dxvk_git/include/spirv/include) -target_link_libraries(dxvk INTERFACE - dxbc) - -# pe-parse subproject - -ExternalProject_Add(peparse_git - PREFIX ${CMAKE_BINARY_DIR}/peparse - GIT_REPOSITORY "https://github.com/trailofbits/pe-parse" - GIT_TAG "v2.1.1" - UPDATE_DISCONNECTED true - USES_TERMINAL_CONFIGURE true - USES_TERMINAL_BUILD true - CONFIGURE_COMMAND - cd ${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library && - CC=clang CXX=clang++ CXXFLAGS=-Wno-deprecated-declarations - cmake -B build -G Ninja - -DCMAKE_BUILD_TYPE=Release - -DBUILD_SHARED_LIBS=1 - BUILD_COMMAND - cd ${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library && - ninja -C build - INSTALL_COMMAND "" -) - -add_library(peparse INTERFACE) -add_dependencies(peparse peparse_git) - -target_link_directories(peparse - INTERFACE ${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library/build) -target_include_directories(peparse SYSTEM - INTERFACE ${CMAKE_BINARY_DIR}/peparse/src/peparse_git/pe-parser-library/include) -target_link_libraries(peparse - INTERFACE pe-parse) - -# main project - -project(lsfg-vk-gen - VERSION 0.0.1 - DESCRIPTION "lsfg-vk-gen: LSFG on Linux through Vulkan (backend)" - LANGUAGES CXX) +project(lsfg-vk-gen LANGUAGES CXX) file(GLOB BACKEND_SOURCES "src/core/*.cpp" @@ -86,7 +7,7 @@ file(GLOB BACKEND_SOURCES "src/*.cpp" ) -add_library(lsfg-vk-gen SHARED ${BACKEND_SOURCES}) +add_library(lsfg-vk-gen STATIC ${BACKEND_SOURCES}) target_include_directories(lsfg-vk-gen PRIVATE include @@ -96,7 +17,7 @@ target_link_libraries(lsfg-vk-gen set_target_properties(lsfg-vk-gen PROPERTIES CXX_CLANG_TIDY clang-tidy) target_compile_options(lsfg-vk-gen PRIVATE - -Weverything + -Weverything -fPIC # disable compat c++ flags -Wno-pre-c++20-compat-pedantic -Wno-pre-c++17-compat From c2aeb2cbaa3cffd9618a2146cd0eccd6a4588c05 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 5 Jul 2025 11:46:12 +0200 Subject: [PATCH 078/253] fix release build and update docs --- CMakeLists.txt | 5 +++-- README.md | 10 ++++++---- cmake/FetchDXVK.cmake | 21 ++------------------- lsfg-vk-gen/CMakeLists.txt | 2 -- 4 files changed, 11 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d2d495..0444e79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,12 @@ cmake_minimum_required(VERSION 3.29) # cmake options set(CMAKE_CXX_COMPILER clang++) +set(CMAKE_CXX_CLANG_TIDY clang-tidy) + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_SKIP_RPATH ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # subprojects @@ -31,8 +34,6 @@ target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk PRIVATE lsfg-vk-gen vulkan) -set_target_properties(lsfg-vk - PROPERTIES CXX_CLANG_TIDY clang-tidy) target_compile_options(lsfg-vk PRIVATE -Weverything # disable compat c++ flags diff --git a/README.md b/README.md index 7bd5540..504dd0a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ By specifying an `LD_PRELOAD`, lsfg-vk can place itself inbetween your game and ## Building, Installing and Running In order to compile LSFG, make sure you have the following components installed on your system: -- Traditional build tools (+ bash, sed, git) +- Traditional build tools (+ sed, git) - Clang compiler (this project does NOT compile easily with GCC) - Vulkan header files - CMake build system @@ -16,13 +16,15 @@ In order to compile LSFG, make sure you have the following components installed Compiling lsfg-vk is relatively straight forward, as everything is neatly integrated into CMake: ```bash -$ CC=clang CXX=clang++ cmake -B build -G Ninja \ +$ cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=absolute-install-path-here + -DCMAKE_INSTALL_PREFIX=$PWD/release \ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \ + -DCMAKE_CXX_CLANG_TIDY="" $ cmake --build build $ cmake --install build ``` -(Make sure you change `absolute-install-path-here` to the path you'd like to install this project to) +(Make sure you change `$PWD/release` to the path you'd like to install this project to) Next, you'll need to download Lossless Scaling from Steam. Switch to the `legacy_2.13` branch or download the corresponding depot. Copy or note down the path of "Lossless.dll" from the game files. diff --git a/cmake/FetchDXVK.cmake b/cmake/FetchDXVK.cmake index d26938f..b51b180 100644 --- a/cmake/FetchDXVK.cmake +++ b/cmake/FetchDXVK.cmake @@ -31,26 +31,9 @@ ExternalProject_Add(dxvk_git ninja -C build install && mv build/src/dxvk/libdxvk.a build/src/dxvk/libldxvk.a && cd build/src/dxgi/libdxvk_dxgi.so.0.20602.p && - ar rcs -o ../libdxgi.a - dxgi_adapter.cpp.o dxgi_enums.cpp.o dxgi_factory.cpp.o - dxgi_format.cpp.o dxgi_main.cpp.o dxgi_monitor.cpp.o - dxgi_options.cpp.o dxgi_output.cpp.o dxgi_surface.cpp.o - dxgi_swapchain.cpp.o && + bash -c "ar rcs -o ../libdxgi.a *.o" && cd ../../d3d11/libdxvk_d3d11.so.0.20602.p && - ar rcs -o ../libd3d11.a - d3d11_annotation.cpp.o d3d11_blend.cpp.o d3d11_buffer.cpp.o - d3d11_class_linkage.cpp.o d3d11_cmdlist.cpp.o d3d11_context.cpp.o - d3d11_context_def.cpp.o d3d11_context_ext.cpp.o d3d11_context_imm.cpp.o - d3d11_cuda.cpp.o d3d11_depth_stencil.cpp.o d3d11_device.cpp.o - d3d11_enums.cpp.o d3d11_features.cpp.o d3d11_fence.cpp.o - d3d11_gdi.cpp.o d3d11_initializer.cpp.o d3d11_input_layout.cpp.o - d3d11_interop.cpp.o d3d11_main.cpp.o d3d11_on_12.cpp.o - d3d11_options.cpp.o d3d11_query.cpp.o d3d11_rasterizer.cpp.o - d3d11_resource.cpp.o d3d11_sampler.cpp.o d3d11_shader.cpp.o - d3d11_state.cpp.o d3d11_state_object.cpp.o d3d11_swapchain.cpp.o - d3d11_texture.cpp.o d3d11_util.cpp.o d3d11_video.cpp.o - d3d11_view_dsv.cpp.o d3d11_view_rtv.cpp.o d3d11_view_srv.cpp.o - d3d11_view_uav.cpp.o + bash -c "ar rcs -o ../libd3d11.a *.o .*.o" INSTALL_COMMAND "" ) diff --git a/lsfg-vk-gen/CMakeLists.txt b/lsfg-vk-gen/CMakeLists.txt index 3c6faa1..580c36b 100644 --- a/lsfg-vk-gen/CMakeLists.txt +++ b/lsfg-vk-gen/CMakeLists.txt @@ -14,8 +14,6 @@ target_include_directories(lsfg-vk-gen PUBLIC public) target_link_libraries(lsfg-vk-gen PUBLIC vulkan peparse dxvk) -set_target_properties(lsfg-vk-gen - PROPERTIES CXX_CLANG_TIDY clang-tidy) target_compile_options(lsfg-vk-gen PRIVATE -Weverything -fPIC # disable compat c++ flags From 7dfcfe252f720a18c5220184f660cff740ed4e5a Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 5 Jul 2025 15:24:54 +0200 Subject: [PATCH 079/253] move utils to subdir --- CMakeLists.txt | 2 +- include/{ => utils}/log.hpp | 0 include/{ => utils}/utils.hpp | 2 +- lsfg-vk-gen/CMakeLists.txt | 1 + lsfg-vk-gen/include/{ => utils}/utils.hpp | 0 lsfg-vk-gen/src/lsfg.cpp | 2 +- lsfg-vk-gen/src/shaderchains/alpha.cpp | 2 +- lsfg-vk-gen/src/shaderchains/beta.cpp | 2 +- lsfg-vk-gen/src/shaderchains/delta.cpp | 2 +- lsfg-vk-gen/src/shaderchains/downsample.cpp | 2 +- lsfg-vk-gen/src/shaderchains/epsilon.cpp | 2 +- lsfg-vk-gen/src/shaderchains/extract.cpp | 2 +- lsfg-vk-gen/src/shaderchains/gamma.cpp | 2 +- lsfg-vk-gen/src/shaderchains/magic.cpp | 2 +- lsfg-vk-gen/src/shaderchains/merge.cpp | 2 +- lsfg-vk-gen/src/shaderchains/zeta.cpp | 2 +- lsfg-vk-gen/src/{ => utils}/utils.cpp | 2 +- src/{ => utils}/utils.cpp | 5 +++-- 18 files changed, 18 insertions(+), 16 deletions(-) rename include/{ => utils}/log.hpp (100%) rename include/{ => utils}/utils.hpp (100%) rename lsfg-vk-gen/include/{ => utils}/utils.hpp (100%) rename lsfg-vk-gen/src/{ => utils}/utils.cpp (99%) rename src/{ => utils}/utils.cpp (98%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0444e79..a29f9a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ project(lsfg-vk LANGUAGES CXX) file(GLOB SOURCES - "src/loader/*.cpp" + "src/utils/*.cpp" "src/mini/*.cpp" "src/*.cpp" ) diff --git a/include/log.hpp b/include/utils/log.hpp similarity index 100% rename from include/log.hpp rename to include/utils/log.hpp diff --git a/include/utils.hpp b/include/utils/utils.hpp similarity index 100% rename from include/utils.hpp rename to include/utils/utils.hpp index 136d50f..0989e2d 100644 --- a/include/utils.hpp +++ b/include/utils/utils.hpp @@ -1,9 +1,9 @@ #ifndef UTILS_HPP #define UTILS_HPP -#include #include +#include #include namespace Utils { diff --git a/lsfg-vk-gen/CMakeLists.txt b/lsfg-vk-gen/CMakeLists.txt index 580c36b..592d809 100644 --- a/lsfg-vk-gen/CMakeLists.txt +++ b/lsfg-vk-gen/CMakeLists.txt @@ -4,6 +4,7 @@ file(GLOB BACKEND_SOURCES "src/core/*.cpp" "src/pool/*.cpp" "src/shaderchains/*.cpp" + "src/utils/*.cpp" "src/*.cpp" ) diff --git a/lsfg-vk-gen/include/utils.hpp b/lsfg-vk-gen/include/utils/utils.hpp similarity index 100% rename from lsfg-vk-gen/include/utils.hpp rename to lsfg-vk-gen/include/utils/utils.hpp diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp index f3fcab9..5f39ea4 100644 --- a/lsfg-vk-gen/src/lsfg.cpp +++ b/lsfg-vk-gen/src/lsfg.cpp @@ -3,7 +3,7 @@ #include "core/instance.hpp" #include "context.hpp" #include "pool/shaderpool.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" #include #include diff --git a/lsfg-vk-gen/src/shaderchains/alpha.cpp b/lsfg-vk-gen/src/shaderchains/alpha.cpp index 8f525ca..61f75d3 100644 --- a/lsfg-vk-gen/src/shaderchains/alpha.cpp +++ b/lsfg-vk-gen/src/shaderchains/alpha.cpp @@ -1,5 +1,5 @@ #include "shaderchains/alpha.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" using namespace LSFG::Shaderchains; diff --git a/lsfg-vk-gen/src/shaderchains/beta.cpp b/lsfg-vk-gen/src/shaderchains/beta.cpp index 48294df..a9e012f 100644 --- a/lsfg-vk-gen/src/shaderchains/beta.cpp +++ b/lsfg-vk-gen/src/shaderchains/beta.cpp @@ -1,5 +1,5 @@ #include "shaderchains/beta.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" using namespace LSFG::Shaderchains; diff --git a/lsfg-vk-gen/src/shaderchains/delta.cpp b/lsfg-vk-gen/src/shaderchains/delta.cpp index efceb6d..333492d 100644 --- a/lsfg-vk-gen/src/shaderchains/delta.cpp +++ b/lsfg-vk-gen/src/shaderchains/delta.cpp @@ -1,5 +1,5 @@ #include "shaderchains/delta.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" using namespace LSFG::Shaderchains; diff --git a/lsfg-vk-gen/src/shaderchains/downsample.cpp b/lsfg-vk-gen/src/shaderchains/downsample.cpp index acd028d..7af81fe 100644 --- a/lsfg-vk-gen/src/shaderchains/downsample.cpp +++ b/lsfg-vk-gen/src/shaderchains/downsample.cpp @@ -1,5 +1,5 @@ #include "shaderchains/downsample.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" using namespace LSFG::Shaderchains; diff --git a/lsfg-vk-gen/src/shaderchains/epsilon.cpp b/lsfg-vk-gen/src/shaderchains/epsilon.cpp index 461d0e1..59c87d7 100644 --- a/lsfg-vk-gen/src/shaderchains/epsilon.cpp +++ b/lsfg-vk-gen/src/shaderchains/epsilon.cpp @@ -1,5 +1,5 @@ #include "shaderchains/epsilon.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" using namespace LSFG::Shaderchains; diff --git a/lsfg-vk-gen/src/shaderchains/extract.cpp b/lsfg-vk-gen/src/shaderchains/extract.cpp index 56c7e0b..8251bd8 100644 --- a/lsfg-vk-gen/src/shaderchains/extract.cpp +++ b/lsfg-vk-gen/src/shaderchains/extract.cpp @@ -1,5 +1,5 @@ #include "shaderchains/extract.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" using namespace LSFG::Shaderchains; diff --git a/lsfg-vk-gen/src/shaderchains/gamma.cpp b/lsfg-vk-gen/src/shaderchains/gamma.cpp index b60ed43..66ba284 100644 --- a/lsfg-vk-gen/src/shaderchains/gamma.cpp +++ b/lsfg-vk-gen/src/shaderchains/gamma.cpp @@ -1,5 +1,5 @@ #include "shaderchains/gamma.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" using namespace LSFG::Shaderchains; diff --git a/lsfg-vk-gen/src/shaderchains/magic.cpp b/lsfg-vk-gen/src/shaderchains/magic.cpp index f3ccd00..1b74a35 100644 --- a/lsfg-vk-gen/src/shaderchains/magic.cpp +++ b/lsfg-vk-gen/src/shaderchains/magic.cpp @@ -1,5 +1,5 @@ #include "shaderchains/magic.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" using namespace LSFG::Shaderchains; diff --git a/lsfg-vk-gen/src/shaderchains/merge.cpp b/lsfg-vk-gen/src/shaderchains/merge.cpp index 10c9208..6b17bdb 100644 --- a/lsfg-vk-gen/src/shaderchains/merge.cpp +++ b/lsfg-vk-gen/src/shaderchains/merge.cpp @@ -1,5 +1,5 @@ #include "shaderchains/merge.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" using namespace LSFG::Shaderchains; diff --git a/lsfg-vk-gen/src/shaderchains/zeta.cpp b/lsfg-vk-gen/src/shaderchains/zeta.cpp index f33b85f..a5350a8 100644 --- a/lsfg-vk-gen/src/shaderchains/zeta.cpp +++ b/lsfg-vk-gen/src/shaderchains/zeta.cpp @@ -1,5 +1,5 @@ #include "shaderchains/zeta.hpp" -#include "utils.hpp" +#include "utils/utils.hpp" using namespace LSFG::Shaderchains; diff --git a/lsfg-vk-gen/src/utils.cpp b/lsfg-vk-gen/src/utils/utils.cpp similarity index 99% rename from lsfg-vk-gen/src/utils.cpp rename to lsfg-vk-gen/src/utils/utils.cpp index 60a4274..5765ea5 100644 --- a/lsfg-vk-gen/src/utils.cpp +++ b/lsfg-vk-gen/src/utils/utils.cpp @@ -1,4 +1,4 @@ -#include "utils.hpp" +#include "utils/utils.hpp" #include "core/buffer.hpp" #include "lsfg.hpp" diff --git a/src/utils.cpp b/src/utils/utils.cpp similarity index 98% rename from src/utils.cpp rename to src/utils/utils.cpp index 45e8b0e..ed5acad 100644 --- a/src/utils.cpp +++ b/src/utils/utils.cpp @@ -1,4 +1,4 @@ -#include "utils.hpp" +#include "utils/utils.hpp" #include @@ -15,7 +15,8 @@ std::pair Utils::findQueue(VkDevice device, VkPhysicalDevice uint32_t familyCount{}; vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr); std::vector families(familyCount); - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, families.data()); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, + families.data()); std::optional idx; for (const auto& queueInfo : enabledQueues) { From 614457fab7c509cbce19a520e71c83d002a19fcc Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 5 Jul 2025 16:03:00 +0200 Subject: [PATCH 080/253] move to vulkan layer --- VkLayer_LS_frame_generation.json | 15 + include/hooks.hpp | 8 +- include/layer.hpp | 214 +++++++++++++ include/loader/dl.hpp | 133 -------- include/loader/vk.hpp | 58 ---- include/utils/utils.hpp | 10 + src/context.cpp | 13 +- src/hooks.cpp | 98 +++--- src/init.cpp | 27 -- src/layer.cpp | 527 +++++++++++++++++++++++++++++++ src/loader/dl.cpp | 182 ----------- src/loader/vk.cpp | 109 ------- src/mini/commandbuffer.cpp | 11 +- src/mini/commandpool.cpp | 5 +- src/mini/image.cpp | 20 +- src/mini/semaphore.cpp | 14 +- src/utils/utils.cpp | 37 ++- 17 files changed, 876 insertions(+), 605 deletions(-) create mode 100644 VkLayer_LS_frame_generation.json create mode 100644 include/layer.hpp delete mode 100644 include/loader/dl.hpp delete mode 100644 include/loader/vk.hpp delete mode 100644 src/init.cpp create mode 100644 src/layer.cpp delete mode 100644 src/loader/dl.cpp delete mode 100644 src/loader/vk.cpp diff --git a/VkLayer_LS_frame_generation.json b/VkLayer_LS_frame_generation.json new file mode 100644 index 0000000..e7040ff --- /dev/null +++ b/VkLayer_LS_frame_generation.json @@ -0,0 +1,15 @@ +{ + "file_format_version": "1.0.0", + "layer": { + "name": "VK_LAYER_LS_frame_generation", + "type": "GLOBAL", + "api_version": "1.4.313", + "library_path": "./build/liblsfg-vk.so", + "implementation_version": "1", + "description": "Lossless Scaling frame generation layer", + "functions": { + "vkGetInstanceProcAddr": "layer_vkGetInstanceProcAddr", + "vkGetDeviceProcAddr": "layer_vkGetDeviceProcAddr" + } + } +} diff --git a/include/hooks.hpp b/include/hooks.hpp index 0758e21..8e66dc5 100644 --- a/include/hooks.hpp +++ b/include/hooks.hpp @@ -3,7 +3,9 @@ #include +#include #include +#include namespace Hooks { @@ -15,10 +17,8 @@ namespace Hooks { uint64_t frameGen; // amount of frames to generate }; - /// - /// Install overrides for hooked Vulkan functions. - /// - void initialize(); + /// Map of hooked Vulkan functions. + extern std::unordered_map hooks; } diff --git a/include/layer.hpp b/include/layer.hpp new file mode 100644 index 0000000..e43b265 --- /dev/null +++ b/include/layer.hpp @@ -0,0 +1,214 @@ +#ifndef LAYER_HPP +#define LAYER_HPP + +#include + +namespace Layer { + /// Call to the original vkCreateInstance function. + VkResult ovkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance); + /// Call to the original vkDestroyInstance function. + void ovkDestroyInstance( + VkInstance instance, + const VkAllocationCallbacks* pAllocator); + + /// Call to the original vkCreateDevice function. + VkResult ovkCreateDevice( + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice); + /// Call to the original vkDestroyDevice function. + void ovkDestroyDevice( + VkDevice device, + const VkAllocationCallbacks* pAllocator); + + /// Call to the original 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 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 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 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 vkCmdPipelineBarrier2 function. + void ovkCmdPipelineBarrier2( + VkCommandBuffer commandBuffer, + const VkDependencyInfo* pDependencyInfo); + /// Call to the original vkCmdCopyImage function. + void ovkCmdCopyImage( + VkCommandBuffer commandBuffer, + VkImage srcImage, + VkImageLayout srcImageLayout, + VkImage dstImage, + VkImageLayout dstImageLayout, + uint32_t regionCount, + const VkImageCopy* pRegions); + + /// 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); + +#endif // LAYER_HPP diff --git a/include/loader/dl.hpp b/include/loader/dl.hpp deleted file mode 100644 index 6ceb44d..0000000 --- a/include/loader/dl.hpp +++ /dev/null @@ -1,133 +0,0 @@ -#ifndef DL_HPP -#define DL_HPP - -#include -#include - -// -// This dynamic loader replaces the standard dlopen, dlsym, and dlclose functions. -// On initialize, the original functions are obtained via dlvsym (glibc exclusive) -// and made available under functions with the "o" prefix. -// -// Any call to regular dlopen, dlsym or dlclose is intercepted and may be -// overriden by registering a File override via `Loader::DL::registerFile`. -// - -namespace Loader::DL { - - /// Dynamic loader override structure. - class File { - public: - /// - /// Create a dynamic loader override for a specific file. - /// - /// @param filename The name of the file to override. - /// - File(std::string filename) - : filename(std::move(filename)) {} - - /// - /// Append a symbol to the dynamic loader override. - /// - /// @param symbol The name of the symbol to add. - /// @param address The address of the symbol. - /// - void defineSymbol(const std::string& symbol, void* address) { - symbols[symbol] = address; - } - - /// Get the filename - [[nodiscard]] const std::string& getFilename() const { return filename; } - /// Get all overriden symbols - [[nodiscard]] const std::unordered_map& getSymbols() const { return symbols; } - - // Find a specific symbol - [[nodiscard]] void* findSymbol(const std::string& symbol) const { - auto it = symbols.find(symbol); - return (it != symbols.end()) ? it->second : nullptr; - } - - /// Get the fake handle - [[nodiscard]] void* getHandle() const { return handle; } - /// Get the real handle - [[nodiscard]] void* getOriginalHandle() const { return handle_orig; } - - /// Set the fake handle - void setHandle(void* new_handle) { handle = new_handle; } - /// Set the real handle - void setOriginalHandle(void* new_handle) { handle_orig = new_handle; } - - /// Copyable, moveable, default destructor - File(const File&) = default; - File(File&&) = default; - File& operator=(const File&) = default; - File& operator=(File&&) = default; - ~File() = default; - private: - std::string filename; - std::unordered_map symbols; - - void* handle = nullptr; - void* handle_orig = nullptr; - }; - - /// - /// Initialize the dynamic loader - /// - void initialize(); - - /// - /// Register a file with the dynamic loader. - /// - /// @param file The file to register. - /// - void registerFile(const File& file); - - /// - /// Disable hooks temporarily. This may be useful - /// when loading third-party libraries you wish not - /// to hook. - /// - void disableHooks(); - - /// - /// Re-enable hooks after they were disabled. - /// - void enableHooks(); - - /// - /// Call the original dlopen function. - /// - /// @param filename The name of the file to open. - /// @param flag The flags to use when opening the file. - /// @return A handle to the opened file, or NULL on failure. - /// - void* odlopen(const char* filename, int flag); - - /// - /// Call the original dlsym function. - /// - /// @param handle The handle to the opened file. - /// @param symbol The name of the symbol to look up. - /// @return A pointer to the symbol, or NULL on failure. - /// - void* odlsym(void* handle, const char* symbol); - - /// - /// Call the original dlclose function. - /// - /// @param handle The handle to the opened file. - /// @return 0 on success, or -1 on failure. - /// - int odlclose(void* handle); - -} - -/// Modified version of the dlopen function. -extern "C" void* dlopen(const char* filename, int flag) noexcept; -/// Modified version of the dlsym function. -extern "C" void* dlsym(void* handle, const char* symbol) noexcept; -/// Modified version of the dlclose function. -extern "C" int dlclose(void* handle) noexcept; - -#endif // DL_HPP diff --git a/include/loader/vk.hpp b/include/loader/vk.hpp deleted file mode 100644 index 38d6338..0000000 --- a/include/loader/vk.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef VK_HPP -#define VK_HPP - -#include - -#include - -// -// Similar to the dynamic loader, the Vulkan loader replaces the standard -// vkGetInstanceProcAddr and vkGetDeviceProcAddr functions. -// -// One thing that should be noted, is that not every application uses the -// Vulkan loader for every method. On Linux it's not unusual to see dlsym -// being used for vulkan functions, so make sure to register the same -// symbol on both loaders. -// - -namespace Loader::VK { - - /// - /// Initialize the Vulkan loader. - /// - void initialize(); - - /// - /// Register a symbol to the Vulkan loader. - /// - /// @param symbol The name of the Vulkan function to override. - /// @param address The address of the Vulkan function. - /// - void registerSymbol(const std::string& symbol, void* address); - - /// - /// Call the original vkGetInstanceProcAddr function. - /// - /// @param instance The (optional) Vulkan instance. - /// @param pName The name of the function to retrieve. - /// @return The address of the function, or nullptr if not found. - /// - PFN_vkVoidFunction ovkGetInstanceProcAddr(VkInstance instance, const char* pName); - - /// - /// Call the original vkGetDeviceProcAddr function. - /// - /// @param device The Vulkan device. - /// @param pName The name of the function to retrieve. - /// @return The address of the function, or nullptr if not found. - /// - PFN_vkVoidFunction ovkGetDeviceProcAddr(VkDevice device, const char* pName); - -} - -/// Modified version of the vkGetInstanceProcAddr function. -extern "C" PFN_vkVoidFunction myvkGetInstanceProcAddr(VkInstance instance, const char* pName); -/// Modified version of the vkGetDeviceProcAddr function. -extern "C" PFN_vkVoidFunction myvkGetDeviceProcAddr(VkDevice device, const char* pName); - -#endif // VK_HPP diff --git a/include/utils/utils.hpp b/include/utils/utils.hpp index 0989e2d..fb6d8b1 100644 --- a/include/utils/utils.hpp +++ b/include/utils/utils.hpp @@ -50,6 +50,16 @@ namespace Utils { VkPipelineStageFlags pre, VkPipelineStageFlags post, bool makeSrcPresentable, bool makeDstPresentable); + /// + /// Store the current layer environment. + /// + void storeLayerEnv(); + + /// + /// Restore the layer environment to the previously stored value. + /// + void restoreLayerEnv(); + } #endif // UTILS_HPP diff --git a/src/context.cpp b/src/context.cpp index d4b2c39..2070bb9 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -1,5 +1,6 @@ #include "context.hpp" -#include "utils.hpp" +#include "layer.hpp" +#include "utils/utils.hpp" #include @@ -36,6 +37,10 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, VK_IMAGE_ASPECT_COLOR_BIT, &out_n_fds.at(i)); + Utils::storeLayerEnv(); + LSFG::initialize(); + Utils::restoreLayerEnv(); + this->lsfgCtxId = std::shared_ptr( new int32_t(LSFG::createContext(extent.width, extent.height, frame_0_fd, frame_1_fd, out_n_fds)), @@ -98,7 +103,7 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk // 3. acquire next swapchain image pass.acquireSemaphores.at(i) = Mini::Semaphore(info.device); uint32_t imageIdx{}; - auto res = vkAcquireNextImageKHR(info.device, this->swapchain, UINT64_MAX, + auto res = Layer::ovkAcquireNextImageKHR(info.device, this->swapchain, UINT64_MAX, pass.acquireSemaphores.at(i).handle(), VK_NULL_HANDLE, &imageIdx); if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image"); @@ -136,7 +141,7 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk .pSwapchains = &this->swapchain, .pImageIndices = &imageIdx, }; - res = vkQueuePresentKHR(queue, &presentInfo); + res = Layer::ovkQueuePresentKHR(queue, &presentInfo); if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) throw LSFG::vulkan_error(res, "Failed to present swapchain image"); } @@ -152,7 +157,7 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk .pSwapchains = &this->swapchain, .pImageIndices = &presentIdx, }; - auto res = vkQueuePresentKHR(queue, &presentInfo); + auto res = Layer::ovkQueuePresentKHR(queue, &presentInfo); if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) throw LSFG::vulkan_error(res, "Failed to present swapchain image"); diff --git a/src/hooks.cpp b/src/hooks.cpp index f7530c0..9831175 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -1,14 +1,14 @@ -#include "loader/dl.hpp" -#include "loader/vk.hpp" -#include "context.hpp" #include "hooks.hpp" -#include "log.hpp" -#include "utils.hpp" +#include "context.hpp" +#include "layer.hpp" +#include "utils/log.hpp" +#include "utils/utils.hpp" #include #include #include +#include using namespace Hooks; @@ -20,11 +20,6 @@ namespace { const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { - // create lsfg - Loader::DL::disableHooks(); - LSFG::initialize(); - Loader::DL::enableHooks(); - // add extensions auto extensions = Utils::addExtensions(pCreateInfo->ppEnabledExtensionNames, pCreateInfo->enabledExtensionCount, { @@ -33,24 +28,26 @@ namespace { "VK_KHR_external_semaphore_capabilities" }); + Log::info("lsfg-vk: Created Vulkan instance"); VkInstanceCreateInfo createInfo = *pCreateInfo; createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); - return vkCreateInstance(&createInfo, pAllocator, pInstance); + return Layer::ovkCreateInstance(&createInfo, pAllocator, pInstance); } void myvkDestroyInstance( VkInstance instance, const VkAllocationCallbacks* pAllocator) { LSFG::finalize(); // destroy lsfg - vkDestroyInstance(instance, pAllocator); + Log::info("lsfg-vk: Destroyed Vulkan instance"); + Layer::ovkDestroyInstance(instance, pAllocator); } // device hooks std::unordered_map devices; - VkResult myvkCreateDevice( + VkResult myvkCreateDevicePre( VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, @@ -67,8 +64,14 @@ namespace { VkDeviceCreateInfo createInfo = *pCreateInfo; createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); - auto res = vkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice); + return Layer::ovkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice); + } + VkResult myvkCreateDevicePost( + VkPhysicalDevice physicalDevice, + VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice) { // store device info try { const char* frameGen = std::getenv("LSFG_MULTIPLIER"); @@ -76,7 +79,7 @@ namespace { devices.emplace(*pDevice, DeviceInfo { .device = *pDevice, .physicalDevice = physicalDevice, - .queue = Utils::findQueue(*pDevice, physicalDevice, &createInfo, + .queue = Utils::findQueue(*pDevice, physicalDevice, pCreateInfo, VK_QUEUE_GRAPHICS_BIT), .frameGen = std::max(1, std::stoul(frameGen) - 1) }); @@ -84,12 +87,16 @@ namespace { Log::error("Failed to create device info: {}", e.what()); return VK_ERROR_INITIALIZATION_FAILED; } - return res; + + Log::info("lsfg-vk: Created Vulkan device"); + return VK_SUCCESS; } void myvkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { devices.erase(device); // erase device info - vkDestroyDevice(device, pAllocator); + + Log::info("lsfg-vk: Destroyed Vulkan device"); + Layer::ovkDestroyDevice(device, pAllocator); } // swapchain hooks @@ -110,7 +117,7 @@ namespace { createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; // allow copy from/to images createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; // force vsync - auto res = vkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); + auto res = Layer::ovkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); if (res != VK_SUCCESS) { Log::error("Failed to create swapchain: {:x}", static_cast(res)); return res; @@ -119,12 +126,12 @@ namespace { try { // get swapchain images uint32_t imageCount{}; - res = vkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr); + res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr); if (res != VK_SUCCESS || imageCount == 0) throw LSFG::vulkan_error(res, "Failed to get swapchain images count"); std::vector swapchainImages(imageCount); - res = vkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, swapchainImages.data()); + res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, swapchainImages.data()); if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Failed to get swapchain images"); @@ -135,7 +142,6 @@ namespace { )); swapchainToDeviceTable.emplace(*pSwapchain, device); - Log::debug("Created swapchain with {} images", imageCount); } catch (const LSFG::vulkan_error& e) { Log::error("Encountered Vulkan error {:x} while creating swapchain: {}", static_cast(e.error()), e.what()); @@ -145,6 +151,7 @@ namespace { return VK_ERROR_INITIALIZATION_FAILED; } + Log::info("lsfg-vk: Created swapchain with {} images", pCreateInfo->minImageCount); return res; } @@ -177,41 +184,24 @@ namespace { const VkAllocationCallbacks* pAllocator) { swapchains.erase(swapchain); // erase swapchain context swapchainToDeviceTable.erase(swapchain); - vkDestroySwapchainKHR(device, swapchain, pAllocator); - } - bool initialized{false}; + Log::info("lsfg-vk: Destroyed swapchain"); + Layer::ovkDestroySwapchainKHR(device, swapchain, pAllocator); + } } -void Hooks::initialize() { - if (initialized) { - Log::warn("Vulkan hooks already initialized, did you call it twice?"); - return; - } +std::unordered_map Hooks::hooks = { + // instance hooks + {"vkCreateInstance", reinterpret_cast(myvkCreateInstance)}, + {"vkDestroyInstance", reinterpret_cast(myvkDestroyInstance)}, - // list of hooks to register - const std::vector> hooks = { - { "vkCreateInstance", reinterpret_cast(myvkCreateInstance) }, - { "vkDestroyInstance", reinterpret_cast(myvkDestroyInstance) }, - { "vkCreateDevice", reinterpret_cast(myvkCreateDevice) }, - { "vkDestroyDevice", reinterpret_cast(myvkDestroyDevice) }, - { "vkCreateSwapchainKHR", reinterpret_cast(myvkCreateSwapchainKHR) }, - { "vkQueuePresentKHR", reinterpret_cast(myvkQueuePresentKHR) }, - { "vkDestroySwapchainKHR", reinterpret_cast(myvkDestroySwapchainKHR) } - }; + // device hooks + {"vkCreateDevicePre", reinterpret_cast(myvkCreateDevicePre)}, + {"vkCreateDevicePost", reinterpret_cast(myvkCreateDevicePost)}, + {"vkDestroyDevice", reinterpret_cast(myvkDestroyDevice)}, - // register hooks to Vulkan loader - for (const auto& hook : hooks) - Loader::VK::registerSymbol(hook.first, hook.second); - - // register hooks to dynamic loader under libvulkan.so.1 and libvulkan.so - for (const char* libName : {"libvulkan.so.1", "libvulkan.so"}) { - Loader::DL::File vkLib(libName); - for (const auto& hook : hooks) - vkLib.defineSymbol(hook.first, hook.second); - Loader::DL::registerFile(vkLib); - } - - initialized = true; - Log::info("Vulkan hooks initialized successfully"); -} + // swapchain hooks + {"vkCreateSwapchainKHR", reinterpret_cast(myvkCreateSwapchainKHR)}, + {"vkQueuePresentKHR", reinterpret_cast(myvkQueuePresentKHR)}, + {"vkDestroySwapchainKHR", reinterpret_cast(myvkDestroySwapchainKHR)} +}; diff --git a/src/init.cpp b/src/init.cpp deleted file mode 100644 index 3127d86..0000000 --- a/src/init.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "loader/dl.hpp" -#include "loader/vk.hpp" -#include "hooks.hpp" -#include "log.hpp" - -extern "C" void __attribute__((constructor)) init(); -extern "C" [[noreturn]] void __attribute__((destructor)) deinit(); - -void init() { - Log::info("lsfg-vk: init() called"); - - // hook loaders - Loader::DL::initialize(); - Loader::VK::initialize(); - - // setup hooks - Hooks::initialize(); - - Log::info("lsfg-vk: init() completed successfully"); -} - -void deinit() { - Log::debug("lsfg-vk: deinit() called, exiting"); - // for some reason some applications unload the library despite it containing - // the dl functions. this will lead to a segmentation fault, so we exit early. - exit(EXIT_SUCCESS); -} diff --git a/src/layer.cpp b/src/layer.cpp new file mode 100644 index 0000000..6f3d361 --- /dev/null +++ b/src/layer.cpp @@ -0,0 +1,527 @@ +#include "layer.hpp" +#include "hooks.hpp" +#include "utils/log.hpp" + +#include +#include + +#include +#include +#include + +namespace { + PFN_vkCreateInstance next_vkCreateInstance{}; + PFN_vkDestroyInstance next_vkDestroyInstance{}; + + PFN_vkCreateDevice next_vkCreateDevice{}; + PFN_vkDestroyDevice next_vkDestroyDevice{}; + + PFN_vkGetInstanceProcAddr next_vkGetInstanceProcAddr{}; + PFN_vkGetDeviceProcAddr next_vkGetDeviceProcAddr{}; + + PFN_vkCreateSwapchainKHR next_vkCreateSwapchainKHR{}; + PFN_vkQueuePresentKHR next_vkQueuePresentKHR{}; + PFN_vkDestroySwapchainKHR next_vkDestroySwapchainKHR{}; + PFN_vkGetSwapchainImagesKHR next_vkGetSwapchainImagesKHR{}; + PFN_vkAllocateCommandBuffers next_vkAllocateCommandBuffers{}; + PFN_vkFreeCommandBuffers next_vkFreeCommandBuffers{}; + PFN_vkBeginCommandBuffer next_vkBeginCommandBuffer{}; + PFN_vkEndCommandBuffer next_vkEndCommandBuffer{}; + PFN_vkCreateCommandPool next_vkCreateCommandPool{}; + PFN_vkDestroyCommandPool next_vkDestroyCommandPool{}; + PFN_vkCreateImage next_vkCreateImage{}; + PFN_vkDestroyImage next_vkDestroyImage{}; + PFN_vkGetImageMemoryRequirements next_vkGetImageMemoryRequirements{}; + PFN_vkBindImageMemory next_vkBindImageMemory{}; + PFN_vkAllocateMemory next_vkAllocateMemory{}; + PFN_vkFreeMemory next_vkFreeMemory{}; + PFN_vkCreateSemaphore next_vkCreateSemaphore{}; + PFN_vkDestroySemaphore next_vkDestroySemaphore{}; + PFN_vkGetMemoryFdKHR next_vkGetMemoryFdKHR{}; + PFN_vkGetSemaphoreFdKHR next_vkGetSemaphoreFdKHR{}; + PFN_vkGetPhysicalDeviceQueueFamilyProperties next_vkGetPhysicalDeviceQueueFamilyProperties{}; + PFN_vkGetPhysicalDeviceMemoryProperties next_vkGetPhysicalDeviceMemoryProperties{}; + PFN_vkGetDeviceQueue next_vkGetDeviceQueue{}; + PFN_vkQueueSubmit next_vkQueueSubmit{}; + PFN_vkCmdPipelineBarrier next_vkCmdPipelineBarrier{}; + PFN_vkCmdPipelineBarrier2 next_vkCmdPipelineBarrier2{}; + PFN_vkCmdCopyImage next_vkCmdCopyImage{}; + PFN_vkAcquireNextImageKHR next_vkAcquireNextImageKHR{}; +} + +namespace { + VkResult layer_vkCreateInstance( // NOLINTBEGIN + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) { + Log::debug("lsfg-vk(layer): Initializing lsfg-vk instance layer"); + + // find layer creation info + auto* layerDesc = const_cast( + reinterpret_cast(pCreateInfo->pNext)); + while (layerDesc && (layerDesc->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO + || layerDesc->function != VK_LAYER_LINK_INFO)) { + layerDesc = const_cast( + reinterpret_cast(layerDesc->pNext)); + } + if (!layerDesc) { + Log::error("lsfg-vk(layer): No layer creation info found in pNext chain"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + // advance link info (i don't really know what this does) + next_vkGetInstanceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetInstanceProcAddr; + layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; + + // create instance + next_vkCreateInstance = reinterpret_cast( + next_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance")); + if (!next_vkCreateInstance) { + Log::error("lsfg-vk(layer): Failed to get vkCreateInstance function pointer"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + auto* layer_vkCreateInstance2 = reinterpret_cast( + Hooks::hooks["vkCreateInstance"]); + auto res = layer_vkCreateInstance2(pCreateInfo, pAllocator, pInstance); + if (res != VK_SUCCESS) { + Log::error("lsfg-vk(layer): Failed to create Vulkan instance: {:x}", + static_cast(res)); + return res; + } + + // get relevant function pointers from the next layer + next_vkDestroyInstance = reinterpret_cast( + next_vkGetInstanceProcAddr(*pInstance, "vkDestroyInstance")); + next_vkGetInstanceProcAddr = reinterpret_cast( + next_vkGetInstanceProcAddr(*pInstance, "vkGetInstanceProcAddr")); + next_vkGetPhysicalDeviceQueueFamilyProperties = + reinterpret_cast( + next_vkGetInstanceProcAddr(*pInstance, "vkGetPhysicalDeviceQueueFamilyProperties")); + next_vkGetPhysicalDeviceMemoryProperties = + reinterpret_cast( + next_vkGetInstanceProcAddr(*pInstance, "vkGetPhysicalDeviceMemoryProperties")); + if (!next_vkDestroyInstance || !next_vkGetInstanceProcAddr || + !next_vkGetPhysicalDeviceQueueFamilyProperties || + !next_vkGetPhysicalDeviceMemoryProperties) { + Log::error("lsfg-vk(layer): Failed to get instance function pointers"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + Log::debug("lsfg-vk(layer): Successfully initialized lsfg-vk instance layer"); + return res; + } // NOLINTEND + + VkResult layer_vkCreateDevice( // NOLINTBEGIN + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice) { + Log::debug("lsfg-vk(layer): Initializing lsfg-vk device layer"); + + // find layer creation info + auto* layerDesc = const_cast( + reinterpret_cast(pCreateInfo->pNext)); + while (layerDesc && (layerDesc->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO + || layerDesc->function != VK_LAYER_LINK_INFO)) { + layerDesc = const_cast( + reinterpret_cast(layerDesc->pNext)); + } + if (!layerDesc) { + Log::error("lsfg-vk(layer): No layer creation info found in pNext chain"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + // advance link info (i don't really know what this does) + next_vkGetDeviceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetDeviceProcAddr; + layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; + + // create device + next_vkCreateDevice = reinterpret_cast( + next_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateDevice")); + if (!next_vkCreateDevice) { + Log::error("lsfg-vk(layer): Failed to get vkCreateDevice function pointer"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + auto* layer_vkCreateDevice2 = reinterpret_cast( + Hooks::hooks["vkCreateDevicePre"]); + auto res = layer_vkCreateDevice2(physicalDevice, pCreateInfo, pAllocator, pDevice); + if (res != VK_SUCCESS) { + Log::error("lsfg-vk(layer): Failed to create Vulkan device: {:x}", + static_cast(res)); + return res; + } + + // get relevant function pointers from the next layer + next_vkDestroyDevice = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkDestroyDevice")); + next_vkCreateSwapchainKHR = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkCreateSwapchainKHR")); + next_vkQueuePresentKHR = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkQueuePresentKHR")); + next_vkDestroySwapchainKHR = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkDestroySwapchainKHR")); + next_vkGetSwapchainImagesKHR = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkGetSwapchainImagesKHR")); + next_vkAllocateCommandBuffers = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkAllocateCommandBuffers")); + next_vkFreeCommandBuffers = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkFreeCommandBuffers")); + next_vkBeginCommandBuffer = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkBeginCommandBuffer")); + next_vkEndCommandBuffer = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkEndCommandBuffer")); + next_vkCreateCommandPool = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkCreateCommandPool")); + next_vkDestroyCommandPool = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkDestroyCommandPool")); + next_vkCreateImage = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkCreateImage")); + next_vkDestroyImage = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkDestroyImage")); + next_vkGetImageMemoryRequirements = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkGetImageMemoryRequirements")); + next_vkBindImageMemory = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkBindImageMemory")); + next_vkGetMemoryFdKHR = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkGetMemoryFdKHR")); + next_vkAllocateMemory = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkAllocateMemory")); + next_vkFreeMemory = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkFreeMemory")); + next_vkCreateSemaphore = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkCreateSemaphore")); + next_vkDestroySemaphore = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkDestroySemaphore")); + next_vkGetSemaphoreFdKHR = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkGetSemaphoreFdKHR")); + next_vkGetDeviceQueue = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkGetDeviceQueue")); + next_vkQueueSubmit = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkQueueSubmit")); + next_vkCmdPipelineBarrier = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkCmdPipelineBarrier")); + next_vkCmdPipelineBarrier2 = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkCmdPipelineBarrier2")); + next_vkCmdCopyImage = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkCmdCopyImage")); + next_vkAcquireNextImageKHR = reinterpret_cast( + next_vkGetDeviceProcAddr(*pDevice, "vkAcquireNextImageKHR")); + if (!next_vkDestroyDevice || !next_vkCreateSwapchainKHR || + !next_vkQueuePresentKHR || !next_vkDestroySwapchainKHR || + !next_vkGetSwapchainImagesKHR || !next_vkAllocateCommandBuffers || + !next_vkFreeCommandBuffers || !next_vkBeginCommandBuffer || + !next_vkEndCommandBuffer || !next_vkCreateCommandPool || + !next_vkDestroyCommandPool || !next_vkCreateImage || + !next_vkDestroyImage || !next_vkGetImageMemoryRequirements || + !next_vkBindImageMemory || !next_vkGetMemoryFdKHR || + !next_vkAllocateMemory || !next_vkFreeMemory || + !next_vkCreateSemaphore || !next_vkDestroySemaphore || + !next_vkGetSemaphoreFdKHR || !next_vkGetDeviceQueue || + !next_vkQueueSubmit || !next_vkCmdPipelineBarrier || + !next_vkCmdPipelineBarrier2 || !next_vkCmdCopyImage || + !next_vkAcquireNextImageKHR) { + Log::error("lsfg-vk(layer): Failed to get device function pointers"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + layer_vkCreateDevice2 = reinterpret_cast( + Hooks::hooks["vkCreateDevicePost"]); + res = layer_vkCreateDevice2(physicalDevice, pCreateInfo, pAllocator, pDevice); + if (res != VK_SUCCESS) { + Log::error("lsfg-vk(layer): Failed to create Vulkan device: {:x}", + static_cast(res)); + return res; + } + + Log::debug("lsfg-vk(layer): Successfully initialized lsfg-vk device layer"); + return res; + } // NOLINTEND +} + +const std::unordered_map layerFunctions = { + { "vkCreateInstance", + reinterpret_cast(&layer_vkCreateInstance) }, + { "vkGetInstanceProcAddr", + reinterpret_cast(&layer_vkGetInstanceProcAddr) }, + { "vkGetDeviceProcAddr", + reinterpret_cast(&layer_vkGetDeviceProcAddr) }, + { "vkCreateDevice", + reinterpret_cast(&layer_vkCreateDevice) }, +}; + +PFN_vkVoidFunction layer_vkGetInstanceProcAddr(VkInstance instance, const char* pName) { + std::string name(pName); + auto it = layerFunctions.find(name); + if (it != layerFunctions.end()) { + Log::debug("lsfg-vk(layer): Inserted layer function for {}", name); + return it->second; + } + + it = Hooks::hooks.find(name); + if (it != Hooks::hooks.end()) { + Log::debug("lsfg-vk(layer): Inserted hook function for {}", name); + return it->second; + } + + return next_vkGetInstanceProcAddr(instance, pName); +} + +PFN_vkVoidFunction layer_vkGetDeviceProcAddr(VkDevice device, const char* pName) { + std::string name(pName); + auto it = layerFunctions.find(name); + if (it != layerFunctions.end()) { + Log::debug("lsfg-vk(layer): Inserted layer function for {}", name); + return it->second; + } + + it = Hooks::hooks.find(name); + if (it != Hooks::hooks.end()) { + Log::debug("lsfg-vk(layer): Inserted hook function for {}", name); + return it->second; + } + + return next_vkGetDeviceProcAddr(device, pName); +} + +// original functions + +VkResult Layer::ovkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) { + return next_vkCreateInstance(pCreateInfo, pAllocator, pInstance); +} +void Layer::ovkDestroyInstance( + VkInstance instance, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyInstance(instance, pAllocator); +} + +VkResult Layer::ovkCreateDevice( + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice) { + return next_vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); +} +void Layer::ovkDestroyDevice( + VkDevice device, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyDevice(device, pAllocator); +} + +PFN_vkVoidFunction Layer::ovkGetInstanceProcAddr( + VkInstance instance, + const char* pName) { + return next_vkGetInstanceProcAddr(instance, pName); +} +PFN_vkVoidFunction Layer::ovkGetDeviceProcAddr( + VkDevice device, + const char* pName) { + return next_vkGetDeviceProcAddr(device, pName); +} + +VkResult Layer::ovkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain) { + return next_vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); +} +VkResult Layer::ovkQueuePresentKHR( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo) { + return next_vkQueuePresentKHR(queue, pPresentInfo); +} +void Layer::ovkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroySwapchainKHR(device, swapchain, pAllocator); +} + +VkResult Layer::ovkGetSwapchainImagesKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint32_t* pSwapchainImageCount, + VkImage* pSwapchainImages) { + return next_vkGetSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, pSwapchainImages); +} + +VkResult Layer::ovkAllocateCommandBuffers( + VkDevice device, + const VkCommandBufferAllocateInfo* pAllocateInfo, + VkCommandBuffer* pCommandBuffers) { + return next_vkAllocateCommandBuffers(device, pAllocateInfo, pCommandBuffers); +} +void Layer::ovkFreeCommandBuffers( + VkDevice device, + VkCommandPool commandPool, + uint32_t commandBufferCount, + const VkCommandBuffer* pCommandBuffers) { + next_vkFreeCommandBuffers(device, commandPool, commandBufferCount, pCommandBuffers); +} + +VkResult Layer::ovkBeginCommandBuffer( + VkCommandBuffer commandBuffer, + const VkCommandBufferBeginInfo* pBeginInfo) { + return next_vkBeginCommandBuffer(commandBuffer, pBeginInfo); +} +VkResult Layer::ovkEndCommandBuffer( + VkCommandBuffer commandBuffer) { + return next_vkEndCommandBuffer(commandBuffer); +} + +VkResult Layer::ovkCreateCommandPool( + VkDevice device, + const VkCommandPoolCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkCommandPool* pCommandPool) { + return next_vkCreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool); +} +void Layer::ovkDestroyCommandPool( + VkDevice device, + VkCommandPool commandPool, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyCommandPool(device, commandPool, pAllocator); +} + +VkResult Layer::ovkCreateImage( + VkDevice device, + const VkImageCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkImage* pImage) { + return next_vkCreateImage(device, pCreateInfo, pAllocator, pImage); +} +void Layer::ovkDestroyImage( + VkDevice device, + VkImage image, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyImage(device, image, pAllocator); +} + +void Layer::ovkGetImageMemoryRequirements( + VkDevice device, + VkImage image, + VkMemoryRequirements* pMemoryRequirements) { + next_vkGetImageMemoryRequirements(device, image, pMemoryRequirements); +} +VkResult Layer::ovkBindImageMemory( + VkDevice device, + VkImage image, + VkDeviceMemory memory, + VkDeviceSize memoryOffset) { + return next_vkBindImageMemory(device, image, memory, memoryOffset); +} + +VkResult Layer::ovkAllocateMemory( + VkDevice device, + const VkMemoryAllocateInfo* pAllocateInfo, + const VkAllocationCallbacks* pAllocator, + VkDeviceMemory* pMemory) { + return next_vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); +} +void Layer::ovkFreeMemory( + VkDevice device, + VkDeviceMemory memory, + const VkAllocationCallbacks* pAllocator) { + next_vkFreeMemory(device, memory, pAllocator); +} + +VkResult Layer::ovkCreateSemaphore( + VkDevice device, + const VkSemaphoreCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSemaphore* pSemaphore) { + return next_vkCreateSemaphore(device, pCreateInfo, pAllocator, pSemaphore); +} +void Layer::ovkDestroySemaphore( + VkDevice device, + VkSemaphore semaphore, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroySemaphore(device, semaphore, pAllocator); +} + +VkResult Layer::ovkGetMemoryFdKHR( + VkDevice device, + const VkMemoryGetFdInfoKHR* pGetFdInfo, + int* pFd) { + return next_vkGetMemoryFdKHR(device, pGetFdInfo, pFd); +} +VkResult Layer::ovkGetSemaphoreFdKHR( + VkDevice device, + const VkSemaphoreGetFdInfoKHR* pGetFdInfo, + int* pFd) { + return next_vkGetSemaphoreFdKHR(device, pGetFdInfo, pFd); +} + +void Layer::ovkGetPhysicalDeviceQueueFamilyProperties( + VkPhysicalDevice physicalDevice, + uint32_t* pQueueFamilyPropertyCount, + VkQueueFamilyProperties* pQueueFamilyProperties) { + next_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties); +} +void Layer::ovkGetPhysicalDeviceMemoryProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceMemoryProperties* pMemoryProperties) { + next_vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties); +} + +void Layer::ovkGetDeviceQueue( + VkDevice device, + uint32_t queueFamilyIndex, + uint32_t queueIndex, + VkQueue* pQueue) { + next_vkGetDeviceQueue(device, queueFamilyIndex, queueIndex, pQueue); +} +VkResult Layer::ovkQueueSubmit( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo* pSubmits, + VkFence fence) { + return next_vkQueueSubmit(queue, submitCount, pSubmits, fence); +} + +void Layer::ovkCmdPipelineBarrier( + VkCommandBuffer commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + uint32_t memoryBarrierCount, + const VkMemoryBarrier* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + const VkBufferMemoryBarrier* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + const VkImageMemoryBarrier* pImageMemoryBarriers) { + next_vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, dependencyFlags, + memoryBarrierCount, pMemoryBarriers, + bufferMemoryBarrierCount, pBufferMemoryBarriers, + imageMemoryBarrierCount, pImageMemoryBarriers); +} +void Layer::ovkCmdPipelineBarrier2( + VkCommandBuffer commandBuffer, + const VkDependencyInfo* pDependencyInfo) { + next_vkCmdPipelineBarrier2(commandBuffer, pDependencyInfo); +} +void Layer::ovkCmdCopyImage( + VkCommandBuffer commandBuffer, + VkImage srcImage, + VkImageLayout srcImageLayout, + VkImage dstImage, + VkImageLayout dstImageLayout, + uint32_t regionCount, + const VkImageCopy* pRegions) { + next_vkCmdCopyImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions); +} + +VkResult Layer::ovkAcquireNextImageKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint64_t timeout, + VkSemaphore semaphore, + VkFence fence, + uint32_t* pImageIndex) { + return next_vkAcquireNextImageKHR(device, swapchain, timeout, semaphore, fence, pImageIndex); +} diff --git a/src/loader/dl.cpp b/src/loader/dl.cpp deleted file mode 100644 index da25349..0000000 --- a/src/loader/dl.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#include "loader/dl.hpp" -#include "log.hpp" - -#include - -using namespace Loader; - -using dlopen_t = void* (*)(const char*, int); -using dlsym_t = void* (*)(void*, const char*); -using dlclose_t = int (*)(void*); - -// glibc exclusive function to get versioned symbols -extern "C" void* dlvsym(long, const char*, const char*); - -namespace { - // original function pointers - dlopen_t dlopen_ptr; - dlsym_t dlsym_ptr; - dlclose_t dlclose_ptr; - - // map of all registered overrides - auto& overrides() { - // this has to be a function rather than a static variable - // because of weird initialization order issues. - static std::unordered_map overrides; - return overrides; - } - - // vector of loaded handles - auto& handles() { - static std::vector handles; - return handles; - } - - bool enable_hooks{true}; -} - -void DL::initialize() { - if (dlopen_ptr || dlsym_ptr || dlclose_ptr) { - Log::warn("lsfg-vk(dl): Dynamic loader already initialized, did you call it twice?"); - return; - } - - dlopen_ptr = reinterpret_cast (dlvsym(-1, "dlopen", "GLIBC_2.2.5")); - dlsym_ptr = reinterpret_cast (dlvsym(-1, "dlsym", "GLIBC_2.2.5")); - dlclose_ptr = reinterpret_cast(dlvsym(-1, "dlclose", "GLIBC_2.2.5")); - if (!dlopen_ptr || !dlsym_ptr || !dlclose_ptr) { - Log::error("lsfg-vk(dl): Failed to initialize dynamic loader, missing symbols"); - exit(EXIT_FAILURE); - } - - Log::debug("lsfg-vk(dl): Initialized dynamic loader with original functions"); -} - -void DL::registerFile(const File& file) { - auto& files = overrides(); - - auto it = files.find(file.getFilename()); - if (it == files.end()) { - // simply register if the file hasn't been registered yet - files.emplace(file.getFilename(), file); - return; - } - - // merge the new file's symbols into the previously registered one - auto& existing_file = it->second; - for (const auto& [symbol, func] : file.getSymbols()) - if (existing_file.findSymbol(symbol) == nullptr) - existing_file.defineSymbol(symbol, func); - else - Log::warn("lsfg-vk(dl): Tried registering symbol {}::{}, but it is already defined", - existing_file.getFilename(), symbol); -} - -void DL::disableHooks() { enable_hooks = false; } -void DL::enableHooks() { enable_hooks = true; } - -void* dlopen(const char* filename, int flag) noexcept { - auto& files = overrides(); - auto& loaded = handles(); - - // ALWAYS load the library and ensure it's tracked - auto* handle = dlopen_ptr(filename, flag); - if (handle && std::ranges::find(loaded, handle) == loaded.end()) - loaded.push_back(handle); - - // no need to check for overrides if hooks are disabled - if (!enable_hooks || !filename) - return handle; - - // try to find an override for this filename - const std::string filename_str(filename); - auto it = files.find(filename_str); - if (it == files.end()) - return handle; - - auto& file = it->second; - file.setOriginalHandle(handle); - file.setHandle(reinterpret_cast(&file)); - - Log::debug("lsfg-vk(dl): Intercepted module load for {}", file.getFilename()); - return file.getHandle(); -} - -void* dlsym(void* handle, const char* symbol) noexcept { - const auto& files = overrides(); - - if (!enable_hooks || !handle || !symbol) - return dlsym_ptr(handle, symbol); - - // see if handle is a fake one - const auto it = std::ranges::find_if(files, [handle](const auto& pair) { - return pair.second.getHandle() == handle; - }); - if (it == files.end()) - return dlsym_ptr(handle, symbol); - const auto& file = it->second; - - // find a symbol override - const std::string symbol_str(symbol); - auto* func = file.findSymbol(symbol_str); - if (func == nullptr) - return dlsym_ptr(file.getOriginalHandle(), symbol); - - Log::debug("lsfg-vk(dl): Intercepted symbol {}::{}", file.getFilename(), symbol_str); - return func; -} - -int dlclose(void* handle) noexcept { - auto& files = overrides(); - auto& loaded = handles(); - - // no handle, let the original dlclose handle it - if (!handle) - return dlclose_ptr(handle); - - // see if the handle is a fake one - auto it = std::ranges::find_if(files, [handle](const auto& pair) { - return pair.second.getHandle() == handle; - }); - if (it == files.end()) { - // if the handle is not fake, check if it's still loaded. - // this is necessary to avoid double closing when - // one handle was acquired while hooks were disabled - auto l_it = std::ranges::find(loaded, handle); - if (l_it == loaded.end()) - return 0; - loaded.erase(l_it); - return dlclose_ptr(handle); - } - - auto& file = it->second; - handle = file.getOriginalHandle(); - file.setHandle(nullptr); - file.setOriginalHandle(nullptr); - - // similarly, if it is fake, check if it's still loaded - // before unloading it again. - auto l_it = std::ranges::find(loaded, handle); - if (l_it == loaded.end()) { - Log::debug("lsfg-vk(dl): Skipping unload for {} (already unloaded)", file.getFilename()); - return 0; - } - loaded.erase(l_it); - - Log::debug("lsfg-vk(dl): Unloaded {}", file.getFilename()); - return dlclose_ptr(handle); -} - -// original function calls - -void* DL::odlopen(const char* filename, int flag) { - return dlopen_ptr(filename, flag); -} - -void* DL::odlsym(void* handle, const char* symbol) { - return dlsym_ptr(handle, symbol); -} - -int DL::odlclose(void* handle) { - return dlclose_ptr(handle); -} diff --git a/src/loader/vk.cpp b/src/loader/vk.cpp deleted file mode 100644 index 4628fab..0000000 --- a/src/loader/vk.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include "loader/vk.hpp" -#include "loader/dl.hpp" -#include "log.hpp" - -using namespace Loader; - -namespace { - // original function pointers - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr_ptr{}; - PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr_ptr{}; - - // map of all overridden symbols - auto& symbols() { - static std::unordered_map symbols; - return symbols; - } -} - -void VK::initialize() { - if (vkGetInstanceProcAddr_ptr || vkGetDeviceProcAddr_ptr) { - Log::warn("lsfg-vk(vk): Vulkan loader already initialized, did you call it twice?"); - return; - } - - // get original function pointers - auto* handle = DL::odlopen("libvulkan.so.1", 0x2); - vkGetInstanceProcAddr_ptr = - reinterpret_cast(DL::odlsym(handle, "vkGetInstanceProcAddr")); - vkGetDeviceProcAddr_ptr = - reinterpret_cast (DL::odlsym(handle, "vkGetDeviceProcAddr")); - if (!vkGetInstanceProcAddr_ptr || !vkGetDeviceProcAddr_ptr) { - Log::error("lsfg-vk(vk): Failed to initialize Vulkan loader, missing symbols"); - exit(EXIT_FAILURE); - } - - // register dynamic loader overrides - DL::File vulkanLib{"libvulkan.so.1"}; - vulkanLib.defineSymbol("vkGetInstanceProcAddr", - reinterpret_cast(myvkGetInstanceProcAddr)); - vulkanLib.defineSymbol("vkGetDeviceProcAddr", - reinterpret_cast(myvkGetDeviceProcAddr)); - DL::registerFile(vulkanLib); - - DL::File vulkanLib2{"libvulkan.so"}; - vulkanLib2.defineSymbol("vkGetInstanceProcAddr", - reinterpret_cast(myvkGetInstanceProcAddr)); - vulkanLib2.defineSymbol("vkGetDeviceProcAddr", - reinterpret_cast(myvkGetDeviceProcAddr)); - DL::registerFile(vulkanLib2); - - // register vulkan loader overrides - VK::registerSymbol("vkGetInstanceProcAddr", reinterpret_cast(myvkGetInstanceProcAddr)); - VK::registerSymbol("vkGetDeviceProcAddr", reinterpret_cast(myvkGetDeviceProcAddr)); - - Log::debug("lsfg-vk(vk): Initialized Vulkan loader with original functions"); -} - -void VK::registerSymbol(const std::string& symbol, void* address) { - auto& syms = symbols(); - - const auto it = syms.find(symbol); - if (it != syms.end()) { - Log::warn("lsfg-vk(vk): Tried registering symbol {}, but it is already defined", symbol); - return; - } - - syms.emplace(symbol, address); -} - -PFN_vkVoidFunction myvkGetInstanceProcAddr(VkInstance instance, const char* pName) { - const auto& syms = symbols(); - - if (!pName) - return vkGetInstanceProcAddr_ptr(instance, pName); - - // try to find an override - const std::string pName_str(pName); - const auto it = syms.find(pName_str); - if (it == syms.end()) - return vkGetInstanceProcAddr_ptr(instance, pName); - - Log::debug("lsfg-vk(vk): Intercepted Vulkan symbol {}", pName_str); - return reinterpret_cast(it->second); -} - -PFN_vkVoidFunction myvkGetDeviceProcAddr(VkDevice device, const char* pName) { - const auto& syms = symbols(); - - if (!pName) - return vkGetDeviceProcAddr_ptr(device, pName); - - const std::string pName_str(pName); - auto it = syms.find(pName_str); - if (it == syms.end()) - return vkGetDeviceProcAddr_ptr(device, pName); - - Log::debug("lsfg-vk(vk): Intercepted Vulkan symbol {}", pName_str); - return reinterpret_cast(it->second); -} - -// original function calls - -PFN_vkVoidFunction VK::ovkGetInstanceProcAddr(VkInstance instance, const char* pName) { - return vkGetInstanceProcAddr_ptr(instance, pName); -} - -PFN_vkVoidFunction VK::ovkGetDeviceProcAddr(VkDevice device, const char* pName) { - return vkGetDeviceProcAddr_ptr(device, pName); -} diff --git a/src/mini/commandbuffer.cpp b/src/mini/commandbuffer.cpp index 8a8ac32..5887f65 100644 --- a/src/mini/commandbuffer.cpp +++ b/src/mini/commandbuffer.cpp @@ -1,4 +1,5 @@ #include "mini/commandbuffer.hpp" +#include "layer.hpp" #include @@ -13,7 +14,7 @@ CommandBuffer::CommandBuffer(VkDevice device, const CommandPool& pool) { .commandBufferCount = 1 }; VkCommandBuffer commandBufferHandle{}; - auto res = vkAllocateCommandBuffers(device, &desc, &commandBufferHandle); + auto res = Layer::ovkAllocateCommandBuffers(device, &desc, &commandBufferHandle); if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE) throw LSFG::vulkan_error(res, "Unable to allocate command buffer"); @@ -22,7 +23,7 @@ CommandBuffer::CommandBuffer(VkDevice device, const CommandPool& pool) { this->commandBuffer = std::shared_ptr( new VkCommandBuffer(commandBufferHandle), [dev = device, pool = pool.handle()](VkCommandBuffer* cmdBuffer) { - vkFreeCommandBuffers(dev, pool, 1, cmdBuffer); + Layer::ovkFreeCommandBuffers(dev, pool, 1, cmdBuffer); } ); } @@ -35,7 +36,7 @@ void CommandBuffer::begin() { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT }; - auto res = vkBeginCommandBuffer(*this->commandBuffer, &beginInfo); + auto res = Layer::ovkBeginCommandBuffer(*this->commandBuffer, &beginInfo); if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Unable to begin command buffer"); @@ -46,7 +47,7 @@ void CommandBuffer::end() { if (*this->state != CommandBufferState::Recording) throw std::logic_error("Command buffer is not in Recording state"); - auto res = vkEndCommandBuffer(*this->commandBuffer); + auto res = Layer::ovkEndCommandBuffer(*this->commandBuffer); if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Unable to end command buffer"); @@ -72,7 +73,7 @@ void CommandBuffer::submit(VkQueue queue, .signalSemaphoreCount = static_cast(signalSemaphores.size()), .pSignalSemaphores = signalSemaphores.data() }; - auto res = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + auto res = Layer::ovkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Unable to submit command buffer"); diff --git a/src/mini/commandpool.cpp b/src/mini/commandpool.cpp index 3407410..a624a59 100644 --- a/src/mini/commandpool.cpp +++ b/src/mini/commandpool.cpp @@ -1,4 +1,5 @@ #include "mini/commandpool.hpp" +#include "layer.hpp" #include @@ -11,7 +12,7 @@ CommandPool::CommandPool(VkDevice device, uint32_t graphicsFamilyIdx) { .queueFamilyIndex = graphicsFamilyIdx }; VkCommandPool commandPoolHandle{}; - auto res = vkCreateCommandPool(device, &desc, nullptr, &commandPoolHandle); + auto res = Layer::ovkCreateCommandPool(device, &desc, nullptr, &commandPoolHandle); if (res != VK_SUCCESS || commandPoolHandle == VK_NULL_HANDLE) throw LSFG::vulkan_error(res, "Unable to create command pool"); @@ -19,7 +20,7 @@ CommandPool::CommandPool(VkDevice device, uint32_t graphicsFamilyIdx) { this->commandPool = std::shared_ptr( new VkCommandPool(commandPoolHandle), [dev = device](VkCommandPool* commandPoolHandle) { - vkDestroyCommandPool(dev, *commandPoolHandle, nullptr); + Layer::ovkDestroyCommandPool(dev, *commandPoolHandle, nullptr); } ); } diff --git a/src/mini/image.cpp b/src/mini/image.cpp index 4fc8dcc..714c9cb 100644 --- a/src/mini/image.cpp +++ b/src/mini/image.cpp @@ -1,4 +1,5 @@ #include "mini/image.hpp" +#include "layer.hpp" #include @@ -32,16 +33,16 @@ Image::Image(VkDevice device, VkPhysicalDevice physicalDevice, .sharingMode = VK_SHARING_MODE_EXCLUSIVE }; VkImage imageHandle{}; - auto res = vkCreateImage(device, &desc, nullptr, &imageHandle); + auto res = Layer::ovkCreateImage(device, &desc, nullptr, &imageHandle); if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE) throw LSFG::vulkan_error(res, "Failed to create Vulkan image"); // find memory type VkPhysicalDeviceMemoryProperties memProps; - vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps); + Layer::ovkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps); VkMemoryRequirements memReqs; - vkGetImageMemoryRequirements(device, imageHandle, &memReqs); + Layer::ovkGetImageMemoryRequirements(device, imageHandle, &memReqs); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" @@ -74,24 +75,21 @@ Image::Image(VkDevice device, VkPhysicalDevice physicalDevice, .memoryTypeIndex = memType.value() }; VkDeviceMemory memoryHandle{}; - res = vkAllocateMemory(device, &allocInfo, nullptr, &memoryHandle); + res = Layer::ovkAllocateMemory(device, &allocInfo, nullptr, &memoryHandle); if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image"); - res = vkBindImageMemory(device, imageHandle, memoryHandle, 0); + res = Layer::ovkBindImageMemory(device, imageHandle, memoryHandle, 0); if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image"); // obtain the sharing fd - auto vkGetMemoryFdKHR = - reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetMemoryFdKHR")); - const VkMemoryGetFdInfoKHR fdInfo{ .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, .memory = memoryHandle, .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR, }; - res = vkGetMemoryFdKHR(device, &fdInfo, fd); + res = Layer::ovkGetMemoryFdKHR(device, &fdInfo, fd); if (res != VK_SUCCESS || *fd < 0) throw LSFG::vulkan_error(res, "Failed to obtain sharing fd for Vulkan image"); @@ -99,13 +97,13 @@ Image::Image(VkDevice device, VkPhysicalDevice physicalDevice, this->image = std::shared_ptr( new VkImage(imageHandle), [dev = device](VkImage* img) { - vkDestroyImage(dev, *img, nullptr); + Layer::ovkDestroyImage(dev, *img, nullptr); } ); this->memory = std::shared_ptr( new VkDeviceMemory(memoryHandle), [dev = device](VkDeviceMemory* mem) { - vkFreeMemory(dev, *mem, nullptr); + Layer::ovkFreeMemory(dev, *mem, nullptr); } ); } diff --git a/src/mini/semaphore.cpp b/src/mini/semaphore.cpp index f4b2225..b54c776 100644 --- a/src/mini/semaphore.cpp +++ b/src/mini/semaphore.cpp @@ -1,4 +1,5 @@ #include "mini/semaphore.hpp" +#include "layer.hpp" #include @@ -10,7 +11,7 @@ Semaphore::Semaphore(VkDevice device) { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; VkSemaphore semaphoreHandle{}; - auto res = vkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle); + auto res = Layer::ovkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle); if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) throw LSFG::vulkan_error(res, "Unable to create semaphore"); @@ -18,7 +19,7 @@ Semaphore::Semaphore(VkDevice device) { this->semaphore = std::shared_ptr( new VkSemaphore(semaphoreHandle), [dev = device](VkSemaphore* semaphoreHandle) { - vkDestroySemaphore(dev, *semaphoreHandle, nullptr); + Layer::ovkDestroySemaphore(dev, *semaphoreHandle, nullptr); } ); } @@ -34,20 +35,17 @@ Semaphore::Semaphore(VkDevice device, int* fd) { .pNext = &exportInfo }; VkSemaphore semaphoreHandle{}; - auto res = vkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle); + auto res = Layer::ovkCreateSemaphore(device, &desc, nullptr, &semaphoreHandle); if (res != VK_SUCCESS || semaphoreHandle == VK_NULL_HANDLE) throw LSFG::vulkan_error(res, "Unable to create semaphore"); // export semaphore to fd - auto vkGetSemaphoreFdKHR = reinterpret_cast( - vkGetDeviceProcAddr(device, "vkGetSemaphoreFdKHR")); - const VkSemaphoreGetFdInfoKHR fdInfo{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, .semaphore = semaphoreHandle, .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT }; - res = vkGetSemaphoreFdKHR(device, &fdInfo, fd); + res = Layer::ovkGetSemaphoreFdKHR(device, &fdInfo, fd); if (res != VK_SUCCESS || *fd < 0) throw LSFG::vulkan_error(res, "Unable to export semaphore to fd"); @@ -55,7 +53,7 @@ Semaphore::Semaphore(VkDevice device, int* fd) { this->semaphore = std::shared_ptr( new VkSemaphore(semaphoreHandle), [dev = device](VkSemaphore* semaphoreHandle) { - vkDestroySemaphore(dev, *semaphoreHandle, nullptr); + Layer::ovkDestroySemaphore(dev, *semaphoreHandle, nullptr); } ); } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index ed5acad..b32e0e6 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -1,9 +1,11 @@ #include "utils/utils.hpp" +#include "layer.hpp" #include #include #include +#include using namespace Utils; @@ -13,9 +15,9 @@ std::pair Utils::findQueue(VkDevice device, VkPhysicalDevice std::copy_n(desc->pQueueCreateInfos, enabledQueues.size(), enabledQueues.data()); uint32_t familyCount{}; - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr); + Layer::ovkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, nullptr); std::vector families(familyCount); - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, + Layer::ovkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, families.data()); std::optional idx; @@ -30,7 +32,7 @@ std::pair Utils::findQueue(VkDevice device, VkPhysicalDevice throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No suitable queue found"); VkQueue queue{}; - vkGetDeviceQueue(device, *idx, 0, &queue); + Layer::ovkGetDeviceQueue(device, *idx, 0, &queue); return { *idx, queue }; } @@ -78,7 +80,7 @@ void Utils::copyImage(VkCommandBuffer buf, } }; const std::vector barriers = { srcBarrier, dstBarrier }; - vkCmdPipelineBarrier(buf, + Layer::ovkCmdPipelineBarrier(buf, pre, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, static_cast(barriers.size()), barriers.data()); @@ -98,7 +100,7 @@ void Utils::copyImage(VkCommandBuffer buf, .depth = 1 } }; - vkCmdCopyImage(buf, + Layer::ovkCmdCopyImage(buf, src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageCopy); @@ -115,7 +117,7 @@ void Utils::copyImage(VkCommandBuffer buf, .layerCount = 1 } }; - vkCmdPipelineBarrier(buf, + Layer::ovkCmdPipelineBarrier(buf, VK_PIPELINE_STAGE_TRANSFER_BIT, post, 0, 0, nullptr, 0, nullptr, 1, &presentBarrier); @@ -135,10 +137,29 @@ void Utils::copyImage(VkCommandBuffer buf, .layerCount = 1 } }; - vkCmdPipelineBarrier(buf, + Layer::ovkCmdPipelineBarrier(buf, VK_PIPELINE_STAGE_TRANSFER_BIT, post, 0, 0, nullptr, 0, nullptr, 1, &presentBarrier); } - +} + +namespace { + std::optional layersEnvironment; +} + +void Utils::storeLayerEnv() { + const char* env = std::getenv("VK_INSTANCE_LAYERS"); + if (env) + layersEnvironment = env; + else + layersEnvironment.reset(); + unsetenv("VK_INSTANCE_LAYERS"); +} + +void Utils::restoreLayerEnv() { + if (layersEnvironment.has_value()) + setenv("VK_INSTANCE_LAYERS", layersEnvironment->c_str(), 1); + else + unsetenv("VK_INSTANCE_LAYERS"); } From 307485c37292d1ce6010d7b31eb410014dee6e05 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 5 Jul 2025 18:36:13 +0200 Subject: [PATCH 081/253] update buildscript and docs --- CMakeLists.txt | 5 ++++- README.md | 21 ++++++++++++--------- VkLayer_LS_frame_generation.json | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a29f9a6..e20ec99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,4 +53,7 @@ target_compile_options(lsfg-vk PRIVATE -Wno-cast-function-type-strict ) -install(FILES "${CMAKE_BINARY_DIR}/liblsfg-vk.so" DESTINATION lib) +install(FILES "${CMAKE_BINARY_DIR}/liblsfg-vk.so" + DESTINATION lib) +install(FILES "${CMAKE_SOURCE_DIR}/VkLayer_LS_frame_generation.json" + DESTINATION share/vulkan/explicit_layer.d) diff --git a/README.md b/README.md index 504dd0a..9d1363b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # lsfg-vk This project brings [Lossless Scaling's Frame Generation](https://store.steampowered.com/app/993090/Lossless_Scaling/) to Linux! - -## How does it work? -LSFG is primarily written in DirectX 11 compute shaders, which means we can use DXVK to translate it into SPIR-V. The surrounding parts have been rewritten in plain Vulkan code in order to make LSFG run natively on Linux. -By specifying an `LD_PRELOAD`, lsfg-vk can place itself inbetween your game and Vulkan. That way it can fetch frames from the game and insert its own frames without any problems. (Beware of anticheats please!) +>[!NOTE] +> This is a work-in-progress. While frame generation has worked in a few games, there's still a long way to go. Please review the wiki for support (the wiki is not written yet) ## Building, Installing and Running + +>[!CAUTION] +> The build instructions have recently changed. Please review them. + In order to compile LSFG, make sure you have the following components installed on your system: - Traditional build tools (+ sed, git) - Clang compiler (this project does NOT compile easily with GCC) @@ -18,25 +20,26 @@ Compiling lsfg-vk is relatively straight forward, as everything is neatly integr ```bash $ cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=$PWD/release \ + -DCMAKE_INSTALL_PREFIX=~/.local \ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \ -DCMAKE_CXX_CLANG_TIDY="" $ cmake --build build $ cmake --install build ``` -(Make sure you change `$PWD/release` to the path you'd like to install this project to) +This will install lsfg-vk to ~/.local/lib and ~/.local/share/vulkan. Next, you'll need to download Lossless Scaling from Steam. Switch to the `legacy_2.13` branch or download the corresponding depot. Copy or note down the path of "Lossless.dll" from the game files. Finally, let's actually start a program with frame generation enabled. I'm going to be using `vkcube` for this example: ```bash -LD_PRELOAD="/home/pancake/.lsfg-vk/liblsfg-vk.so" LSFG_DLL_PATH="/home/pancake/games/Lossless Scaling/Lossless.dll" LSFG_MULTIPLIER=4 vkcube +VK_INSTANCE_LAYERS="VK_LAYER_LS_frame_generation" LSFG_DLL_PATH="/home/pancake/games/Lossless Scaling/Lossless.dll" LSFG_MULTIPLIER=4 vkcube ``` Make sure you adjust the paths. Let's examine each one: -- `LD_PRELOAD`: This is how you let the loader know that you want to load lsfg-vk before anything else. This HAS to be either an ABSOLUTE path, or a path starting with `./` to make it relative. Specify `liblsfg-vk.so` here. -- `LSFG_DLL_PATH`: Here you specify the Lossless.dll you downloaded from Steam. This is either an absolute path, or a relative path from where you are running the app. +- `LVK_INSTANCE_LAYERS`: Specify `VK_LAYER_LS_frame_generation` here. This forces any Vulkan app to load the lsfg-vk layer. +- `LSFG_DLL_PATH`: Here you specify the Lossless.dll you downloaded from Steam. lsfg-vk will extract and translate the shaders from here. - `LSFG_MULTIPLIER`: This is the multiplier you should be familiar with. Specify `2` for doubling the framerate, etc. +- `VK_LAYER_PATH`: If you did not install to `~/.local` or `/usr`, you have to specify the `explicit_layer.d` folder here. >[!WARNING] > Unlike on Windows, LSFG_MULTIPLIER is heavily limited here (at the moment!). If your hardware can create 8 swapchain images, then setting LSFG_MULTIPLIER to 4 occupies 4 of those, leaving only 4 to the game. If the game requested 5 or more, it will crash. diff --git a/VkLayer_LS_frame_generation.json b/VkLayer_LS_frame_generation.json index e7040ff..4761d40 100644 --- a/VkLayer_LS_frame_generation.json +++ b/VkLayer_LS_frame_generation.json @@ -4,7 +4,7 @@ "name": "VK_LAYER_LS_frame_generation", "type": "GLOBAL", "api_version": "1.4.313", - "library_path": "./build/liblsfg-vk.so", + "library_path": "../../../lib/liblsfg-vk.so", "implementation_version": "1", "description": "Lossless Scaling frame generation layer", "functions": { From c8e7d4810ec21c8f88ed9c80246f0c34f8372c96 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 5 Jul 2025 20:18:20 +0200 Subject: [PATCH 082/253] log failed pointer resolves --- src/hooks.cpp | 1 - src/layer.cpp | 153 ++++++++++++++++++++------------------------------ 2 files changed, 60 insertions(+), 94 deletions(-) diff --git a/src/hooks.cpp b/src/hooks.cpp index 9831175..4809c06 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -38,7 +38,6 @@ namespace { void myvkDestroyInstance( VkInstance instance, const VkAllocationCallbacks* pAllocator) { - LSFG::finalize(); // destroy lsfg Log::info("lsfg-vk: Destroyed Vulkan instance"); Layer::ovkDestroyInstance(instance, pAllocator); } diff --git a/src/layer.cpp b/src/layer.cpp index 6f3d361..3a412f1 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -47,8 +47,29 @@ namespace { PFN_vkCmdPipelineBarrier2 next_vkCmdPipelineBarrier2{}; PFN_vkCmdCopyImage next_vkCmdCopyImage{}; PFN_vkAcquireNextImageKHR next_vkAcquireNextImageKHR{}; + + template + bool initInstanceFunc(VkInstance instance, const char* name, T* func) { + *func = reinterpret_cast(next_vkGetInstanceProcAddr(instance, name)); + if (!*func) { + Log::error("lsfg-vk(layer): Failed to get instance function pointer for {}", name); + return false; + } + return true; + } + + template + bool initDeviceFunc(VkDevice device, const char* name, T* func) { + *func = reinterpret_cast(next_vkGetDeviceProcAddr(device, name)); + if (!*func) { + Log::error("lsfg-vk(layer): Failed to get device function pointer for {}", name); + return false; + } + return true; + } } + namespace { VkResult layer_vkCreateInstance( // NOLINTBEGIN const VkInstanceCreateInfo* pCreateInfo, @@ -74,12 +95,8 @@ namespace { layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; // create instance - next_vkCreateInstance = reinterpret_cast( - next_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance")); - if (!next_vkCreateInstance) { - Log::error("lsfg-vk(layer): Failed to get vkCreateInstance function pointer"); - return VK_ERROR_INITIALIZATION_FAILED; - } + auto success = initInstanceFunc(nullptr, "vkCreateInstance", &next_vkCreateInstance); + if (!success) return VK_ERROR_INITIALIZATION_FAILED; auto* layer_vkCreateInstance2 = reinterpret_cast( Hooks::hooks["vkCreateInstance"]); @@ -91,19 +108,11 @@ namespace { } // get relevant function pointers from the next layer - next_vkDestroyInstance = reinterpret_cast( - next_vkGetInstanceProcAddr(*pInstance, "vkDestroyInstance")); - next_vkGetInstanceProcAddr = reinterpret_cast( - next_vkGetInstanceProcAddr(*pInstance, "vkGetInstanceProcAddr")); - next_vkGetPhysicalDeviceQueueFamilyProperties = - reinterpret_cast( - next_vkGetInstanceProcAddr(*pInstance, "vkGetPhysicalDeviceQueueFamilyProperties")); - next_vkGetPhysicalDeviceMemoryProperties = - reinterpret_cast( - next_vkGetInstanceProcAddr(*pInstance, "vkGetPhysicalDeviceMemoryProperties")); - if (!next_vkDestroyInstance || !next_vkGetInstanceProcAddr || - !next_vkGetPhysicalDeviceQueueFamilyProperties || - !next_vkGetPhysicalDeviceMemoryProperties) { + success = true; + success &= initInstanceFunc(*pInstance, "vkDestroyInstance", &next_vkDestroyInstance); + success &= initInstanceFunc(*pInstance, "vkGetPhysicalDeviceQueueFamilyProperties", &next_vkGetPhysicalDeviceQueueFamilyProperties); + success &= initInstanceFunc(*pInstance, "vkGetPhysicalDeviceMemoryProperties", &next_vkGetPhysicalDeviceMemoryProperties); + if (!success) { Log::error("lsfg-vk(layer): Failed to get instance function pointers"); return VK_ERROR_INITIALIZATION_FAILED; } @@ -137,12 +146,9 @@ namespace { layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; // create device - next_vkCreateDevice = reinterpret_cast( - next_vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateDevice")); - if (!next_vkCreateDevice) { - Log::error("lsfg-vk(layer): Failed to get vkCreateDevice function pointer"); - return VK_ERROR_INITIALIZATION_FAILED; - } + auto success = initInstanceFunc(nullptr, "vkCreateDevice", + &next_vkCreateDevice); + if (!success) return VK_ERROR_INITIALIZATION_FAILED; auto* layer_vkCreateDevice2 = reinterpret_cast( Hooks::hooks["vkCreateDevicePre"]); @@ -154,74 +160,35 @@ namespace { } // get relevant function pointers from the next layer - next_vkDestroyDevice = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkDestroyDevice")); - next_vkCreateSwapchainKHR = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkCreateSwapchainKHR")); - next_vkQueuePresentKHR = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkQueuePresentKHR")); - next_vkDestroySwapchainKHR = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkDestroySwapchainKHR")); - next_vkGetSwapchainImagesKHR = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkGetSwapchainImagesKHR")); - next_vkAllocateCommandBuffers = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkAllocateCommandBuffers")); - next_vkFreeCommandBuffers = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkFreeCommandBuffers")); - next_vkBeginCommandBuffer = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkBeginCommandBuffer")); - next_vkEndCommandBuffer = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkEndCommandBuffer")); - next_vkCreateCommandPool = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkCreateCommandPool")); - next_vkDestroyCommandPool = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkDestroyCommandPool")); - next_vkCreateImage = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkCreateImage")); - next_vkDestroyImage = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkDestroyImage")); - next_vkGetImageMemoryRequirements = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkGetImageMemoryRequirements")); - next_vkBindImageMemory = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkBindImageMemory")); - next_vkGetMemoryFdKHR = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkGetMemoryFdKHR")); - next_vkAllocateMemory = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkAllocateMemory")); - next_vkFreeMemory = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkFreeMemory")); - next_vkCreateSemaphore = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkCreateSemaphore")); - next_vkDestroySemaphore = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkDestroySemaphore")); - next_vkGetSemaphoreFdKHR = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkGetSemaphoreFdKHR")); - next_vkGetDeviceQueue = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkGetDeviceQueue")); - next_vkQueueSubmit = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkQueueSubmit")); - next_vkCmdPipelineBarrier = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkCmdPipelineBarrier")); - next_vkCmdPipelineBarrier2 = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkCmdPipelineBarrier2")); - next_vkCmdCopyImage = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkCmdCopyImage")); - next_vkAcquireNextImageKHR = reinterpret_cast( - next_vkGetDeviceProcAddr(*pDevice, "vkAcquireNextImageKHR")); - if (!next_vkDestroyDevice || !next_vkCreateSwapchainKHR || - !next_vkQueuePresentKHR || !next_vkDestroySwapchainKHR || - !next_vkGetSwapchainImagesKHR || !next_vkAllocateCommandBuffers || - !next_vkFreeCommandBuffers || !next_vkBeginCommandBuffer || - !next_vkEndCommandBuffer || !next_vkCreateCommandPool || - !next_vkDestroyCommandPool || !next_vkCreateImage || - !next_vkDestroyImage || !next_vkGetImageMemoryRequirements || - !next_vkBindImageMemory || !next_vkGetMemoryFdKHR || - !next_vkAllocateMemory || !next_vkFreeMemory || - !next_vkCreateSemaphore || !next_vkDestroySemaphore || - !next_vkGetSemaphoreFdKHR || !next_vkGetDeviceQueue || - !next_vkQueueSubmit || !next_vkCmdPipelineBarrier || - !next_vkCmdPipelineBarrier2 || !next_vkCmdCopyImage || - !next_vkAcquireNextImageKHR) { + success = true; + success &= initDeviceFunc(*pDevice, "vkDestroyDevice", &next_vkDestroyDevice); + success &= initDeviceFunc(*pDevice, "vkCreateSwapchainKHR", &next_vkCreateSwapchainKHR); + success &= initDeviceFunc(*pDevice, "vkQueuePresentKHR", &next_vkQueuePresentKHR); + success &= initDeviceFunc(*pDevice, "vkDestroySwapchainKHR", &next_vkDestroySwapchainKHR); + success &= initDeviceFunc(*pDevice, "vkGetSwapchainImagesKHR", &next_vkGetSwapchainImagesKHR); + success &= initDeviceFunc(*pDevice, "vkAllocateCommandBuffers", &next_vkAllocateCommandBuffers); + success &= initDeviceFunc(*pDevice, "vkFreeCommandBuffers", &next_vkFreeCommandBuffers); + success &= initDeviceFunc(*pDevice, "vkBeginCommandBuffer", &next_vkBeginCommandBuffer); + success &= initDeviceFunc(*pDevice, "vkEndCommandBuffer", &next_vkEndCommandBuffer); + success &= initDeviceFunc(*pDevice, "vkCreateCommandPool", &next_vkCreateCommandPool); + success &= initDeviceFunc(*pDevice, "vkDestroyCommandPool", &next_vkDestroyCommandPool); + success &= initDeviceFunc(*pDevice, "vkCreateImage", &next_vkCreateImage); + success &= initDeviceFunc(*pDevice, "vkDestroyImage", &next_vkDestroyImage); + success &= initDeviceFunc(*pDevice, "vkGetImageMemoryRequirements", &next_vkGetImageMemoryRequirements); + success &= initDeviceFunc(*pDevice, "vkBindImageMemory", &next_vkBindImageMemory); + success &= initDeviceFunc(*pDevice, "vkGetMemoryFdKHR", &next_vkGetMemoryFdKHR); + success &= initDeviceFunc(*pDevice, "vkAllocateMemory", &next_vkAllocateMemory); + success &= initDeviceFunc(*pDevice, "vkFreeMemory", &next_vkFreeMemory); + success &= initDeviceFunc(*pDevice, "vkCreateSemaphore", &next_vkCreateSemaphore); + success &= initDeviceFunc(*pDevice, "vkDestroySemaphore", &next_vkDestroySemaphore); + success &= initDeviceFunc(*pDevice, "vkGetSemaphoreFdKHR", &next_vkGetSemaphoreFdKHR); + success &= initDeviceFunc(*pDevice, "vkGetDeviceQueue", &next_vkGetDeviceQueue); + success &= initDeviceFunc(*pDevice, "vkQueueSubmit", &next_vkQueueSubmit); + success &= initDeviceFunc(*pDevice, "vkCmdPipelineBarrier", &next_vkCmdPipelineBarrier); + success &= initDeviceFunc(*pDevice, "vkCmdPipelineBarrier2", &next_vkCmdPipelineBarrier2); + success &= initDeviceFunc(*pDevice, "vkCmdCopyImage", &next_vkCmdCopyImage); + success &= initDeviceFunc(*pDevice, "vkAcquireNextImageKHR", &next_vkAcquireNextImageKHR); + if (!success) { Log::error("lsfg-vk(layer): Failed to get device function pointers"); return VK_ERROR_INITIALIZATION_FAILED; } From 2eeadd49b09220d92805474f17a8d6b7181d7788 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 5 Jul 2025 20:36:58 +0200 Subject: [PATCH 083/253] remove synchronization2 functions --- include/layer.hpp | 4 ---- src/layer.cpp | 7 ------- 2 files changed, 11 deletions(-) diff --git a/include/layer.hpp b/include/layer.hpp index e43b265..d06bbb4 100644 --- a/include/layer.hpp +++ b/include/layer.hpp @@ -182,10 +182,6 @@ namespace Layer { const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers); - /// Call to the original vkCmdPipelineBarrier2 function. - void ovkCmdPipelineBarrier2( - VkCommandBuffer commandBuffer, - const VkDependencyInfo* pDependencyInfo); /// Call to the original vkCmdCopyImage function. void ovkCmdCopyImage( VkCommandBuffer commandBuffer, diff --git a/src/layer.cpp b/src/layer.cpp index 3a412f1..007d2d4 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -44,7 +44,6 @@ namespace { PFN_vkGetDeviceQueue next_vkGetDeviceQueue{}; PFN_vkQueueSubmit next_vkQueueSubmit{}; PFN_vkCmdPipelineBarrier next_vkCmdPipelineBarrier{}; - PFN_vkCmdPipelineBarrier2 next_vkCmdPipelineBarrier2{}; PFN_vkCmdCopyImage next_vkCmdCopyImage{}; PFN_vkAcquireNextImageKHR next_vkAcquireNextImageKHR{}; @@ -185,7 +184,6 @@ namespace { success &= initDeviceFunc(*pDevice, "vkGetDeviceQueue", &next_vkGetDeviceQueue); success &= initDeviceFunc(*pDevice, "vkQueueSubmit", &next_vkQueueSubmit); success &= initDeviceFunc(*pDevice, "vkCmdPipelineBarrier", &next_vkCmdPipelineBarrier); - success &= initDeviceFunc(*pDevice, "vkCmdPipelineBarrier2", &next_vkCmdPipelineBarrier2); success &= initDeviceFunc(*pDevice, "vkCmdCopyImage", &next_vkCmdCopyImage); success &= initDeviceFunc(*pDevice, "vkAcquireNextImageKHR", &next_vkAcquireNextImageKHR); if (!success) { @@ -467,11 +465,6 @@ void Layer::ovkCmdPipelineBarrier( bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers); } -void Layer::ovkCmdPipelineBarrier2( - VkCommandBuffer commandBuffer, - const VkDependencyInfo* pDependencyInfo) { - next_vkCmdPipelineBarrier2(commandBuffer, pDependencyInfo); -} void Layer::ovkCmdCopyImage( VkCommandBuffer commandBuffer, VkImage srcImage, From 9f806d5bb5c26bff6398efc7cff36495ea1065a5 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 13:30:38 +0200 Subject: [PATCH 084/253] much more verbose logging in hook --- CMakeLists.txt | 4 + include/utils/log.hpp | 73 +++++++++---- src/context.cpp | 35 +++++- src/hooks.cpp | 110 ++++++++++++++----- src/layer.cpp | 245 +++++++++++++++++++++++++++++++++++------- src/utils/log.cpp | 43 ++++++++ src/utils/utils.cpp | 9 +- 7 files changed, 436 insertions(+), 83 deletions(-) create mode 100644 src/utils/log.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e20ec99..a96a1f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_SKIP_RPATH ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +if(CMAKE_BUILD_TYPE STREQUAL "Release") + add_definitions("-DLSFG_NO_DEBUG") +endif() + # subprojects include(cmake/FetchDXVK.cmake) include(cmake/FetchPeParse.cmake) diff --git a/include/utils/log.hpp b/include/utils/log.hpp index c249833..f85649e 100644 --- a/include/utils/log.hpp +++ b/include/utils/log.hpp @@ -2,35 +2,72 @@ #define LOG_HPP #include +#include #include +#include +#include namespace Log { + namespace Internal { + extern bool isSetup; + + extern std::set debugModules; + extern bool debugAllModules; + + extern std::ofstream logFile; + extern std::mutex logMutex; + + void setup(); + } + + template + void log(std::string_view color, std::string_view module, + std::format_string fmt, Args&&... args) { + Internal::setup(); + + std::string prefix = std::format("lsfg-vk({}): ", module); + std::string message = std::format(fmt, std::forward(args)...); + + std::lock_guard lock(Internal::logMutex); + std::cerr << color << prefix << message << "\033[0m" << '\n'; + if (Internal::logFile.is_open()) { + Internal::logFile << prefix << message << '\n'; + Internal::logFile.flush(); + } + } + const std::string_view WHITE = "\033[1;37m"; const std::string_view YELLOW = "\033[1;33m"; const std::string_view RED = "\033[1;31m"; + + template + void info(std::string_view module, std::format_string fmt, Args&&... args) { + log(WHITE, module, fmt, std::forward(args)...); + } + + template + void warn(std::string_view module, std::format_string fmt, Args&&... args) { + log(YELLOW, module, fmt, std::forward(args)...); + } + + template + void error(std::string_view module, std::format_string fmt, Args&&... args) { + log(RED, module, fmt, std::forward(args)...); + } + const std::string_view GRAY = "\033[1;90m"; - const std::string_view RESET = "\033[0m"; +#ifdef LSFG_NO_DEBUG +template +void debug(std::string_view, std::format_string, Args&&...) {} // NOLINT +#else template - void info(std::format_string fmt, Args&&... args) { - std::cerr << WHITE << std::format(fmt, std::forward(args)...) << RESET << '\n'; - } - - template - void warn(std::format_string fmt, Args&&... args) { - std::cerr << YELLOW << std::format(fmt, std::forward(args)...) << RESET << '\n'; - } - - template - void error(std::format_string fmt, Args&&... args) { - std::cerr << RED << std::format(fmt, std::forward(args)...) << RESET << '\n'; - } - - template - void debug(std::format_string fmt, Args&&... args) { - std::cerr << GRAY << std::format(fmt, std::forward(args)...) << RESET << '\n'; + void debug(std::string_view module, std::format_string fmt, Args&&... args) { + if (Internal::debugAllModules || Internal::debugModules.contains(std::string(module))) + log(GRAY, module, fmt, std::forward(args)...); } +#endif } diff --git a/src/context.cpp b/src/context.cpp index 2070bb9..2816f41 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -1,5 +1,6 @@ #include "context.hpp" #include "layer.hpp" +#include "utils/log.hpp" #include "utils/utils.hpp" #include @@ -11,7 +12,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, VkExtent2D extent, const std::vector& swapchainImages) : swapchain(swapchain), swapchainImages(swapchainImages), extent(extent) { - // initialize lsfg + // prepare textures for lsfg int frame_0_fd{}; this->frame_0 = Mini::Image( info.device, info.physicalDevice, @@ -19,6 +20,8 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, &frame_0_fd); + Log::info("context", "Created frame_0 image and obtained fd: {}", + frame_0_fd); int frame_1_fd{}; this->frame_1 = Mini::Image( @@ -27,27 +30,42 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, &frame_1_fd); + Log::info("context", "Created frame_1 image and obtained fd: {}", + frame_1_fd); std::vector out_n_fds(info.frameGen); - for (size_t i = 0; i < info.frameGen; ++i) + for (size_t i = 0; i < info.frameGen; ++i) { this->out_n.emplace_back( info.device, info.physicalDevice, extent, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_ASPECT_COLOR_BIT, &out_n_fds.at(i)); + Log::info("context", "Created out_n[{}] image and obtained fd: {}", + i, out_n_fds.at(i)); + } + // initialize lsfg + Log::debug("context", "(entering LSFG initialization)"); Utils::storeLayerEnv(); LSFG::initialize(); Utils::restoreLayerEnv(); + Log::debug("context", "(exiting LSFG initialization)"); + Log::debug("context", "(entering LSFG context creation)"); this->lsfgCtxId = std::shared_ptr( new int32_t(LSFG::createContext(extent.width, extent.height, frame_0_fd, frame_1_fd, out_n_fds)), [](const int32_t* id) { + Log::info("context", + "(entering LSFG context deletion with id: {})", *id); LSFG::deleteContext(*id); + Log::info("context", + "(exiting LSFG context deletion with id: {})", *id); } ); + Log::info("context", "(exiting LSFG context creation with id: {})", + *this->lsfgCtxId); // prepare render passes this->cmdPool = Mini::CommandPool(info.device, info.queue.first); @@ -59,6 +77,8 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, pass.postCopySemaphores.resize(info.frameGen); pass.prevPostCopySemaphores.resize(info.frameGen); } + + Log::info("context", "Remaining misc context init finished successfully."); } VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue, @@ -66,6 +86,8 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk auto& pass = this->passInfos.at(this->frameIdx % 8); // 1. copy swapchain image to frame_0/frame_1 + Log::debug("context2", "1. Copying swapchain image {} to frame {}", + presentIdx, this->frameIdx % 2 == 0 ? "0" : "1"); int preCopySemaphoreFd{}; pass.preCopySemaphores.at(0) = Mini::Semaphore(info.device, &preCopySemaphoreFd); pass.preCopySemaphores.at(1) = Mini::Semaphore(info.device); @@ -91,16 +113,22 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk pass.preCopySemaphores.at(1).handle() }); // 2. render intermediary frames + Log::debug("context2", "2. Rendering intermediary frames"); std::vector renderSemaphoreFds(info.frameGen); for (size_t i = 0; i < info.frameGen; ++i) pass.renderSemaphores.at(i) = Mini::Semaphore(info.device, &renderSemaphoreFds.at(i)); + Log::debug("context2", + "(entering LSFG present with id: {})", *this->lsfgCtxId); LSFG::presentContext(*this->lsfgCtxId, preCopySemaphoreFd, renderSemaphoreFds); + Log::debug("context2", + "(exiting LSFG present with id: {})", *this->lsfgCtxId); for (size_t i = 0; i < info.frameGen; i++) { // 3. acquire next swapchain image + Log::debug("context2", "3. Acquiring next swapchain image for frame {}", i); pass.acquireSemaphores.at(i) = Mini::Semaphore(info.device); uint32_t imageIdx{}; auto res = Layer::ovkAcquireNextImageKHR(info.device, this->swapchain, UINT64_MAX, @@ -109,6 +137,7 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image"); // 4. copy output image to swapchain image + Log::debug("context2", "4. Copying output image to swapchain image for frame {}", i); pass.postCopySemaphores.at(i) = Mini::Semaphore(info.device); pass.prevPostCopySemaphores.at(i) = Mini::Semaphore(info.device); pass.postCopyBufs.at(i) = Mini::CommandBuffer(info.device, this->cmdPool); @@ -129,6 +158,7 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk pass.prevPostCopySemaphores.at(i).handle() }); // 5. present swapchain image + Log::debug("context2", "5. Presenting swapchain image for frame {}", i); std::vector waitSemaphores{ pass.postCopySemaphores.at(i).handle() }; if (i != 0) waitSemaphores.emplace_back(pass.prevPostCopySemaphores.at(i - 1).handle()); @@ -147,6 +177,7 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk } // 6. present actual next frame + Log::debug("context2", "6. Presenting actual next frame"); VkSemaphore lastPrevPostCopySemaphore = pass.prevPostCopySemaphores.at(info.frameGen - 1).handle(); const VkPresentInfoKHR presentInfo{ diff --git a/src/hooks.cpp b/src/hooks.cpp index 4809c06..a640f87 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -27,18 +27,26 @@ namespace { "VK_KHR_external_memory_capabilities", "VK_KHR_external_semaphore_capabilities" }); - - Log::info("lsfg-vk: Created Vulkan instance"); VkInstanceCreateInfo createInfo = *pCreateInfo; createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); - return Layer::ovkCreateInstance(&createInfo, pAllocator, pInstance); + auto res = Layer::ovkCreateInstance(&createInfo, pAllocator, pInstance); + if (res != VK_SUCCESS) { + Log::error("hooks", "Failed to create Vulkan instance: {:x}", + static_cast(res)); + return res; + } + + Log::info("hooks", "Instance created successfully: {:x}", + reinterpret_cast(*pInstance)); + return res; } void myvkDestroyInstance( VkInstance instance, const VkAllocationCallbacks* pAllocator) { - Log::info("lsfg-vk: Destroyed Vulkan instance"); + Log::info("hooks", "Instance destroyed successfully: {:x}", + reinterpret_cast(instance)); Layer::ovkDestroyInstance(instance, pAllocator); } @@ -63,38 +71,59 @@ namespace { VkDeviceCreateInfo createInfo = *pCreateInfo; createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); - return Layer::ovkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice); + auto res = Layer::ovkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice); + if (res != VK_SUCCESS) { + Log::error("hooks", "Failed to create Vulkan device: {:x}", + static_cast(res)); + return res; + } + + Log::info("hooks", "Device created successfully: {:x}", + reinterpret_cast(*pDevice)); + return res; } VkResult myvkCreateDevicePost( VkPhysicalDevice physicalDevice, VkDeviceCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, + const VkAllocationCallbacks*, // NOLINT VkDevice* pDevice) { // store device info + Log::debug("hooks", "Creating device info for device: {:x}", + reinterpret_cast(*pDevice)); try { - const char* frameGen = std::getenv("LSFG_MULTIPLIER"); - if (!frameGen) frameGen = "2"; + const char* frameGenEnv = std::getenv("LSFG_MULTIPLIER"); + const uint64_t frameGen = std::max(1, + std::stoul(frameGenEnv ? frameGenEnv : "2") - 1); + Log::debug("hooks", "Using {}x frame generation", + frameGen + 1); + + auto queue = Utils::findQueue(*pDevice, physicalDevice, pCreateInfo, + VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT); + Log::debug("hooks", "Found queue at index {}: {:x}", + queue.first, reinterpret_cast(queue.second)); + devices.emplace(*pDevice, DeviceInfo { .device = *pDevice, .physicalDevice = physicalDevice, - .queue = Utils::findQueue(*pDevice, physicalDevice, pCreateInfo, - VK_QUEUE_GRAPHICS_BIT), - .frameGen = std::max(1, std::stoul(frameGen) - 1) + .queue = queue, + .frameGen = frameGen, }); } catch (const std::exception& e) { - Log::error("Failed to create device info: {}", e.what()); + Log::error("hooks", "Failed to create device info: {}", e.what()); return VK_ERROR_INITIALIZATION_FAILED; } - Log::info("lsfg-vk: Created Vulkan device"); + Log::info("hooks", "Device info created successfully for: {:x}", + reinterpret_cast(*pDevice)); return VK_SUCCESS; } void myvkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { devices.erase(device); // erase device info - Log::info("lsfg-vk: Destroyed Vulkan device"); + Log::info("hooks", "Device & Device info destroyed successfully: {:x}", + reinterpret_cast(device)); Layer::ovkDestroyDevice(device, pAllocator); } @@ -118,10 +147,25 @@ namespace { createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; // force vsync auto res = Layer::ovkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); if (res != VK_SUCCESS) { - Log::error("Failed to create swapchain: {:x}", static_cast(res)); + Log::error("hooks", "Failed to create swapchain: {:x}", static_cast(res)); return res; } + Log::info("hooks", "Swapchain created successfully: {:x}", + reinterpret_cast(*pSwapchain)); + // retire previous swapchain if it exists + if (pCreateInfo->oldSwapchain) { + Log::debug("hooks", "Retiring previous swapchain context: {:x}", + reinterpret_cast(pCreateInfo->oldSwapchain)); + swapchains.erase(pCreateInfo->oldSwapchain); + swapchainToDeviceTable.erase(pCreateInfo->oldSwapchain); + Log::info("hooks", "Previous swapchain context retired successfully: {:x}", + reinterpret_cast(pCreateInfo->oldSwapchain)); + } + + // create swapchain context + Log::debug("hooks", "Creating swapchain context for device: {:x}", + reinterpret_cast(device)); try { // get swapchain images uint32_t imageCount{}; @@ -133,24 +177,25 @@ namespace { res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, swapchainImages.data()); if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Failed to get swapchain images"); + Log::debug("hooks", "Swapchain has {} images", swapchainImages.size()); // create swapchain context swapchains.emplace(*pSwapchain, LsContext( deviceInfo, *pSwapchain, pCreateInfo->imageExtent, swapchainImages )); - swapchainToDeviceTable.emplace(*pSwapchain, device); } catch (const LSFG::vulkan_error& e) { - Log::error("Encountered Vulkan error {:x} while creating swapchain: {}", + Log::error("hooks", "Encountered Vulkan error {:x} while creating swapchain context: {}", static_cast(e.error()), e.what()); return e.error(); } catch (const std::exception& e) { - Log::error("Encountered error while creating swapchain: {}", e.what()); + Log::error("hooks", "Encountered error while creating swapchain context: {}", e.what()); return VK_ERROR_INITIALIZATION_FAILED; } - Log::info("lsfg-vk: Created swapchain with {} images", pCreateInfo->minImageCount); + Log::info("hooks", "Swapchain context created successfully for: {:x}", + reinterpret_cast(*pSwapchain)); return res; } @@ -160,21 +205,33 @@ namespace { auto& deviceInfo = devices.at(swapchainToDeviceTable.at(*pPresentInfo->pSwapchains)); auto& swapchain = swapchains.at(*pPresentInfo->pSwapchains); + Log::debug("hooks2", "Presenting swapchain: {:x} on queue: {:x}", + reinterpret_cast(*pPresentInfo->pSwapchains), + reinterpret_cast(queue)); + VkResult res{}; try { - std::vector waitSemaphores(pPresentInfo->waitSemaphoreCount); - std::copy_n(pPresentInfo->pWaitSemaphores, waitSemaphores.size(), waitSemaphores.data()); + std::vector semaphores(pPresentInfo->waitSemaphoreCount); + std::copy_n(pPresentInfo->pWaitSemaphores, semaphores.size(), semaphores.data()); + Log::debug("hooks2", "Waiting on {} semaphores", semaphores.size()); // present the next frame - return swapchain.present(deviceInfo, pPresentInfo->pNext, - queue, waitSemaphores, *pPresentInfo->pImageIndices); + res = swapchain.present(deviceInfo, pPresentInfo->pNext, + queue, semaphores, *pPresentInfo->pImageIndices); } catch (const LSFG::vulkan_error& e) { - Log::error("Encountered Vulkan error {:x} while presenting: {}", + Log::error("hooks2", "Encountered Vulkan error {:x} while presenting: {}", static_cast(e.error()), e.what()); return e.error(); } catch (const std::exception& e) { - Log::error("Encountered error while creating presenting: {}", e.what()); + Log::error("hooks2", "Encountered error while creating presenting: {}", + e.what()); return VK_ERROR_INITIALIZATION_FAILED; } + + // non VK_SUCCESS or VK_SUBOPTIMAL_KHR doesn't reach here + Log::debug("hooks2", "Presented swapchain {:x} on queue {:x} successfully", + reinterpret_cast(*pPresentInfo->pSwapchains), + reinterpret_cast(queue)); + return res; } void myvkDestroySwapchainKHR( @@ -184,7 +241,8 @@ namespace { swapchains.erase(swapchain); // erase swapchain context swapchainToDeviceTable.erase(swapchain); - Log::info("lsfg-vk: Destroyed swapchain"); + Log::info("hooks", "Swapchain & Swapchain context destroyed successfully: {:x}", + reinterpret_cast(swapchain)); Layer::ovkDestroySwapchainKHR(device, swapchain, pAllocator); } } diff --git a/src/layer.cpp b/src/layer.cpp index 007d2d4..d5ec26f 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -51,7 +51,7 @@ namespace { bool initInstanceFunc(VkInstance instance, const char* name, T* func) { *func = reinterpret_cast(next_vkGetInstanceProcAddr(instance, name)); if (!*func) { - Log::error("lsfg-vk(layer): Failed to get instance function pointer for {}", name); + Log::error("layer", "Failed to get instance function pointer for {}", name); return false; } return true; @@ -61,7 +61,7 @@ namespace { bool initDeviceFunc(VkDevice device, const char* name, T* func) { *func = reinterpret_cast(next_vkGetDeviceProcAddr(device, name)); if (!*func) { - Log::error("lsfg-vk(layer): Failed to get device function pointer for {}", name); + Log::error("layer", "Failed to get device function pointer for {}", name); return false; } return true; @@ -74,7 +74,7 @@ namespace { const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { - Log::debug("lsfg-vk(layer): Initializing lsfg-vk instance layer"); + Log::debug("layer", "Initializing lsfg-vk instance layer..."); // find layer creation info auto* layerDesc = const_cast( @@ -85,12 +85,15 @@ namespace { reinterpret_cast(layerDesc->pNext)); } if (!layerDesc) { - Log::error("lsfg-vk(layer): No layer creation info found in pNext chain"); + Log::error("layer", "No layer creation info found in pNext chain"); return VK_ERROR_INITIALIZATION_FAILED; } // advance link info (i don't really know what this does) next_vkGetInstanceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetInstanceProcAddr; + Log::debug("layer", "Next instance proc addr: {:x}", + reinterpret_cast(next_vkGetInstanceProcAddr)); + layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; // create instance @@ -101,7 +104,7 @@ namespace { Hooks::hooks["vkCreateInstance"]); auto res = layer_vkCreateInstance2(pCreateInfo, pAllocator, pInstance); if (res != VK_SUCCESS) { - Log::error("lsfg-vk(layer): Failed to create Vulkan instance: {:x}", + Log::error("layer", "Failed to create Vulkan instance: {:x}", static_cast(res)); return res; } @@ -112,11 +115,11 @@ namespace { success &= initInstanceFunc(*pInstance, "vkGetPhysicalDeviceQueueFamilyProperties", &next_vkGetPhysicalDeviceQueueFamilyProperties); success &= initInstanceFunc(*pInstance, "vkGetPhysicalDeviceMemoryProperties", &next_vkGetPhysicalDeviceMemoryProperties); if (!success) { - Log::error("lsfg-vk(layer): Failed to get instance function pointers"); + Log::error("layer", "Failed to get instance function pointers"); return VK_ERROR_INITIALIZATION_FAILED; } - Log::debug("lsfg-vk(layer): Successfully initialized lsfg-vk instance layer"); + Log::debug("layer", "Successfully initialized lsfg-vk instance layer"); return res; } // NOLINTEND @@ -125,7 +128,7 @@ namespace { const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { - Log::debug("lsfg-vk(layer): Initializing lsfg-vk device layer"); + Log::debug("layer", "Initializing lsfg-vk device layer..."); // find layer creation info auto* layerDesc = const_cast( @@ -136,24 +139,26 @@ namespace { reinterpret_cast(layerDesc->pNext)); } if (!layerDesc) { - Log::error("lsfg-vk(layer): No layer creation info found in pNext chain"); + Log::error("layer", "No layer creation info found in pNext chain"); return VK_ERROR_INITIALIZATION_FAILED; } // advance link info (i don't really know what this does) next_vkGetDeviceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetDeviceProcAddr; + Log::debug("layer", "Next device proc addr: {:x}", + reinterpret_cast(next_vkGetDeviceProcAddr)); + layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; // create device - auto success = initInstanceFunc(nullptr, "vkCreateDevice", - &next_vkCreateDevice); + auto success = initInstanceFunc(nullptr, "vkCreateDevice", &next_vkCreateDevice); if (!success) return VK_ERROR_INITIALIZATION_FAILED; auto* layer_vkCreateDevice2 = reinterpret_cast( Hooks::hooks["vkCreateDevicePre"]); auto res = layer_vkCreateDevice2(physicalDevice, pCreateInfo, pAllocator, pDevice); if (res != VK_SUCCESS) { - Log::error("lsfg-vk(layer): Failed to create Vulkan device: {:x}", + Log::error("layer", "Failed to create Vulkan device: {:x}", static_cast(res)); return res; } @@ -187,7 +192,7 @@ namespace { success &= initDeviceFunc(*pDevice, "vkCmdCopyImage", &next_vkCmdCopyImage); success &= initDeviceFunc(*pDevice, "vkAcquireNextImageKHR", &next_vkAcquireNextImageKHR); if (!success) { - Log::error("lsfg-vk(layer): Failed to get device function pointers"); + Log::error("layer", "Failed to get device function pointers"); return VK_ERROR_INITIALIZATION_FAILED; } @@ -195,12 +200,12 @@ namespace { Hooks::hooks["vkCreateDevicePost"]); res = layer_vkCreateDevice2(physicalDevice, pCreateInfo, pAllocator, pDevice); if (res != VK_SUCCESS) { - Log::error("lsfg-vk(layer): Failed to create Vulkan device: {:x}", + Log::error("layer", "Failed to create Vulkan device: {:x}", static_cast(res)); return res; } - Log::debug("lsfg-vk(layer): Successfully initialized lsfg-vk device layer"); + Log::debug("layer", "Successfully initialized lsfg-vk device layer"); return res; } // NOLINTEND } @@ -217,16 +222,16 @@ const std::unordered_map layerFunctions = { }; PFN_vkVoidFunction layer_vkGetInstanceProcAddr(VkInstance instance, const char* pName) { - std::string name(pName); + const std::string name(pName); auto it = layerFunctions.find(name); if (it != layerFunctions.end()) { - Log::debug("lsfg-vk(layer): Inserted layer function for {}", name); + Log::debug("layer", "Inserted layer function for {}", name); return it->second; } it = Hooks::hooks.find(name); if (it != Hooks::hooks.end()) { - Log::debug("lsfg-vk(layer): Inserted hook function for {}", name); + Log::debug("layer", "Inserted hook function for {}", name); return it->second; } @@ -234,16 +239,16 @@ PFN_vkVoidFunction layer_vkGetInstanceProcAddr(VkInstance instance, const char* } PFN_vkVoidFunction layer_vkGetDeviceProcAddr(VkDevice device, const char* pName) { - std::string name(pName); + const std::string name(pName); auto it = layerFunctions.find(name); if (it != layerFunctions.end()) { - Log::debug("lsfg-vk(layer): Inserted layer function for {}", name); + Log::debug("layer", "Inserted layer function for {}", name); return it->second; } it = Hooks::hooks.find(name); if (it != Hooks::hooks.end()) { - Log::debug("lsfg-vk(layer): Inserted hook function for {}", name); + Log::debug("layer", "Inserted hook function for {}", name); return it->second; } @@ -252,15 +257,29 @@ PFN_vkVoidFunction layer_vkGetDeviceProcAddr(VkDevice device, const char* pName) // original functions +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +// NOLINTBEGIN + VkResult Layer::ovkCreateInstance( const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { - return next_vkCreateInstance(pCreateInfo, pAllocator, pInstance); + Log::debug("vulkan", "vkCreateInstance called with {} extensions:", + pCreateInfo->enabledExtensionCount); + for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; ++i) + Log::debug("vulkan", " - {}", pCreateInfo->ppEnabledExtensionNames[i]); + auto res = next_vkCreateInstance(pCreateInfo, pAllocator, pInstance); + Log::debug("vulkan", "vkCreateInstance({}) returned handle {:x}", + static_cast(res), + reinterpret_cast(*pInstance)); + return res; } void Layer::ovkDestroyInstance( VkInstance instance, const VkAllocationCallbacks* pAllocator) { + Log::debug("vulkan", "vkDestroyInstance called for instance {:x}", + reinterpret_cast(instance)); next_vkDestroyInstance(instance, pAllocator); } @@ -269,12 +288,24 @@ VkResult Layer::ovkCreateDevice( const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { - return next_vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); + Log::debug("vulkan", "vkCreateDevice called with {} extensions:", + pCreateInfo->enabledExtensionCount); + for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; ++i) + Log::debug("vulkan", " - {}", pCreateInfo->ppEnabledExtensionNames[i]); + auto res = next_vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); + Log::debug("vulkan", "vkCreateDevice({}) returned handle {:x}", + static_cast(res), + reinterpret_cast(*pDevice)); + return res; } void Layer::ovkDestroyDevice( VkDevice device, const VkAllocationCallbacks* pAllocator) { + Log::debug("vulkan", "vkDestroyDevice called for device {:x}", + reinterpret_cast(device)); next_vkDestroyDevice(device, pAllocator); + Log::debug("vulkan", "Device {:x} destroyed successfully", + reinterpret_cast(device)); } PFN_vkVoidFunction Layer::ovkGetInstanceProcAddr( @@ -293,17 +324,39 @@ VkResult Layer::ovkCreateSwapchainKHR( const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain) { - return next_vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + Log::debug("vulkan", "vkCreateSwapchainKHR called with {} images, extent: {}x{}", + pCreateInfo->minImageCount, pCreateInfo->imageExtent.width, pCreateInfo->imageExtent.height); + auto res = next_vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + Log::debug("vulkan", "vkCreateSwapchainKHR({}) returned handle {:x}", + static_cast(res), + reinterpret_cast(*pSwapchain)); + return res; } VkResult Layer::ovkQueuePresentKHR( VkQueue queue, const VkPresentInfoKHR* pPresentInfo) { - return next_vkQueuePresentKHR(queue, pPresentInfo); + Log::debug("vulkan2", "vkQueuePresentKHR called with {} wait semaphores:", + pPresentInfo->waitSemaphoreCount); + for (uint32_t i = 0; i < pPresentInfo->waitSemaphoreCount; i++) + Log::debug("vulkan2", " - {:x}", reinterpret_cast(pPresentInfo->pWaitSemaphores[i])); + Log::debug("vulkan2", "and {} signal semaphores:", + pPresentInfo->swapchainCount); + for (uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) + Log::debug("vulkan2", " - {:x}", reinterpret_cast(pPresentInfo->pSwapchains[i])); + Log::debug("vulkan2", "and queue: {:x}, image: {}", + reinterpret_cast(queue), + *pPresentInfo->pImageIndices); + auto res = next_vkQueuePresentKHR(queue, pPresentInfo); + Log::debug("vulkan2", "vkQueuePresentKHR({}) returned", + static_cast(res)); + return res; } void Layer::ovkDestroySwapchainKHR( VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* pAllocator) { + Log::debug("vulkan", "vkDestroySwapchainKHR called for swapchain {:x}", + reinterpret_cast(swapchain)); next_vkDestroySwapchainKHR(device, swapchain, pAllocator); } @@ -312,30 +365,48 @@ VkResult Layer::ovkGetSwapchainImagesKHR( VkSwapchainKHR swapchain, uint32_t* pSwapchainImageCount, VkImage* pSwapchainImages) { - return next_vkGetSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, pSwapchainImages); + Log::debug("vulkan", "vkGetSwapchainImagesKHR called for swapchain {:x}", + reinterpret_cast(swapchain)); + auto res = next_vkGetSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, pSwapchainImages); + Log::debug("vulkan", "vkGetSwapchainImagesKHR({}) returned {} images", + static_cast(res), + *pSwapchainImageCount); + return res; } VkResult Layer::ovkAllocateCommandBuffers( VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers) { - return next_vkAllocateCommandBuffers(device, pAllocateInfo, pCommandBuffers); + Log::debug("vulkan2", "vkAllocateCommandBuffers called for command pool {:x}", + reinterpret_cast(pAllocateInfo->commandPool)); + auto res = next_vkAllocateCommandBuffers(device, pAllocateInfo, pCommandBuffers); + Log::debug("vulkan2", "vkAllocateCommandBuffers({}) returned command buffer: {}", + static_cast(res), + reinterpret_cast(*pCommandBuffers)); + return res; } void Layer::ovkFreeCommandBuffers( VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers) { + Log::debug("vulkan2", "vkFreeCommandBuffers called for command buffer: {:x}", + reinterpret_cast(*pCommandBuffers)); next_vkFreeCommandBuffers(device, commandPool, commandBufferCount, pCommandBuffers); } VkResult Layer::ovkBeginCommandBuffer( VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo) { + Log::debug("vulkan2", "vkBeginCommandBuffer called for command buffer {:x}", + reinterpret_cast(commandBuffer)); return next_vkBeginCommandBuffer(commandBuffer, pBeginInfo); } VkResult Layer::ovkEndCommandBuffer( VkCommandBuffer commandBuffer) { + Log::debug("vulkan2", "vkEndCommandBuffer called for command buffer {:x}", + reinterpret_cast(commandBuffer)); return next_vkEndCommandBuffer(commandBuffer); } @@ -344,12 +415,19 @@ VkResult Layer::ovkCreateCommandPool( const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool) { - return next_vkCreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool); + Log::debug("vulkan", "vkCreateCommandPool called"); + auto res = next_vkCreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool); + Log::debug("vulkan", "vkCreateCommandPool({}) returned handle {:x}", + static_cast(res), + reinterpret_cast(*pCommandPool)); + return res; } void Layer::ovkDestroyCommandPool( VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks* pAllocator) { + Log::debug("vulkan", "vkDestroyCommandPool called for command pool {:x}", + reinterpret_cast(commandPool)); next_vkDestroyCommandPool(device, commandPool, pAllocator); } @@ -358,12 +436,22 @@ VkResult Layer::ovkCreateImage( const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage) { - return next_vkCreateImage(device, pCreateInfo, pAllocator, pImage); + Log::debug("vulkan", "vkCreateImage called with format: {}, extent: {}x{}, usage: {}", + static_cast(pCreateInfo->format), + pCreateInfo->extent.width, pCreateInfo->extent.height, + static_cast(pCreateInfo->usage)); + auto res = next_vkCreateImage(device, pCreateInfo, pAllocator, pImage); + Log::debug("vulkan", "vkCreateImage({}) returned handle {:x}", + static_cast(res), + reinterpret_cast(*pImage)); + return res; } void Layer::ovkDestroyImage( VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator) { + Log::debug("vulkan", "vkDestroyImage called for image {:x}", + reinterpret_cast(image)); next_vkDestroyImage(device, image, pAllocator); } @@ -371,6 +459,8 @@ void Layer::ovkGetImageMemoryRequirements( VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements) { + Log::debug("vulkan", "vkGetImageMemoryRequirements called for image {:x}", + reinterpret_cast(image)); next_vkGetImageMemoryRequirements(device, image, pMemoryRequirements); } VkResult Layer::ovkBindImageMemory( @@ -378,7 +468,14 @@ VkResult Layer::ovkBindImageMemory( VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset) { - return next_vkBindImageMemory(device, image, memory, memoryOffset); + Log::debug("vulkan", "vkBindImageMemory called for image {:x}, memory {:x}, offset: {}", + reinterpret_cast(image), + reinterpret_cast(memory), + memoryOffset); + auto res = next_vkBindImageMemory(device, image, memory, memoryOffset); + Log::debug("vulkan", "vkBindImageMemory({}) returned", + static_cast(res)); + return res; } VkResult Layer::ovkAllocateMemory( @@ -386,12 +483,21 @@ VkResult Layer::ovkAllocateMemory( const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory) { - return next_vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); + Log::debug("vulkan", "vkAllocateMemory called with size: {}, memory type index: {}", + pAllocateInfo->allocationSize, + pAllocateInfo->memoryTypeIndex); + auto res = next_vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); + Log::debug("vulkan", "vkAllocateMemory({}) returned handle {:x}", + static_cast(res), + reinterpret_cast(*pMemory)); + return res; } void Layer::ovkFreeMemory( VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator) { + Log::debug("vulkan", "vkFreeMemory called for memory {:x}", + reinterpret_cast(memory)); next_vkFreeMemory(device, memory, pAllocator); } @@ -400,12 +506,20 @@ VkResult Layer::ovkCreateSemaphore( const VkSemaphoreCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSemaphore* pSemaphore) { - return next_vkCreateSemaphore(device, pCreateInfo, pAllocator, pSemaphore); + Log::debug("vulkan2", "vkCreateSemaphore called", + static_cast(pCreateInfo->flags)); + auto res = next_vkCreateSemaphore(device, pCreateInfo, pAllocator, pSemaphore); + Log::debug("vulkan2", "vkCreateSemaphore({}) returned handle {:x}", + static_cast(res), + reinterpret_cast(*pSemaphore)); + return res; } void Layer::ovkDestroySemaphore( VkDevice device, VkSemaphore semaphore, const VkAllocationCallbacks* pAllocator) { + Log::debug("vulkan2", "vkDestroySemaphore called for semaphore {:x}", + reinterpret_cast(semaphore)); next_vkDestroySemaphore(device, semaphore, pAllocator); } @@ -413,24 +527,39 @@ VkResult Layer::ovkGetMemoryFdKHR( VkDevice device, const VkMemoryGetFdInfoKHR* pGetFdInfo, int* pFd) { - return next_vkGetMemoryFdKHR(device, pGetFdInfo, pFd); + Log::debug("vulkan", "vkGetMemoryFdKHR called for memory {:x}, handle type: {}", + reinterpret_cast(pGetFdInfo->memory), + static_cast(pGetFdInfo->handleType)); + auto res = next_vkGetMemoryFdKHR(device, pGetFdInfo, pFd); + Log::debug("vulkan", "vkGetMemoryFdKHR({}) returned fd: {}", + static_cast(res), *pFd); + return res; } VkResult Layer::ovkGetSemaphoreFdKHR( VkDevice device, const VkSemaphoreGetFdInfoKHR* pGetFdInfo, int* pFd) { - return next_vkGetSemaphoreFdKHR(device, pGetFdInfo, pFd); + Log::debug("vulkan2", "vkGetSemaphoreFdKHR called for semaphore {:x}", + reinterpret_cast(pGetFdInfo->semaphore)); + auto res = next_vkGetSemaphoreFdKHR(device, pGetFdInfo, pFd); + Log::debug("vulkan2", "vkGetSemaphoreFdKHR({}) returned fd: {}", + static_cast(res), *pFd); + return res; } void Layer::ovkGetPhysicalDeviceQueueFamilyProperties( VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties) { + Log::debug("vulkan", "vkGetPhysicalDeviceQueueFamilyProperties called for physical device {:x}", + reinterpret_cast(physicalDevice)); next_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties); } void Layer::ovkGetPhysicalDeviceMemoryProperties( VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties) { + Log::debug("vulkan", "vkGetPhysicalDeviceMemoryProperties called for physical device {:x}", + reinterpret_cast(physicalDevice)); next_vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties); } @@ -439,6 +568,10 @@ void Layer::ovkGetDeviceQueue( uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue) { + Log::debug("vulkan", "vkGetDeviceQueue called for device {:x}, queue family index: {}, queue index: {}", + reinterpret_cast(device), + queueFamilyIndex, + queueIndex); next_vkGetDeviceQueue(device, queueFamilyIndex, queueIndex, pQueue); } VkResult Layer::ovkQueueSubmit( @@ -446,7 +579,21 @@ VkResult Layer::ovkQueueSubmit( uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence) { - return next_vkQueueSubmit(queue, submitCount, pSubmits, fence); + Log::debug("vulkan2", "vkQueueSubmit called for queue {:x}, submitting: {} with wait semaphores:", + reinterpret_cast(queue), + reinterpret_cast(*pSubmits->pCommandBuffers)); + for (uint32_t i = 0; i < pSubmits->waitSemaphoreCount; ++i) + Log::debug("vulkan2", " - {:x}", reinterpret_cast(pSubmits->pWaitSemaphores[i])); + Log::debug("vulkan2", "and {} signal semaphores:", + pSubmits->waitSemaphoreCount); + for (uint32_t i = 0; i < submitCount; ++i) + Log::debug("vulkan2", " - {:x}", reinterpret_cast(pSubmits[i].pSignalSemaphores)); + Log::debug("vulkan2", "and fence: {:x}", + reinterpret_cast(fence)); + auto res = next_vkQueueSubmit(queue, submitCount, pSubmits, fence); + Log::debug("vulkan2", "vkQueueSubmit({}) returned", + static_cast(res)); + return res; } void Layer::ovkCmdPipelineBarrier( @@ -460,6 +607,16 @@ void Layer::ovkCmdPipelineBarrier( const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers) { + Log::debug("vulkan2", "vkCmdPipelineBarrier called for command buffer {:x}, src stage: {}, dst stage: {}, transitioning:", + reinterpret_cast(commandBuffer), + static_cast(srcStageMask), + static_cast(dstStageMask)); + for (uint32_t i = 0; i < imageMemoryBarrierCount; ++i) { + Log::debug("vulkan2", " - image {:x}, old layout: {}, new layout: {}", + reinterpret_cast(pImageMemoryBarriers[i].image), + static_cast(pImageMemoryBarriers[i].oldLayout), + static_cast(pImageMemoryBarriers[i].newLayout)); + } next_vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, @@ -473,6 +630,10 @@ void Layer::ovkCmdCopyImage( VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageCopy* pRegions) { + Log::debug("vulkan2", "vkCmdCopyImage called for command buffer {:x}, src image {:x}, dst image {:x}", + reinterpret_cast(commandBuffer), + reinterpret_cast(srcImage), + reinterpret_cast(dstImage)); next_vkCmdCopyImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions); } @@ -483,5 +644,17 @@ VkResult Layer::ovkAcquireNextImageKHR( VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex) { - return next_vkAcquireNextImageKHR(device, swapchain, timeout, semaphore, fence, pImageIndex); + Log::debug("vulkan", "vkAcquireNextImageKHR called for swapchain {:x}, timeout: {}, semaphore: {:x}, fence: {:x}", + reinterpret_cast(swapchain), + timeout, + reinterpret_cast(semaphore), + reinterpret_cast(fence)); + auto res = next_vkAcquireNextImageKHR(device, swapchain, timeout, semaphore, fence, pImageIndex); + Log::debug("vulkan", "vkAcquireNextImageKHR({}) returned image index: {}", + static_cast(res), + *pImageIndex); + return res; } + +#pragma clang diagnostic pop +// NOLINTEND diff --git a/src/utils/log.cpp b/src/utils/log.cpp new file mode 100644 index 0000000..21405d0 --- /dev/null +++ b/src/utils/log.cpp @@ -0,0 +1,43 @@ +#include "utils/log.hpp" + +#include +#include + +using namespace Log; + +bool Internal::isSetup{}; + +std::set Internal::debugModules; +bool Internal::debugAllModules{}; + +std::ofstream Internal::logFile; +std::mutex Internal::logMutex; + +void Internal::setup() { + if (isSetup) return; + isSetup = true; + + // open log file + const char* env_log_file = std::getenv("LSFG_LOG_FILE"); + if (env_log_file) { + std::ostringstream filename; + filename << getpid() << "_" << env_log_file; + logFile.open(filename.str(), std::ios::app); + } + + // parse debug modules + const char* env_log_debug = std::getenv("LSFG_LOG_DEBUG"); + if (!env_log_debug) + return; + const std::string debugModulesStr(env_log_debug); + + std::stringstream ss(debugModulesStr); + std::string item; + while (std::getline(ss, item, ',')) { + if (item == "all") { + debugAllModules = true; + return; + } + debugModules.insert(item); + } + } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index b32e0e6..8621f76 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -1,4 +1,5 @@ #include "utils/utils.hpp" +#include "utils/log.hpp" #include "layer.hpp" #include @@ -44,8 +45,12 @@ std::vector Utils::addExtensions(const char* const* extensions, siz for (const auto& e : requiredExtensions) { auto it = std::ranges::find(ext, e); - if (it == ext.end()) + if (it == ext.end()) { + Log::debug("hooks-init", "Adding extension: {}", e); ext.push_back(e); + } else { + Log::debug("hooks-init", "Extension {} already present", e); + } } return ext; @@ -148,6 +153,8 @@ namespace { std::optional layersEnvironment; } +// TODO: more environment variables? what about explicit disable? + void Utils::storeLayerEnv() { const char* env = std::getenv("VK_INSTANCE_LAYERS"); if (env) From 4ad1ea8523c2e59ac4d26aeda1c95550c2cf41f0 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 14:52:45 +0200 Subject: [PATCH 085/253] use same device for subcontext fixes #13 --- include/layer.hpp | 24 ++++++++------- include/utils/utils.hpp | 8 +++++ lsfg-vk-gen/src/core/device.cpp | 18 ++++++++--- src/context.cpp | 4 +++ src/layer.cpp | 53 ++++++++++++++++++++------------- src/utils/utils.cpp | 8 ++++- 6 files changed, 80 insertions(+), 35 deletions(-) diff --git a/include/layer.hpp b/include/layer.hpp index d06bbb4..87c6857 100644 --- a/include/layer.hpp +++ b/include/layer.hpp @@ -34,6 +34,20 @@ namespace Layer { 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 vkCreateSwapchainKHR function. VkResult ovkCreateSwapchainKHR( VkDevice device, @@ -147,16 +161,6 @@ namespace Layer { const VkSemaphoreGetFdInfoKHR* pGetFdInfo, int* pFd); - /// 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 vkGetDeviceQueue function. void ovkGetDeviceQueue( VkDevice device, diff --git a/include/utils/utils.hpp b/include/utils/utils.hpp index fb6d8b1..147bfe3 100644 --- a/include/utils/utils.hpp +++ b/include/utils/utils.hpp @@ -22,6 +22,14 @@ namespace Utils { std::pair findQueue(VkDevice device, VkPhysicalDevice physicalDevice, VkDeviceCreateInfo* desc, VkQueueFlags flags); + /// + /// Get the UUID of the physical device. + /// + /// @param physicalDevice The physical device to get the UUID from. + /// @return The UUID of the physical device. + /// + uint64_t getDeviceUUID(VkPhysicalDevice physicalDevice); + /// /// Ensure a list of extensions is present in the given array. /// diff --git a/lsfg-vk-gen/src/core/device.cpp b/lsfg-vk-gen/src/core/device.cpp index ebfbd3f..098ebbf 100644 --- a/lsfg-vk-gen/src/core/device.cpp +++ b/lsfg-vk-gen/src/core/device.cpp @@ -24,18 +24,28 @@ Device::Device(const Instance& instance) { if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Failed to get physical devices"); + // get uuid env vars + const char* deviceUUIDEnv = std::getenv("LSFG_DEVICE_UUID"); + if (!deviceUUIDEnv) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "LSFG_DEVICE_UUID environment variable not set"); + const uint64_t deviceUUID = std::stoull(deviceUUIDEnv); + // find first discrete GPU std::optional physicalDevice; for (const auto& device : devices) { VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(device, &properties); - physicalDevice = device; - if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) - break; // dedicated will always work + const uint64_t uuid = static_cast(properties.vendorID) << 32 | properties.deviceID; + if (deviceUUID == uuid) { + physicalDevice = device; + break; + } } if (!physicalDevice) - throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "No discrete GPU found"); + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "Could not find physical device with UUID"); // find queue family indices uint32_t familyCount{}; diff --git a/src/context.cpp b/src/context.cpp index 2816f41..76f8ae4 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -3,6 +3,7 @@ #include "utils/log.hpp" #include "utils/utils.hpp" +#include #include #include @@ -46,6 +47,9 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, } // initialize lsfg + const uint64_t deviceUUID = Utils::getDeviceUUID(info.physicalDevice); + setenv("LSFG_DEVICE_UUID", std::to_string(deviceUUID).c_str(), 1); + Log::debug("context", "(entering LSFG initialization)"); Utils::storeLayerEnv(); LSFG::initialize(); diff --git a/src/layer.cpp b/src/layer.cpp index d5ec26f..af0bf5c 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -19,6 +19,10 @@ namespace { PFN_vkGetInstanceProcAddr next_vkGetInstanceProcAddr{}; PFN_vkGetDeviceProcAddr next_vkGetDeviceProcAddr{}; + PFN_vkGetPhysicalDeviceQueueFamilyProperties next_vkGetPhysicalDeviceQueueFamilyProperties{}; + PFN_vkGetPhysicalDeviceMemoryProperties next_vkGetPhysicalDeviceMemoryProperties{}; + PFN_vkGetPhysicalDeviceProperties next_vkGetPhysicalDeviceProperties{}; + PFN_vkCreateSwapchainKHR next_vkCreateSwapchainKHR{}; PFN_vkQueuePresentKHR next_vkQueuePresentKHR{}; PFN_vkDestroySwapchainKHR next_vkDestroySwapchainKHR{}; @@ -39,8 +43,6 @@ namespace { PFN_vkDestroySemaphore next_vkDestroySemaphore{}; PFN_vkGetMemoryFdKHR next_vkGetMemoryFdKHR{}; PFN_vkGetSemaphoreFdKHR next_vkGetSemaphoreFdKHR{}; - PFN_vkGetPhysicalDeviceQueueFamilyProperties next_vkGetPhysicalDeviceQueueFamilyProperties{}; - PFN_vkGetPhysicalDeviceMemoryProperties next_vkGetPhysicalDeviceMemoryProperties{}; PFN_vkGetDeviceQueue next_vkGetDeviceQueue{}; PFN_vkQueueSubmit next_vkQueueSubmit{}; PFN_vkCmdPipelineBarrier next_vkCmdPipelineBarrier{}; @@ -112,8 +114,12 @@ namespace { // get relevant function pointers from the next layer success = true; success &= initInstanceFunc(*pInstance, "vkDestroyInstance", &next_vkDestroyInstance); - success &= initInstanceFunc(*pInstance, "vkGetPhysicalDeviceQueueFamilyProperties", &next_vkGetPhysicalDeviceQueueFamilyProperties); - success &= initInstanceFunc(*pInstance, "vkGetPhysicalDeviceMemoryProperties", &next_vkGetPhysicalDeviceMemoryProperties); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceQueueFamilyProperties", &next_vkGetPhysicalDeviceQueueFamilyProperties); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceMemoryProperties", &next_vkGetPhysicalDeviceMemoryProperties); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceProperties", &next_vkGetPhysicalDeviceProperties); if (!success) { Log::error("layer", "Failed to get instance function pointers"); return VK_ERROR_INITIALIZATION_FAILED; @@ -319,6 +325,29 @@ PFN_vkVoidFunction Layer::ovkGetDeviceProcAddr( return next_vkGetDeviceProcAddr(device, pName); } +void Layer::ovkGetPhysicalDeviceQueueFamilyProperties( + VkPhysicalDevice physicalDevice, + uint32_t* pQueueFamilyPropertyCount, + VkQueueFamilyProperties* pQueueFamilyProperties) { + Log::debug("vulkan", "vkGetPhysicalDeviceQueueFamilyProperties called for physical device {:x}", + reinterpret_cast(physicalDevice)); + next_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties); +} +void Layer::ovkGetPhysicalDeviceMemoryProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceMemoryProperties* pMemoryProperties) { + Log::debug("vulkan", "vkGetPhysicalDeviceMemoryProperties called for physical device {:x}", + reinterpret_cast(physicalDevice)); + next_vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties); +} +void Layer::ovkGetPhysicalDeviceProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceProperties* pProperties) { + Log::debug("vulkan", "vkGetPhysicalDeviceProperties called for physical device {:x}", + reinterpret_cast(physicalDevice)); + next_vkGetPhysicalDeviceProperties(physicalDevice, pProperties); +} + VkResult Layer::ovkCreateSwapchainKHR( VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo, @@ -547,22 +576,6 @@ VkResult Layer::ovkGetSemaphoreFdKHR( return res; } -void Layer::ovkGetPhysicalDeviceQueueFamilyProperties( - VkPhysicalDevice physicalDevice, - uint32_t* pQueueFamilyPropertyCount, - VkQueueFamilyProperties* pQueueFamilyProperties) { - Log::debug("vulkan", "vkGetPhysicalDeviceQueueFamilyProperties called for physical device {:x}", - reinterpret_cast(physicalDevice)); - next_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties); -} -void Layer::ovkGetPhysicalDeviceMemoryProperties( - VkPhysicalDevice physicalDevice, - VkPhysicalDeviceMemoryProperties* pMemoryProperties) { - Log::debug("vulkan", "vkGetPhysicalDeviceMemoryProperties called for physical device {:x}", - reinterpret_cast(physicalDevice)); - next_vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties); -} - void Layer::ovkGetDeviceQueue( VkDevice device, uint32_t queueFamilyIndex, diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 8621f76..a50c067 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -6,7 +6,6 @@ #include #include -#include using namespace Utils; @@ -38,6 +37,13 @@ std::pair Utils::findQueue(VkDevice device, VkPhysicalDevice return { *idx, queue }; } +uint64_t Utils::getDeviceUUID(VkPhysicalDevice physicalDevice) { + VkPhysicalDeviceProperties properties{}; + Layer::ovkGetPhysicalDeviceProperties(physicalDevice, &properties); + + return static_cast(properties.vendorID) << 32 | properties.deviceID; +} + std::vector Utils::addExtensions(const char* const* extensions, size_t count, const std::vector& requiredExtensions) { std::vector ext(count); From 27eeea3d0cabfbc90fb8326b4c76ff8fc39c6775 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 16:14:53 +0200 Subject: [PATCH 086/253] replace copy with blit, allowing for most swapchain formats fixes #1 --- include/layer.hpp | 7 ++++--- src/layer.cpp | 13 +++++++------ src/utils/utils.cpp | 21 ++++++++++++++------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/include/layer.hpp b/include/layer.hpp index 87c6857..a7dd653 100644 --- a/include/layer.hpp +++ b/include/layer.hpp @@ -186,15 +186,16 @@ namespace Layer { const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers); - /// Call to the original vkCmdCopyImage function. - void ovkCmdCopyImage( + /// Call to the original vkCmdBlitImage function. + void ovkCmdBlitImage( VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, - const VkImageCopy* pRegions); + const VkImageBlit* pRegions, + VkFilter filter); /// Call to the original vkAcquireNextImageKHR function. VkResult ovkAcquireNextImageKHR( diff --git a/src/layer.cpp b/src/layer.cpp index af0bf5c..5b8f952 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -46,7 +46,7 @@ namespace { PFN_vkGetDeviceQueue next_vkGetDeviceQueue{}; PFN_vkQueueSubmit next_vkQueueSubmit{}; PFN_vkCmdPipelineBarrier next_vkCmdPipelineBarrier{}; - PFN_vkCmdCopyImage next_vkCmdCopyImage{}; + PFN_vkCmdBlitImage next_vkCmdBlitImage{}; PFN_vkAcquireNextImageKHR next_vkAcquireNextImageKHR{}; template @@ -195,7 +195,7 @@ namespace { success &= initDeviceFunc(*pDevice, "vkGetDeviceQueue", &next_vkGetDeviceQueue); success &= initDeviceFunc(*pDevice, "vkQueueSubmit", &next_vkQueueSubmit); success &= initDeviceFunc(*pDevice, "vkCmdPipelineBarrier", &next_vkCmdPipelineBarrier); - success &= initDeviceFunc(*pDevice, "vkCmdCopyImage", &next_vkCmdCopyImage); + success &= initDeviceFunc(*pDevice, "vkCmdBlitImage", &next_vkCmdBlitImage); success &= initDeviceFunc(*pDevice, "vkAcquireNextImageKHR", &next_vkAcquireNextImageKHR); if (!success) { Log::error("layer", "Failed to get device function pointers"); @@ -635,19 +635,20 @@ void Layer::ovkCmdPipelineBarrier( bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers); } -void Layer::ovkCmdCopyImage( +void Layer::ovkCmdBlitImage( VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, - const VkImageCopy* pRegions) { - Log::debug("vulkan2", "vkCmdCopyImage called for command buffer {:x}, src image {:x}, dst image {:x}", + const VkImageBlit* pRegions, + VkFilter filter) { + Log::debug("vulkan2", "vkCmdBlitImage called for command buffer {:x}, src image {:x}, dst image {:x}", reinterpret_cast(commandBuffer), reinterpret_cast(srcImage), reinterpret_cast(dstImage)); - next_vkCmdCopyImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions); + next_vkCmdBlitImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions, filter); } VkResult Layer::ovkAcquireNextImageKHR( diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index a50c067..0bc4ee4 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -6,6 +6,7 @@ #include #include +#include using namespace Utils; @@ -96,25 +97,31 @@ void Utils::copyImage(VkCommandBuffer buf, 0, nullptr, 0, nullptr, static_cast(barriers.size()), barriers.data()); - const VkImageCopy imageCopy{ + const VkImageBlit imageBlit{ .srcSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = 1 }, + .srcOffsets = { + { 0, 0, 0 }, + { static_cast(width), static_cast(height), 1 } + }, .dstSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = 1 }, - .extent = { - .width = width, - .height = height, - .depth = 1 + .dstOffsets = { + { 0, 0, 0 }, + { static_cast(width), static_cast(height), 1 } } }; - Layer::ovkCmdCopyImage(buf, + Layer::ovkCmdBlitImage( + buf, src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, &imageCopy); + 1, &imageBlit, + VK_FILTER_NEAREST + ); if (makeSrcPresentable) { const VkImageMemoryBarrier presentBarrier{ From 48c773574a2b568b611490c421133a536f5b1521 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 16:54:00 +0200 Subject: [PATCH 087/253] enhancement: better default path logic fixes #16 --- lsfg-vk-gen/src/lsfg.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp index 5f39ea4..f75286d 100644 --- a/lsfg-vk-gen/src/lsfg.cpp +++ b/lsfg-vk-gen/src/lsfg.cpp @@ -25,7 +25,22 @@ void LSFG::initialize() { return; char* dllPath = getenv("LSFG_DLL_PATH"); - const std::string dllPathStr = dllPath ? std::string(dllPath) : "Lossless.dll"; + std::string dllPathStr; + if (dllPath && *dllPath != '\0') { + dllPathStr = std::string(dllPath); + } else { + std::string baseDirStr; + + const char* baseDir = getenv("XDG_DATA_HOME"); + if (!baseDir || *baseDir == '\0') + baseDir = getenv("HOME"); + if (!baseDir || *baseDir == '\0') + baseDir = ""; + baseDirStr = std::string(baseDir); + + dllPathStr = baseDirStr + + "/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; + } instance.emplace(); device.emplace(*instance); From 2fbdacc9fa6a15d14b5b8dadaf6c4698534f1eb0 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 17:23:00 +0200 Subject: [PATCH 088/253] switching to an implicit layer --- CMakeLists.txt | 2 +- README.md | 42 ++------------------------------ VkLayer_LS_frame_generation.json | 6 +++++ include/utils/utils.hpp | 10 -------- src/context.cpp | 5 +--- src/layer.cpp | 1 + src/utils/utils.cpp | 22 ----------------- 7 files changed, 11 insertions(+), 77 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a96a1f1..23c6afb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,4 +60,4 @@ target_compile_options(lsfg-vk PRIVATE install(FILES "${CMAKE_BINARY_DIR}/liblsfg-vk.so" DESTINATION lib) install(FILES "${CMAKE_SOURCE_DIR}/VkLayer_LS_frame_generation.json" - DESTINATION share/vulkan/explicit_layer.d) + DESTINATION share/vulkan/implicit_layer.d) diff --git a/README.md b/README.md index 9d1363b..294ae34 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,7 @@ # lsfg-vk This project brings [Lossless Scaling's Frame Generation](https://store.steampowered.com/app/993090/Lossless_Scaling/) to Linux! ->[!NOTE] -> This is a work-in-progress. While frame generation has worked in a few games, there's still a long way to go. Please review the wiki for support (the wiki is not written yet) - -## Building, Installing and Running >[!CAUTION] -> The build instructions have recently changed. Please review them. +> This is a work-in-progress. While frame generation has worked in quite a few games, there's still a long way to go. -In order to compile LSFG, make sure you have the following components installed on your system: -- Traditional build tools (+ sed, git) -- Clang compiler (this project does NOT compile easily with GCC) -- Vulkan header files -- CMake build system -- Meson build system (for DXVK) -- Ninja build system (backend for CMake) - -Compiling lsfg-vk is relatively straight forward, as everything is neatly integrated into CMake: -```bash -$ cmake -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=~/.local \ - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \ - -DCMAKE_CXX_CLANG_TIDY="" -$ cmake --build build -$ cmake --install build -``` -This will install lsfg-vk to ~/.local/lib and ~/.local/share/vulkan. - -Next, you'll need to download Lossless Scaling from Steam. Switch to the `legacy_2.13` branch or download the corresponding depot. -Copy or note down the path of "Lossless.dll" from the game files. - -Finally, let's actually start a program with frame generation enabled. I'm going to be using `vkcube` for this example: -```bash -VK_INSTANCE_LAYERS="VK_LAYER_LS_frame_generation" LSFG_DLL_PATH="/home/pancake/games/Lossless Scaling/Lossless.dll" LSFG_MULTIPLIER=4 vkcube -``` -Make sure you adjust the paths. Let's examine each one: -- `LVK_INSTANCE_LAYERS`: Specify `VK_LAYER_LS_frame_generation` here. This forces any Vulkan app to load the lsfg-vk layer. -- `LSFG_DLL_PATH`: Here you specify the Lossless.dll you downloaded from Steam. lsfg-vk will extract and translate the shaders from here. -- `LSFG_MULTIPLIER`: This is the multiplier you should be familiar with. Specify `2` for doubling the framerate, etc. -- `VK_LAYER_PATH`: If you did not install to `~/.local` or `/usr`, you have to specify the `explicit_layer.d` folder here. - ->[!WARNING] -> Unlike on Windows, LSFG_MULTIPLIER is heavily limited here (at the moment!). If your hardware can create 8 swapchain images, then setting LSFG_MULTIPLIER to 4 occupies 4 of those, leaving only 4 to the game. If the game requested 5 or more, it will crash. +Please see the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki) for install instructions diff --git a/VkLayer_LS_frame_generation.json b/VkLayer_LS_frame_generation.json index 4761d40..ece2a5f 100644 --- a/VkLayer_LS_frame_generation.json +++ b/VkLayer_LS_frame_generation.json @@ -10,6 +10,12 @@ "functions": { "vkGetInstanceProcAddr": "layer_vkGetInstanceProcAddr", "vkGetDeviceProcAddr": "layer_vkGetDeviceProcAddr" + }, + "enable_environment": { + "ENABLE_LSFG": "1" + }, + "disable_environment": { + "DISABLE_LSFG": "1" } } } diff --git a/include/utils/utils.hpp b/include/utils/utils.hpp index 147bfe3..e40c85c 100644 --- a/include/utils/utils.hpp +++ b/include/utils/utils.hpp @@ -58,16 +58,6 @@ namespace Utils { VkPipelineStageFlags pre, VkPipelineStageFlags post, bool makeSrcPresentable, bool makeDstPresentable); - /// - /// Store the current layer environment. - /// - void storeLayerEnv(); - - /// - /// Restore the layer environment to the previously stored value. - /// - void restoreLayerEnv(); - } #endif // UTILS_HPP diff --git a/src/context.cpp b/src/context.cpp index 76f8ae4..19c8b7b 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -3,11 +3,10 @@ #include "utils/log.hpp" #include "utils/utils.hpp" -#include #include +#include #include -#include LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, VkExtent2D extent, const std::vector& swapchainImages) @@ -51,9 +50,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, setenv("LSFG_DEVICE_UUID", std::to_string(deviceUUID).c_str(), 1); Log::debug("context", "(entering LSFG initialization)"); - Utils::storeLayerEnv(); LSFG::initialize(); - Utils::restoreLayerEnv(); Log::debug("context", "(exiting LSFG initialization)"); Log::debug("context", "(entering LSFG context creation)"); diff --git a/src/layer.cpp b/src/layer.cpp index 5b8f952..7184688 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -77,6 +77,7 @@ namespace { const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { Log::debug("layer", "Initializing lsfg-vk instance layer..."); + setenv("DISABLE_LSFG", "1", 1); // mustn't load further // find layer creation info auto* layerDesc = const_cast( diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 0bc4ee4..5ee3dfa 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -161,25 +161,3 @@ void Utils::copyImage(VkCommandBuffer buf, 1, &presentBarrier); } } - -namespace { - std::optional layersEnvironment; -} - -// TODO: more environment variables? what about explicit disable? - -void Utils::storeLayerEnv() { - const char* env = std::getenv("VK_INSTANCE_LAYERS"); - if (env) - layersEnvironment = env; - else - layersEnvironment.reset(); - unsetenv("VK_INSTANCE_LAYERS"); -} - -void Utils::restoreLayerEnv() { - if (layersEnvironment.has_value()) - setenv("VK_INSTANCE_LAYERS", layersEnvironment->c_str(), 1); - else - unsetenv("VK_INSTANCE_LAYERS"); -} From ef0ff5a70be6c325aabd4720523ce14f9bc971b1 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 17:27:35 +0200 Subject: [PATCH 089/253] fix: incorrect lookup for data home fixes #16 --- lsfg-vk-gen/src/lsfg.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp index f75286d..6323763 100644 --- a/lsfg-vk-gen/src/lsfg.cpp +++ b/lsfg-vk-gen/src/lsfg.cpp @@ -25,21 +25,23 @@ void LSFG::initialize() { return; char* dllPath = getenv("LSFG_DLL_PATH"); - std::string dllPathStr; + std::string dllPathStr; // (absolutely beautiful code) if (dllPath && *dllPath != '\0') { dllPathStr = std::string(dllPath); } else { - std::string baseDirStr; - - const char* baseDir = getenv("XDG_DATA_HOME"); - if (!baseDir || *baseDir == '\0') - baseDir = getenv("HOME"); - if (!baseDir || *baseDir == '\0') - baseDir = ""; - baseDirStr = std::string(baseDir); - - dllPathStr = baseDirStr + - "/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; + const char* dataDir = getenv("XDG_DATA_HOME"); + if (dataDir && *dataDir != '\0') { + dllPathStr = std::string(dataDir) + + "Steam/steamapps/common/Lossless Scaling/Lossless.dll"; + } else { + const char* homeDir = getenv("HOME"); + if (homeDir && *homeDir != '\0') { + dllPathStr = std::string(homeDir) + + "/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; + } else { + dllPathStr = "Lossless.dll"; + } + } } instance.emplace(); From ef6d3bd39db2ffedc59bc00a64faa7ec5ca51534 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 19:09:37 +0200 Subject: [PATCH 090/253] fix: unset disable var in case future processes --- src/context.cpp | 2 ++ src/layer.cpp | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/context.cpp b/src/context.cpp index 19c8b7b..66180ba 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -50,7 +50,9 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, setenv("LSFG_DEVICE_UUID", std::to_string(deviceUUID).c_str(), 1); Log::debug("context", "(entering LSFG initialization)"); + setenv("DISABLE_LSFG", "1", 1); LSFG::initialize(); + unsetenv("DISABLE_LSFG"); Log::debug("context", "(exiting LSFG initialization)"); Log::debug("context", "(entering LSFG context creation)"); diff --git a/src/layer.cpp b/src/layer.cpp index 7184688..5b8f952 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -77,7 +77,6 @@ namespace { const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { Log::debug("layer", "Initializing lsfg-vk instance layer..."); - setenv("DISABLE_LSFG", "1", 1); // mustn't load further // find layer creation info auto* layerDesc = const_cast( From 180256a2222827bc028e81d5395858ff2316e54a Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 19:58:22 +0200 Subject: [PATCH 091/253] disable unneeded dxvk features --- cmake/FetchDXVK.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/FetchDXVK.cmake b/cmake/FetchDXVK.cmake index b51b180..f84de82 100644 --- a/cmake/FetchDXVK.cmake +++ b/cmake/FetchDXVK.cmake @@ -24,6 +24,9 @@ ExternalProject_Add(dxvk_git --prefix /build-native ${STRIP_FLAG} -Dbuild_id=false + -Denable_d3d8=false + -Denable_d3d9=false + -Denable_d3d10=false --force-fallback-for=libdisplay-info --wipe build From 192f5e4e9fd9f5bddb5e6cc44dbcdba6579c5644 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 20:41:19 +0200 Subject: [PATCH 092/253] cache pipelines to improve loading speeds --- lsfg-vk-gen/include/pool/shaderpool.hpp | 14 ++++++++++++++ lsfg-vk-gen/src/pool/shaderpool.cpp | 15 +++++++++++++++ lsfg-vk-gen/src/shaderchains/alpha.cpp | 12 +++++++----- lsfg-vk-gen/src/shaderchains/beta.cpp | 13 ++++++++----- lsfg-vk-gen/src/shaderchains/delta.cpp | 12 +++++++----- lsfg-vk-gen/src/shaderchains/downsample.cpp | 2 +- lsfg-vk-gen/src/shaderchains/epsilon.cpp | 12 +++++++----- lsfg-vk-gen/src/shaderchains/extract.cpp | 2 +- lsfg-vk-gen/src/shaderchains/gamma.cpp | 14 +++++++++----- lsfg-vk-gen/src/shaderchains/magic.cpp | 2 +- lsfg-vk-gen/src/shaderchains/merge.cpp | 2 +- lsfg-vk-gen/src/shaderchains/zeta.cpp | 12 +++++++----- 12 files changed, 78 insertions(+), 34 deletions(-) diff --git a/lsfg-vk-gen/include/pool/shaderpool.hpp b/lsfg-vk-gen/include/pool/shaderpool.hpp index aeb5ba2..8477887 100644 --- a/lsfg-vk-gen/include/pool/shaderpool.hpp +++ b/lsfg-vk-gen/include/pool/shaderpool.hpp @@ -2,6 +2,7 @@ #define SHADERPOOL_HPP #include "core/device.hpp" +#include "core/pipeline.hpp" #include "core/shadermodule.hpp" #include "pool/extract.hpp" @@ -39,9 +40,22 @@ namespace LSFG::Pool { Core::ShaderModule getShader( const Core::Device& device, const std::string& name, const std::vector>& types); + + /// + /// Retrieve a pipeline shader module by name or create it. + /// + /// @param device Vulkan device + /// @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: Extractor extractor; std::unordered_map shaders; + std::unordered_map pipelines; }; } diff --git a/lsfg-vk-gen/src/pool/shaderpool.cpp b/lsfg-vk-gen/src/pool/shaderpool.cpp index 46dd7c1..33cce03 100644 --- a/lsfg-vk-gen/src/pool/shaderpool.cpp +++ b/lsfg-vk-gen/src/pool/shaderpool.cpp @@ -63,3 +63,18 @@ Core::ShaderModule ShaderPool::getShader( shaders[name] = shader; return shader; } + +Core::Pipeline ShaderPool::getPipeline( + const Core::Device& device, const std::string& name) { + auto it = pipelines.find(name); + if (it != pipelines.end()) + return it->second; + + // grab the shader module + auto shader = this->getShader(device, name, {}); + + // create the pipeline + Core::Pipeline pipeline(device, shader); + pipelines[name] = pipeline; + return pipeline; +} diff --git a/lsfg-vk-gen/src/shaderchains/alpha.cpp b/lsfg-vk-gen/src/shaderchains/alpha.cpp index 61f75d3..3b8b10c 100644 --- a/lsfg-vk-gen/src/shaderchains/alpha.cpp +++ b/lsfg-vk-gen/src/shaderchains/alpha.cpp @@ -25,13 +25,15 @@ Alpha::Alpha(const Core::Device& device, Pool::ShaderPool& shaderpool, { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; - for (size_t i = 0; i < 4; i++) { - this->pipelines.at(i) = Core::Pipeline(device, - this->shaderModules.at(i)); - if (i == 3) continue; // last shader is special + this->pipelines = {{ + shaderpool.getPipeline(device, "alpha/0.spv"), + shaderpool.getPipeline(device, "alpha/1.spv"), + shaderpool.getPipeline(device, "alpha/2.spv"), + shaderpool.getPipeline(device, "alpha/3.spv") + }}; + for (size_t i = 0; i < 3; i++) this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); - } for (size_t i = 0; i < 3; i++) this->specialDescriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(3)); diff --git a/lsfg-vk-gen/src/shaderchains/beta.cpp b/lsfg-vk-gen/src/shaderchains/beta.cpp index a9e012f..55e8f82 100644 --- a/lsfg-vk-gen/src/shaderchains/beta.cpp +++ b/lsfg-vk-gen/src/shaderchains/beta.cpp @@ -35,13 +35,16 @@ Beta::Beta(const Core::Device& device, Pool::ShaderPool& shaderpool, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; - for (size_t i = 0; i < 5; i++) { - this->pipelines.at(i) = Core::Pipeline(device, - this->shaderModules.at(i)); - if (i == 0 || i == 4) continue; // first shader has special logic + this->pipelines = {{ + shaderpool.getPipeline(device, "beta/0.spv"), + shaderpool.getPipeline(device, "beta/1.spv"), + shaderpool.getPipeline(device, "beta/2.spv"), + shaderpool.getPipeline(device, "beta/3.spv"), + shaderpool.getPipeline(device, "beta/4.spv") + }}; + for (size_t i = 1; i < 4; i++) this->descriptorSets.at(i - 1) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); - } for (size_t i = 0; i < 3; i++) this->specialDescriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(0)); diff --git a/lsfg-vk-gen/src/shaderchains/delta.cpp b/lsfg-vk-gen/src/shaderchains/delta.cpp index 333492d..67c3eae 100644 --- a/lsfg-vk-gen/src/shaderchains/delta.cpp +++ b/lsfg-vk-gen/src/shaderchains/delta.cpp @@ -29,13 +29,15 @@ Delta::Delta(const Core::Device& device, Pool::ShaderPool& shaderpool, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; - for (size_t i = 0; i < 4; i++) { - this->pipelines.at(i) = Core::Pipeline(device, - this->shaderModules.at(i)); - if (i == 3) continue; + this->pipelines = {{ + shaderpool.getPipeline(device, "delta/0.spv"), + shaderpool.getPipeline(device, "delta/1.spv"), + shaderpool.getPipeline(device, "delta/2.spv"), + shaderpool.getPipeline(device, "delta/3.spv") + }}; + for (size_t i = 0; i < 3; i++) this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); - } for (size_t i = 0; i < genc; i++) this->nDescriptorSets.emplace_back(device, pool, this->shaderModules.at(3)); diff --git a/lsfg-vk-gen/src/shaderchains/downsample.cpp b/lsfg-vk-gen/src/shaderchains/downsample.cpp index 7af81fe..4d54283 100644 --- a/lsfg-vk-gen/src/shaderchains/downsample.cpp +++ b/lsfg-vk-gen/src/shaderchains/downsample.cpp @@ -14,7 +14,7 @@ Downsample::Downsample(const Core::Device& device, Pool::ShaderPool& shaderpool, { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); - this->pipeline = Core::Pipeline(device, this->shaderModule); + this->pipeline = shaderpool.getPipeline(device, "downsample.spv"); for (size_t i = 0; i < 2; i++) this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModule); diff --git a/lsfg-vk-gen/src/shaderchains/epsilon.cpp b/lsfg-vk-gen/src/shaderchains/epsilon.cpp index 59c87d7..b9cdf43 100644 --- a/lsfg-vk-gen/src/shaderchains/epsilon.cpp +++ b/lsfg-vk-gen/src/shaderchains/epsilon.cpp @@ -31,13 +31,15 @@ Epsilon::Epsilon(const Core::Device& device, Pool::ShaderPool& shaderpool, { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; - for (size_t i = 0; i < 4; i++) { - this->pipelines.at(i) = Core::Pipeline(device, - this->shaderModules.at(i)); - if (i == 3) continue; + this->pipelines = {{ + shaderpool.getPipeline(device, "epsilon/0.spv"), + shaderpool.getPipeline(device, "epsilon/1.spv"), + shaderpool.getPipeline(device, "epsilon/2.spv"), + shaderpool.getPipeline(device, "epsilon/3.spv") + }}; + for (size_t i = 0; i < 3; i++) this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); - } for (size_t i = 0; i < genc; i++) this->nDescriptorSets.emplace_back(device, pool, this->shaderModules.at(3)); diff --git a/lsfg-vk-gen/src/shaderchains/extract.cpp b/lsfg-vk-gen/src/shaderchains/extract.cpp index 8251bd8..041bd72 100644 --- a/lsfg-vk-gen/src/shaderchains/extract.cpp +++ b/lsfg-vk-gen/src/shaderchains/extract.cpp @@ -16,7 +16,7 @@ Extract::Extract(const Core::Device& device, Pool::ShaderPool& shaderpool, { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); - this->pipeline = Core::Pipeline(device, this->shaderModule); + this->pipeline = shaderpool.getPipeline(device, "extract.spv"); for (size_t i = 0; i < genc; i++) this->nDescriptorSets.emplace_back(device, pool, this->shaderModule); diff --git a/lsfg-vk-gen/src/shaderchains/gamma.cpp b/lsfg-vk-gen/src/shaderchains/gamma.cpp index 66ba284..4e73c60 100644 --- a/lsfg-vk-gen/src/shaderchains/gamma.cpp +++ b/lsfg-vk-gen/src/shaderchains/gamma.cpp @@ -47,13 +47,17 @@ Gamma::Gamma(const Core::Device& device, Pool::ShaderPool& shaderpool, { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; - for (size_t i = 0; i < 6; i++) { - this->pipelines.at(i) = Core::Pipeline(device, - this->shaderModules.at(i)); - if (i == 0 || i >= 4) continue; // first shader has special logic + this->pipelines = {{ + shaderpool.getPipeline(device, "gamma/0.spv"), + shaderpool.getPipeline(device, "gamma/1.spv"), + shaderpool.getPipeline(device, "gamma/2.spv"), + shaderpool.getPipeline(device, "gamma/3.spv"), + shaderpool.getPipeline(device, "gamma/4.spv"), + shaderpool.getPipeline(device, "gamma/5.spv") + }}; + for (size_t i = 1; i < 4; i++) this->descriptorSets.at(i - 1) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); - } for (size_t i = 0; i < genc; i++) this->n1DescriptorSets.emplace_back(device, pool, this->shaderModules.at(4)); diff --git a/lsfg-vk-gen/src/shaderchains/magic.cpp b/lsfg-vk-gen/src/shaderchains/magic.cpp index 1b74a35..e87f26b 100644 --- a/lsfg-vk-gen/src/shaderchains/magic.cpp +++ b/lsfg-vk-gen/src/shaderchains/magic.cpp @@ -22,7 +22,7 @@ Magic::Magic(const Core::Device& device, Pool::ShaderPool& shaderpool, { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 4+4+2+1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 3+3+2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); - this->pipeline = Core::Pipeline(device, this->shaderModule); + this->pipeline = shaderpool.getPipeline(device, "magic.spv"); for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.emplace_back(); for (size_t j = 0; j < 3; j++) diff --git a/lsfg-vk-gen/src/shaderchains/merge.cpp b/lsfg-vk-gen/src/shaderchains/merge.cpp index 6b17bdb..5f9290a 100644 --- a/lsfg-vk-gen/src/shaderchains/merge.cpp +++ b/lsfg-vk-gen/src/shaderchains/merge.cpp @@ -22,7 +22,7 @@ Merge::Merge(const Core::Device& device, Pool::ShaderPool& shaderpool, { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); - this->pipeline = Core::Pipeline(device, this->shaderModule); + this->pipeline = shaderpool.getPipeline(device, "merge.spv"); for (size_t i = 0; i < genc; i++) { this->nDescriptorSets.emplace_back(); for (size_t j = 0; j < 2; j++) diff --git a/lsfg-vk-gen/src/shaderchains/zeta.cpp b/lsfg-vk-gen/src/shaderchains/zeta.cpp index a5350a8..7f740c3 100644 --- a/lsfg-vk-gen/src/shaderchains/zeta.cpp +++ b/lsfg-vk-gen/src/shaderchains/zeta.cpp @@ -31,13 +31,15 @@ Zeta::Zeta(const Core::Device& device, Pool::ShaderPool& shaderpool, { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; - for (size_t i = 0; i < 4; i++) { - this->pipelines.at(i) = Core::Pipeline(device, - this->shaderModules.at(i)); - if (i == 3) continue; + this->pipelines = {{ + shaderpool.getPipeline(device, "zeta/0.spv"), + shaderpool.getPipeline(device, "zeta/1.spv"), + shaderpool.getPipeline(device, "zeta/2.spv"), + shaderpool.getPipeline(device, "zeta/3.spv") + }}; + for (size_t i = 0; i < 3; i++) this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModules.at(i)); - } for (size_t i = 0; i < genc; i++) this->nDescriptorSets.emplace_back(device, pool, this->shaderModules.at(3)); From 01379439f469e24c933ee500f6d6c5ecf3576caf Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 20:57:59 +0200 Subject: [PATCH 093/253] workaround a mesa bug fixes #17 --- src/layer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/layer.cpp b/src/layer.cpp index 5b8f952..ea2003b 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -72,6 +72,7 @@ namespace { namespace { + VkInstance gInstance; VkResult layer_vkCreateInstance( // NOLINTBEGIN const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, @@ -125,6 +126,8 @@ namespace { return VK_ERROR_INITIALIZATION_FAILED; } + gInstance = *pInstance; // workaround mesa bug + Log::debug("layer", "Successfully initialized lsfg-vk instance layer"); return res; } // NOLINTEND @@ -157,7 +160,7 @@ namespace { layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; // create device - auto success = initInstanceFunc(nullptr, "vkCreateDevice", &next_vkCreateDevice); + auto success = initInstanceFunc(gInstance, "vkCreateDevice", &next_vkCreateDevice); if (!success) return VK_ERROR_INITIALIZATION_FAILED; auto* layer_vkCreateDevice2 = reinterpret_cast( From 92c7f98a12a0c32419bcc96b36698aa931035421 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 6 Jul 2025 21:15:15 +0200 Subject: [PATCH 094/253] hdr support fixes #15 --- lsfg-vk-gen/src/context.cpp | 8 ++++++-- lsfg-vk-gen/src/shaderchains/merge.cpp | 6 ++++-- lsfg-vk-gen/src/utils/utils.cpp | 3 +++ src/context.cpp | 13 ++++++++++--- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lsfg-vk-gen/src/context.cpp b/lsfg-vk-gen/src/context.cpp index b19e187..8c9b3da 100644 --- a/lsfg-vk-gen/src/context.cpp +++ b/lsfg-vk-gen/src/context.cpp @@ -14,14 +14,18 @@ using namespace LSFG; Context::Context(const Core::Device& device, Pool::ShaderPool& shaderpool, uint32_t width, uint32_t height, int in0, int in1, const std::vector& outN) { + const VkFormat format = getenv("LSFG_HDR") == nullptr + ? VK_FORMAT_R8G8B8A8_UNORM + : VK_FORMAT_R16G16B16A16_SFLOAT; + // import images this->inImg_0 = Core::Image(device, { width, height }, - VK_FORMAT_R8G8B8A8_UNORM, + format, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT, in0); this->inImg_1 = Core::Image(device, { width, height }, - VK_FORMAT_R8G8B8A8_UNORM, + format, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT, in1); diff --git a/lsfg-vk-gen/src/shaderchains/merge.cpp b/lsfg-vk-gen/src/shaderchains/merge.cpp index 5f9290a..0d8716a 100644 --- a/lsfg-vk-gen/src/shaderchains/merge.cpp +++ b/lsfg-vk-gen/src/shaderchains/merge.cpp @@ -36,10 +36,12 @@ Merge::Merge(const Core::Device& device, Pool::ShaderPool& shaderpool, auto extent = this->inImg1.getExtent(); + const VkFormat format = getenv("LSFG_HDR") == nullptr + ? VK_FORMAT_R8G8B8A8_UNORM + : VK_FORMAT_R16G16B16A16_SFLOAT; for (size_t i = 0; i < genc; i++) this->outImgs.emplace_back(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, + extent, format, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT, outFds.at(i)); diff --git a/lsfg-vk-gen/src/utils/utils.cpp b/lsfg-vk-gen/src/utils/utils.cpp index 5765ea5..4260e0d 100644 --- a/lsfg-vk-gen/src/utils/utils.cpp +++ b/lsfg-vk-gen/src/utils/utils.cpp @@ -2,6 +2,7 @@ #include "core/buffer.hpp" #include "lsfg.hpp" +#include #include using namespace LSFG; @@ -188,6 +189,8 @@ void Globals::initializeGlobals(const Core::Device& device) { // initialize global constant buffer fgBuffer = { .inputOffset = { 0, 29 }, + .advancedColorKind = getenv("LSFG_HDR") == nullptr ? 0U : 2U, + .hdrSupport = getenv("LSFG_HDR") != nullptr, .resolutionInvScale = 1.0F, .timestamp = 0.5F, .uiThreshold = 0.1F, diff --git a/src/context.cpp b/src/context.cpp index 66180ba..9b5d05e 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -12,11 +12,18 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, VkExtent2D extent, const std::vector& swapchainImages) : swapchain(swapchain), swapchainImages(swapchainImages), extent(extent) { + + // we could take the format from the swapchain, + // but honestly this is safer. + const VkFormat format = getenv("LSFG_HDR") == nullptr + ? VK_FORMAT_R8G8B8A8_UNORM + : VK_FORMAT_R16G16B16A16_SFLOAT; + // prepare textures for lsfg int frame_0_fd{}; this->frame_0 = Mini::Image( info.device, info.physicalDevice, - extent, VK_FORMAT_R8G8B8A8_UNORM, + extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, &frame_0_fd); @@ -26,7 +33,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, int frame_1_fd{}; this->frame_1 = Mini::Image( info.device, info.physicalDevice, - extent, VK_FORMAT_R8G8B8A8_UNORM, + extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, &frame_1_fd); @@ -37,7 +44,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, for (size_t i = 0; i < info.frameGen; ++i) { this->out_n.emplace_back( info.device, info.physicalDevice, - extent, VK_FORMAT_R8G8B8A8_UNORM, + extent, format, VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_ASPECT_COLOR_BIT, &out_n_fds.at(i)); From 42ff9d7fd05aa482323e725505b7a2cbdeaa71ae Mon Sep 17 00:00:00 2001 From: Samuel <36420837+Samueru-sama@users.noreply.github.com> Date: Sun, 6 Jul 2025 17:26:48 -0400 Subject: [PATCH 095/253] add missing slash when `XDG_DATA_HOME` is set --- lsfg-vk-gen/src/lsfg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp index 6323763..dfad72f 100644 --- a/lsfg-vk-gen/src/lsfg.cpp +++ b/lsfg-vk-gen/src/lsfg.cpp @@ -32,7 +32,7 @@ void LSFG::initialize() { const char* dataDir = getenv("XDG_DATA_HOME"); if (dataDir && *dataDir != '\0') { dllPathStr = std::string(dataDir) + - "Steam/steamapps/common/Lossless Scaling/Lossless.dll"; + "/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; } else { const char* homeDir = getenv("HOME"); if (homeDir && *homeDir != '\0') { From a9452ab198e40edd7aa8fb07d058d872b919b361 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 7 Jul 2025 03:00:58 +0200 Subject: [PATCH 096/253] fix extension check logic --- src/utils/utils.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 5ee3dfa..843acca 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -2,6 +2,7 @@ #include "utils/log.hpp" #include "layer.hpp" +#include #include #include @@ -51,7 +52,10 @@ std::vector Utils::addExtensions(const char* const* extensions, siz std::copy_n(extensions, count, ext.data()); for (const auto& e : requiredExtensions) { - auto it = std::ranges::find(ext, e); + auto it = std::ranges::find_if(ext, + [e](const char* extName) { + return std::strcmp(extName, e) == 0; + }); if (it == ext.end()) { Log::debug("hooks-init", "Adding extension: {}", e); ext.push_back(e); From 77a8c5bbfc6e9d29517e58116b9e16fb2412e9b0 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 7 Jul 2025 03:01:39 +0200 Subject: [PATCH 097/253] fix first debug log messages being dropped --- include/utils/log.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/utils/log.hpp b/include/utils/log.hpp index f85649e..0c29623 100644 --- a/include/utils/log.hpp +++ b/include/utils/log.hpp @@ -64,6 +64,7 @@ void debug(std::string_view, std::format_string, Args&&...) {} // NOLIN #else template void debug(std::string_view module, std::format_string fmt, Args&&... args) { + Internal::setup(); if (Internal::debugAllModules || Internal::debugModules.contains(std::string(module))) log(GRAY, module, fmt, std::forward(args)...); } From ced1b326dfe56349b6c2a370e294c890e409502b Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 7 Jul 2025 03:15:47 +0200 Subject: [PATCH 098/253] fix: sneaky underflow when multiplier is zero fixes #22 --- src/hooks.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks.cpp b/src/hooks.cpp index a640f87..81dceaa 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -93,8 +93,8 @@ namespace { reinterpret_cast(*pDevice)); try { const char* frameGenEnv = std::getenv("LSFG_MULTIPLIER"); - const uint64_t frameGen = std::max(1, - std::stoul(frameGenEnv ? frameGenEnv : "2") - 1); + const uint64_t frameGen = static_cast( + std::max(1, std::stol(frameGenEnv ? frameGenEnv : "2") - 1)); Log::debug("hooks", "Using {}x frame generation", frameGen + 1); From 2071c97aeffabc0d9c9f6f6ae5d9d33fe3fc2bbc Mon Sep 17 00:00:00 2001 From: Pancake Date: Mon, 7 Jul 2025 13:48:45 +0200 Subject: [PATCH 099/253] Update issue templates --- .../bug-report--non-functional-.md | 35 +++++++++++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 35 +++++++++++++++++++ .github/ISSUE_TEMPLATE/other.md | 10 ++++++ 3 files changed, 80 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report--non-functional-.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/other.md diff --git a/.github/ISSUE_TEMPLATE/bug-report--non-functional-.md b/.github/ISSUE_TEMPLATE/bug-report--non-functional-.md new file mode 100644 index 0000000..bd4daeb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report--non-functional-.md @@ -0,0 +1,35 @@ +--- +name: Bug report (non-functional) +about: Report a bug (if lsfg-vk is not working at all). +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. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open '...' +2. Move/Resize '...' +3. See error + +**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** +Grab the latest debug build (change "Release" to "Debug" in build parameters) and launch your game with these environment variables set: +`LSFG_LOG_FILE=lsfg.log LSFG_LOG_DEBUG=all VK_LOADER_DEBUG=all` +Pipe the application output to a file (`2>&1 | tee app.log` at the end of the command) + +Upload both xxxx_lsfg.log and app.log. + +Open up a terminal and type `vulkaninfo -o vulkan.txt`, this will create a file called vulkan.txt. Upload it as well. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f30fce7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +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. + +**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** +(you may skip this step if not relevant)) + +Grab the latest debug build (change "Release" to "Debug" in build parameters) and launch your game with these environment variables set: +`LSFG_LOG_FILE=lsfg.log LSFG_LOG_DEBUG=all VK_LOADER_DEBUG=all` +Pipe the application output to a file (`2>&1 | tee app.log` at the end of the command) + +Upload both xxxx_lsfg.log and app.log. + +Open up a terminal and type `vulkaninfo -o vulkan.txt`, this will create a file called vulkan.txt. Upload it as well. diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md new file mode 100644 index 0000000..1ddfdd7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other.md @@ -0,0 +1,10 @@ +--- +name: Other +about: Create an empty issue. +title: '' +labels: '' +assignees: '' + +--- + + From a73ce58f9c864c60d47fde6edbd6fb2a2c3943b9 Mon Sep 17 00:00:00 2001 From: Pancake Date: Mon, 7 Jul 2025 14:04:25 +0200 Subject: [PATCH 100/253] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..6c0dd38 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [PancakeTAS] From 45f4296319e8a20526a61c3bf5e8697a15ca48bf Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 8 Jul 2025 18:08:55 +0200 Subject: [PATCH 101/253] docs: update README.md and promote discord as issue tracker --- .../bug-report--non-functional-.md | 35 ------------------- .github/ISSUE_TEMPLATE/bug_report.md | 35 ------------------- .github/ISSUE_TEMPLATE/other.md | 10 ------ README.md | 8 +++-- 4 files changed, 6 insertions(+), 82 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug-report--non-functional-.md delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/other.md diff --git a/.github/ISSUE_TEMPLATE/bug-report--non-functional-.md b/.github/ISSUE_TEMPLATE/bug-report--non-functional-.md deleted file mode 100644 index bd4daeb..0000000 --- a/.github/ISSUE_TEMPLATE/bug-report--non-functional-.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Bug report (non-functional) -about: Report a bug (if lsfg-vk is not working at all). -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. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open '...' -2. Move/Resize '...' -3. See error - -**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** -Grab the latest debug build (change "Release" to "Debug" in build parameters) and launch your game with these environment variables set: -`LSFG_LOG_FILE=lsfg.log LSFG_LOG_DEBUG=all VK_LOADER_DEBUG=all` -Pipe the application output to a file (`2>&1 | tee app.log` at the end of the command) - -Upload both xxxx_lsfg.log and app.log. - -Open up a terminal and type `vulkaninfo -o vulkan.txt`, this will create a file called vulkan.txt. Upload it as well. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index f30fce7..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -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. - -**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** -(you may skip this step if not relevant)) - -Grab the latest debug build (change "Release" to "Debug" in build parameters) and launch your game with these environment variables set: -`LSFG_LOG_FILE=lsfg.log LSFG_LOG_DEBUG=all VK_LOADER_DEBUG=all` -Pipe the application output to a file (`2>&1 | tee app.log` at the end of the command) - -Upload both xxxx_lsfg.log and app.log. - -Open up a terminal and type `vulkaninfo -o vulkan.txt`, this will create a file called vulkan.txt. Upload it as well. diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md deleted file mode 100644 index 1ddfdd7..0000000 --- a/.github/ISSUE_TEMPLATE/other.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Other -about: Create an empty issue. -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/README.md b/README.md index 294ae34..cbf22c7 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ This project brings [Lossless Scaling's Frame Generation](https://store.steampowered.com/app/993090/Lossless_Scaling/) to Linux! >[!CAUTION] -> This is a work-in-progress. While frame generation has worked in quite a few games, there's still a long way to go. +> **This is a work-in-progress**. While frame generation has worked in quite a few games, compatibility in many games as well as frame pacing issues still need to be fixed. + +Please see the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki) for install instructions and join the [Discord](https://discord.gg/losslessscaling) for help (In order to see the linux channels, verify your Steam account.) + +>[!WARNING] +> Please do note open GitHub issues for anything other than feature requests. Due to the nature of this project, it is much easier to deal with issues through Discord, than GitHub. Use the #linux-reports channel for game compatibility. -Please see the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki) for install instructions From 778a87dd24cc6f0d490b3b128d82da07e8e7266c Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 01:50:36 +0200 Subject: [PATCH 102/253] implement lsfg 3.0 --- lsfg-vk-gen/CMakeLists.txt | 37 -- lsfg-vk-gen/include/context.hpp | 95 ----- lsfg-vk-gen/include/pool/extract.hpp | 63 ---- lsfg-vk-gen/include/shaderchains/alpha.hpp | 82 ----- lsfg-vk-gen/include/shaderchains/beta.hpp | 88 ----- lsfg-vk-gen/include/shaderchains/delta.hpp | 83 ----- .../include/shaderchains/downsample.hpp | 76 ---- lsfg-vk-gen/include/shaderchains/epsilon.hpp | 86 ----- lsfg-vk-gen/include/shaderchains/extract.hpp | 81 ----- lsfg-vk-gen/include/shaderchains/gamma.hpp | 106 ------ lsfg-vk-gen/include/shaderchains/magic.hpp | 98 ------ lsfg-vk-gen/include/shaderchains/merge.hpp | 91 ----- lsfg-vk-gen/include/shaderchains/zeta.hpp | 86 ----- lsfg-vk-gen/src/context.cpp | 184 ---------- lsfg-vk-gen/src/lsfg.cpp | 100 ------ lsfg-vk-gen/src/pool/extract.cpp | 89 ----- lsfg-vk-gen/src/pool/shaderpool.cpp | 80 ----- lsfg-vk-gen/src/shaderchains/alpha.cpp | 176 ---------- lsfg-vk-gen/src/shaderchains/beta.cpp | 189 ---------- lsfg-vk-gen/src/shaderchains/delta.cpp | 145 -------- lsfg-vk-gen/src/shaderchains/downsample.cpp | 60 ---- lsfg-vk-gen/src/shaderchains/epsilon.cpp | 148 -------- lsfg-vk-gen/src/shaderchains/extract.cpp | 74 ---- lsfg-vk-gen/src/shaderchains/gamma.cpp | 272 --------------- lsfg-vk-gen/src/shaderchains/magic.cpp | 117 ------- lsfg-vk-gen/src/shaderchains/merge.cpp | 85 ----- lsfg-vk-gen/src/shaderchains/zeta.cpp | 148 -------- lsfg-vk-v3.1/.clang-tidy-no | 25 ++ lsfg-vk-v3.1/.gitattributes | 3 + lsfg-vk-v3.1/.gitignore | 9 + lsfg-vk-v3.1/CMakeLists.txt | 57 +++ lsfg-vk-v3.1/LICENSE.md | 21 ++ lsfg-vk-v3.1/README.md | 14 + lsfg-vk-v3.1/include/context.hpp | 75 ++++ .../include/core/buffer.hpp | 0 .../include/core/commandbuffer.hpp | 0 .../include/core/commandpool.hpp | 0 .../include/core/descriptorpool.hpp | 0 .../include/core/descriptorset.hpp | 0 .../include/core/device.hpp | 3 +- .../include/core/fence.hpp | 0 .../include/core/image.hpp | 6 +- .../include/core/instance.hpp | 0 .../include/core/pipeline.hpp | 0 .../include/core/sampler.hpp | 7 +- .../include/core/semaphore.hpp | 0 .../include/core/shadermodule.hpp | 0 lsfg-vk-v3.1/include/pool/resourcepool.hpp | 70 ++++ .../include/pool/shaderpool.hpp | 14 +- lsfg-vk-v3.1/include/shaders/alpha.hpp | 61 ++++ lsfg-vk-v3.1/include/shaders/beta.hpp | 61 ++++ lsfg-vk-v3.1/include/shaders/delta.hpp | 78 +++++ lsfg-vk-v3.1/include/shaders/gamma.hpp | 70 ++++ lsfg-vk-v3.1/include/shaders/generate.hpp | 67 ++++ lsfg-vk-v3.1/include/shaders/mipmaps.hpp | 60 ++++ lsfg-vk-v3.1/include/utils/trans.hpp | 19 + .../include/utils/utils.hpp | 42 +-- {lsfg-vk-gen => lsfg-vk-v3.1}/public/lsfg.hpp | 33 +- lsfg-vk-v3.1/src/context.cpp | 110 ++++++ .../src/core/buffer.cpp | 0 .../src/core/commandbuffer.cpp | 0 .../src/core/commandpool.cpp | 0 .../src/core/descriptorpool.cpp | 0 .../src/core/descriptorset.cpp | 0 .../src/core/device.cpp | 14 +- .../src/core/fence.cpp | 0 .../src/core/image.cpp | 0 .../src/core/instance.cpp | 0 .../src/core/pipeline.cpp | 0 .../src/core/sampler.cpp | 13 +- .../src/core/semaphore.cpp | 0 .../src/core/shadermodule.cpp | 0 lsfg-vk-v3.1/src/lsfg.cpp | 100 ++++++ lsfg-vk-v3.1/src/pool/resourcepool.cpp | 67 ++++ lsfg-vk-v3.1/src/pool/shaderpool.cpp | 44 +++ lsfg-vk-v3.1/src/shaders/alpha.cpp | 130 +++++++ lsfg-vk-v3.1/src/shaders/beta.cpp | 151 ++++++++ lsfg-vk-v3.1/src/shaders/delta.cpp | 329 ++++++++++++++++++ lsfg-vk-v3.1/src/shaders/gamma.cpp | 181 ++++++++++ lsfg-vk-v3.1/src/shaders/generate.cpp | 73 ++++ lsfg-vk-v3.1/src/shaders/mipmaps.cpp | 56 +++ lsfg-vk-v3.1/src/utils/trans.cpp | 28 ++ .../src/utils/utils.cpp | 41 +-- 83 files changed, 2039 insertions(+), 3032 deletions(-) delete mode 100644 lsfg-vk-gen/CMakeLists.txt delete mode 100644 lsfg-vk-gen/include/context.hpp delete mode 100644 lsfg-vk-gen/include/pool/extract.hpp delete mode 100644 lsfg-vk-gen/include/shaderchains/alpha.hpp delete mode 100644 lsfg-vk-gen/include/shaderchains/beta.hpp delete mode 100644 lsfg-vk-gen/include/shaderchains/delta.hpp delete mode 100644 lsfg-vk-gen/include/shaderchains/downsample.hpp delete mode 100644 lsfg-vk-gen/include/shaderchains/epsilon.hpp delete mode 100644 lsfg-vk-gen/include/shaderchains/extract.hpp delete mode 100644 lsfg-vk-gen/include/shaderchains/gamma.hpp delete mode 100644 lsfg-vk-gen/include/shaderchains/magic.hpp delete mode 100644 lsfg-vk-gen/include/shaderchains/merge.hpp delete mode 100644 lsfg-vk-gen/include/shaderchains/zeta.hpp delete mode 100644 lsfg-vk-gen/src/context.cpp delete mode 100644 lsfg-vk-gen/src/lsfg.cpp delete mode 100644 lsfg-vk-gen/src/pool/extract.cpp delete mode 100644 lsfg-vk-gen/src/pool/shaderpool.cpp delete mode 100644 lsfg-vk-gen/src/shaderchains/alpha.cpp delete mode 100644 lsfg-vk-gen/src/shaderchains/beta.cpp delete mode 100644 lsfg-vk-gen/src/shaderchains/delta.cpp delete mode 100644 lsfg-vk-gen/src/shaderchains/downsample.cpp delete mode 100644 lsfg-vk-gen/src/shaderchains/epsilon.cpp delete mode 100644 lsfg-vk-gen/src/shaderchains/extract.cpp delete mode 100644 lsfg-vk-gen/src/shaderchains/gamma.cpp delete mode 100644 lsfg-vk-gen/src/shaderchains/magic.cpp delete mode 100644 lsfg-vk-gen/src/shaderchains/merge.cpp delete mode 100644 lsfg-vk-gen/src/shaderchains/zeta.cpp create mode 100644 lsfg-vk-v3.1/.clang-tidy-no create mode 100644 lsfg-vk-v3.1/.gitattributes create mode 100644 lsfg-vk-v3.1/.gitignore create mode 100644 lsfg-vk-v3.1/CMakeLists.txt create mode 100644 lsfg-vk-v3.1/LICENSE.md create mode 100644 lsfg-vk-v3.1/README.md create mode 100644 lsfg-vk-v3.1/include/context.hpp rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/buffer.hpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/commandbuffer.hpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/commandpool.hpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/descriptorpool.hpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/descriptorset.hpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/device.hpp (92%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/fence.hpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/image.hpp (91%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/instance.hpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/pipeline.hpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/sampler.hpp (80%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/semaphore.hpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/core/shadermodule.hpp (100%) create mode 100644 lsfg-vk-v3.1/include/pool/resourcepool.hpp rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/pool/shaderpool.hpp (83%) create mode 100644 lsfg-vk-v3.1/include/shaders/alpha.hpp create mode 100644 lsfg-vk-v3.1/include/shaders/beta.hpp create mode 100644 lsfg-vk-v3.1/include/shaders/delta.hpp create mode 100644 lsfg-vk-v3.1/include/shaders/gamma.hpp create mode 100644 lsfg-vk-v3.1/include/shaders/generate.hpp create mode 100644 lsfg-vk-v3.1/include/shaders/mipmaps.hpp create mode 100644 lsfg-vk-v3.1/include/utils/trans.hpp rename {lsfg-vk-gen => lsfg-vk-v3.1}/include/utils/utils.hpp (75%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/public/lsfg.hpp (73%) create mode 100644 lsfg-vk-v3.1/src/context.cpp rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/buffer.cpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/commandbuffer.cpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/commandpool.cpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/descriptorpool.cpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/descriptorset.cpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/device.cpp (89%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/fence.cpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/image.cpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/instance.cpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/pipeline.cpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/sampler.cpp (71%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/semaphore.cpp (100%) rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/core/shadermodule.cpp (100%) create mode 100644 lsfg-vk-v3.1/src/lsfg.cpp create mode 100644 lsfg-vk-v3.1/src/pool/resourcepool.cpp create mode 100644 lsfg-vk-v3.1/src/pool/shaderpool.cpp create mode 100644 lsfg-vk-v3.1/src/shaders/alpha.cpp create mode 100644 lsfg-vk-v3.1/src/shaders/beta.cpp create mode 100644 lsfg-vk-v3.1/src/shaders/delta.cpp create mode 100644 lsfg-vk-v3.1/src/shaders/gamma.cpp create mode 100644 lsfg-vk-v3.1/src/shaders/generate.cpp create mode 100644 lsfg-vk-v3.1/src/shaders/mipmaps.cpp create mode 100644 lsfg-vk-v3.1/src/utils/trans.cpp rename {lsfg-vk-gen => lsfg-vk-v3.1}/src/utils/utils.cpp (86%) diff --git a/lsfg-vk-gen/CMakeLists.txt b/lsfg-vk-gen/CMakeLists.txt deleted file mode 100644 index 592d809..0000000 --- a/lsfg-vk-gen/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -project(lsfg-vk-gen LANGUAGES CXX) - -file(GLOB BACKEND_SOURCES - "src/core/*.cpp" - "src/pool/*.cpp" - "src/shaderchains/*.cpp" - "src/utils/*.cpp" - "src/*.cpp" -) - -add_library(lsfg-vk-gen STATIC ${BACKEND_SOURCES}) - -target_include_directories(lsfg-vk-gen - PRIVATE include - PUBLIC public) -target_link_libraries(lsfg-vk-gen - PUBLIC vulkan peparse dxvk) -target_compile_options(lsfg-vk-gen PRIVATE - -Weverything -fPIC - # 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 - # required for peparse - -Wno-unused-template -) diff --git a/lsfg-vk-gen/include/context.hpp b/lsfg-vk-gen/include/context.hpp deleted file mode 100644 index d4120c2..0000000 --- a/lsfg-vk-gen/include/context.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef CONTEXT_HPP -#define CONTEXT_HPP - -#include "pool/shaderpool.hpp" -#include "core/commandbuffer.hpp" -#include "core/commandpool.hpp" -#include "core/descriptorpool.hpp" -#include "core/fence.hpp" -#include "core/image.hpp" -#include "core/semaphore.hpp" -#include "shaderchains/alpha.hpp" -#include "shaderchains/beta.hpp" -#include "shaderchains/delta.hpp" -#include "shaderchains/downsample.hpp" -#include "shaderchains/epsilon.hpp" -#include "shaderchains/extract.hpp" -#include "shaderchains/gamma.hpp" -#include "shaderchains/magic.hpp" -#include "shaderchains/merge.hpp" -#include "shaderchains/zeta.hpp" - -namespace LSFG { - - /// - /// LSFG context. - /// - class Context { - public: - /// - /// Create a generator instance. - /// - /// @param device The Vulkan device to use. - /// @param shaderpool The shader pool to use. - /// @param width Width of the input images. - /// @param height Height of the input images. - /// @param in0 File descriptor for the first input image. - /// @param in1 File descriptor for the second input image. - /// @param outN File descriptor for the output image. - /// - /// @throws LSFG::vulkan_error if the generator fails to initialize. - /// - Context(const Core::Device& device, Pool::ShaderPool& shaderpool, - uint32_t width, uint32_t height, int in0, int in1, - const std::vector& outN); - - /// - /// Schedule the next generation. - /// - /// @param device The Vulkan device to use. - /// @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 generator fails to present. - /// - void present(const Core::Device& device, int inSem, - const std::vector& outSem); - - // Trivially copyable, moveable and destructible - Context(const Context&) = default; - Context(Context&&) = default; - Context& operator=(const Context&) = default; - Context& operator=(Context&&) = default; - ~Context() = default; - private: - Core::DescriptorPool descPool; - Core::CommandPool cmdPool; - - Core::Image inImg_0, inImg_1; // inImg_0 is next (inImg_1 prev) when fc % 2 == 0 - uint64_t frameIdx{0}; - - struct RenderInfo { - Core::Semaphore inSemaphore; // wait for copy - Core::CommandBuffer cmdBuffer1; - std::vector internalSemaphores; // first step output - std::vector cmdBuffers2; // second step output - std::vector outSemaphores; // signal when done with each pass - std::optional> completionFences; - }; // data for a single render - std::array renderInfos; // 8 passes, why not - - Shaderchains::Downsample downsampleChain; - std::array alphaChains; - Shaderchains::Beta betaChain; - std::array gammaChains; - std::array magicChains; - std::array deltaChains; - std::array epsilonChains; - std::array zetaChains; - std::array extractChains; - Shaderchains::Merge mergeChain; - }; - -} - -#endif // CONTEXT_HPP diff --git a/lsfg-vk-gen/include/pool/extract.hpp b/lsfg-vk-gen/include/pool/extract.hpp deleted file mode 100644 index 53e4b67..0000000 --- a/lsfg-vk-gen/include/pool/extract.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef RESOURCES_HPP -#define RESOURCES_HPP - -#include -#include - -#include -#include -#include -#include - -namespace LSFG::Pool { - - /// - /// DLL resource extractor class. - /// - class Extractor { - public: - Extractor() noexcept = default; - - /// - /// Create a new extractor. - /// - /// @param path Path to the DLL file. - /// - /// @throws std::runtime_error if the file cannot be parsed. - /// - Extractor(const std::string& path); - - /// - /// Get a resource by its hash. - /// - /// @param hash Hash of the resource. - /// @return Resource data - /// - /// @throws std::runtime_error if the resource is not found. - /// - [[nodiscard]] std::vector getResource(uint32_t hash) const; - - // Trivially copyable, moveable and destructible - Extractor(const Extractor&) = delete; - Extractor& operator=(const Extractor&) = delete; - Extractor(Extractor&&) = default; - Extractor& operator=(Extractor&&) = default; - ~Extractor() = default; - private: - std::unordered_map> resources; - }; - - /// - /// Translate DXBC into SPIR-V. - /// - /// @param dxbc Bytecode to translate. - /// @return Translated SPIR-V bytecode. - /// - /// @throws std::runtime_error if the translation fails. - /// - [[nodiscard]] std::vector dxbcToSpirv(const std::vector& dxbc); - -} - - -#endif // RESOURCES_HPP diff --git a/lsfg-vk-gen/include/shaderchains/alpha.hpp b/lsfg-vk-gen/include/shaderchains/alpha.hpp deleted file mode 100644 index b40a8ea..0000000 --- a/lsfg-vk-gen/include/shaderchains/alpha.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef ALPHA_HPP -#define ALPHA_HPP - -#include "pool/shaderpool.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -#include - -namespace LSFG::Shaderchains { - - /// - /// Shader chain alpha. - /// - /// Takes an 8-bit R image creates four quarter-sized 8-bit RGBA images. - /// - class Alpha { - public: - Alpha() = default; - - /// - /// Initialize the shaderchain. - /// - /// @param device The Vulkan device to create the resources on. - /// @param shaderpool The shader pool to use for shader modules. - /// @param pool The descriptor pool to allocate in. - /// @param inImg The input image to process - /// - /// @throws LSFG::vulkan_error if resource creation fails. - /// - Alpha(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - Core::Image inImg); - - /// - /// Dispatch the shaderchain. - /// - /// @param buf The command buffer to use for dispatching. - /// @param fc The frame count, used to determine which output images to write to. - /// - /// @throws std::logic_error if the command buffer is not recording. - /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); - - /// Get the output images written to when fc % 3 == 0 - [[nodiscard]] const auto& getOutImages0() const { return this->outImgs_0; } - /// Get the output images written to when fc % 3 == 1 - [[nodiscard]] const auto& getOutImages1() const { return this->outImgs_1; } - /// Get the output images written to when fc % 3 == 2 - [[nodiscard]] const auto& getOutImages2() const { return this->outImgs_2; } - - /// Trivially copyable, moveable and destructible - Alpha(const Alpha&) noexcept = default; - Alpha& operator=(const Alpha&) noexcept = default; - Alpha(Alpha&&) noexcept = default; - Alpha& operator=(Alpha&&) noexcept = default; - ~Alpha() = default; - private: - std::array shaderModules; - std::array pipelines; - std::array descriptorSets; // last shader is special - std::array specialDescriptorSets; - - Core::Image inImg; - - std::array tempImgs1; - std::array tempImgs2; - std::array tempImgs3; - - std::array outImgs_0; - std::array outImgs_1; - std::array outImgs_2; - }; - -} - -#endif // ALPHA_HPP diff --git a/lsfg-vk-gen/include/shaderchains/beta.hpp b/lsfg-vk-gen/include/shaderchains/beta.hpp deleted file mode 100644 index f39b824..0000000 --- a/lsfg-vk-gen/include/shaderchains/beta.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef BETA_HPP -#define BETA_HPP - -#include "pool/shaderpool.hpp" -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -#include - -namespace LSFG::Shaderchains { - - /// - /// Shader chain beta. - /// - /// Takes eight temporal 8-bit RGBA images, as well as the four output images from alpha, - /// and creates six 8-bit R images, halving in resolution each step. - /// - class Beta { - public: - Beta() = default; - - /// - /// Initialize the shaderchain. - /// - /// @param device The Vulkan device to create the resources on. - /// @param shaderpool The shader pool to use for shader modules. - /// @param pool The descriptor pool to allocate in. - /// @param inImgs_0 The next input images to process (when fc % 3 == 0) - /// @param inImgs_1 The prev input images to process (when fc % 3 == 0) - /// @param inImgs_2 The prev prev input images to process (when fc % 3 == 0) - /// @param genc Amount of frames to generate. - /// - /// @throws LSFG::vulkan_error if resource creation fails. - /// - Beta(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs_0, - std::array inImgs_1, - std::array inImgs_2, - size_t genc); - - /// - /// Dispatch the shaderchain. - /// - /// @param buf The command buffer to use for dispatching. - /// @param fc The frame count, used to select the input images. - /// @param pass The pass number - /// - /// @throws std::logic_error if the command buffer is not recording. - /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass); - - /// Get the output images. - [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } - - /// Trivially copyable, moveable and destructible - Beta(const Beta&) noexcept = default; - Beta& operator=(const Beta&) noexcept = default; - Beta(Beta&&) noexcept = default; - Beta& operator=(Beta&&) noexcept = default; - ~Beta() = default; - private: - std::array shaderModules; - std::array pipelines; - std::array descriptorSets; // first shader has special logic - std::array specialDescriptorSets; - std::vector nDescriptorSets; - std::vector buffers; - - std::array inImgs_0; - std::array inImgs_1; - std::array inImgs_2; - - std::array tempImgs1; - std::array tempImgs2; - - std::array outImgs; - }; - -} - -#endif // BETA_HPP diff --git a/lsfg-vk-gen/include/shaderchains/delta.hpp b/lsfg-vk-gen/include/shaderchains/delta.hpp deleted file mode 100644 index 42220cc..0000000 --- a/lsfg-vk-gen/include/shaderchains/delta.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef DELTA_HPP -#define DELTA_HPP - -#include "pool/shaderpool.hpp" -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -#include - -namespace LSFG::Shaderchains { - - /// - /// Shader chain delta. - /// - /// Takes two 8-bit RGBA images and an optional third 16-bit half-res RGBA image, - /// producing a full-res 16-bit RGBA image. - /// - class Delta { - public: - Delta() = default; - - /// - /// Initialize the shaderchain. - /// - /// @param device The Vulkan device to create the resources on. - /// @param shaderpool The shader pool to use for shader modules. - /// @param pool The descriptor pool to allocate in. - /// @param inImgs The input images to process. - /// @param optImg An optional additional input from the previous pass. - /// @param genc Amount of frames to generate. - /// - /// @throws LSFG::vulkan_error if resource creation fails. - /// - Delta(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs, - std::optional optImg, - size_t genc); - - /// - /// Dispatch the shaderchain. - /// - /// @param buf The command buffer to use for dispatching. - /// @param pass The pass number. - /// - /// @throws std::logic_error if the command buffer is not recording. - /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t pass); - - /// Get the output image. - [[nodiscard]] const auto& getOutImage() const { return this->outImg; } - - /// Trivially copyable, moveable and destructible - Delta(const Delta&) noexcept = default; - Delta& operator=(const Delta&) noexcept = default; - Delta(Delta&&) noexcept = default; - Delta& operator=(Delta&&) noexcept = default; - ~Delta() = default; - private: - std::array shaderModules; - std::array pipelines; - std::array descriptorSets; - std::vector nDescriptorSets; - std::vector buffers; - - std::array inImgs; - std::optional optImg; - - std::array tempImgs1; - std::array tempImgs2; - - Core::Image outImg; - }; - -} - -#endif // DELTA_HPP diff --git a/lsfg-vk-gen/include/shaderchains/downsample.hpp b/lsfg-vk-gen/include/shaderchains/downsample.hpp deleted file mode 100644 index bcf1410..0000000 --- a/lsfg-vk-gen/include/shaderchains/downsample.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef DOWNSAMPLE_HPP -#define DOWNSAMPLE_HPP - -#include "pool/shaderpool.hpp" -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -#include - -namespace LSFG::Shaderchains { - - /// - /// Downsample shader. - /// - /// Takes an 8-bit RGBA image and downsamples it into 7x 8-bit R images. - /// - class Downsample { - public: - Downsample() = default; - - /// - /// Initialize the shaderchain. - /// - /// @param device The Vulkan device to create the resources on. - /// @param shaderpool The shader pool to use for shader modules. - /// @param pool The descriptor pool to allocate in. - /// @param inImg_0 The next full image to downsample (when fc % 2 == 0) - /// @param inImg_1 The next full image to downsample (when fc % 2 == 1) - /// @param genc Amount of frames to generate. - /// - /// @throws LSFG::vulkan_error if resource creation fails. - /// - Downsample(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - Core::Image inImg_0, Core::Image inImg_1, - size_t genc); - - /// - /// Dispatch the shaderchain. - /// - /// @param buf The command buffer to use for dispatching. - /// @param fc The frame count, used to select the input image. - /// - /// @throws std::logic_error if the command buffer is not recording. - /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t fc); - - /// Get the output images. - [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } - - /// Trivially copyable, moveable and destructible - Downsample(const Downsample&) noexcept = default; - Downsample& operator=(const Downsample&) noexcept = default; - Downsample(Downsample&&) noexcept = default; - Downsample& operator=(Downsample&&) noexcept = default; - ~Downsample() = default; - private: - Core::ShaderModule shaderModule; - Core::Pipeline pipeline; - std::array descriptorSets; // one for each input image - Core::Buffer buffer; - - Core::Image inImg_0, inImg_1; - - std::array outImgs; - }; - -} - -#endif // DOWNSAMPLE_HPP diff --git a/lsfg-vk-gen/include/shaderchains/epsilon.hpp b/lsfg-vk-gen/include/shaderchains/epsilon.hpp deleted file mode 100644 index d1c1c61..0000000 --- a/lsfg-vk-gen/include/shaderchains/epsilon.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef EPSILON_HPP -#define EPSILON_HPP - -#include "pool/shaderpool.hpp" -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -#include - -namespace LSFG::Shaderchains { - - /// - /// Shader chain epsilon. - /// - /// Takes three 8-bit RGBA textures, a fourth 8-bit R texture, an optional fifth - /// half-res 16-bit RGBA texture and produces a full-res 16-bit RGBA texture. - /// - class Epsilon { - public: - Epsilon() = default; - - /// - /// Initialize the shaderchain. - /// - /// @param device The Vulkan device to create the resources on. - /// @param shaderpool The shader pool to use for shader modules. - /// @param pool The descriptor pool to use for descriptor sets. - /// @param inImgs1 The first set of input images to process. - /// @param inImg2 The second type image to process. - /// @param optImg An optional additional input from the previous pass. - /// @param genc Amount of frames to generate. - /// - /// @throws LSFG::vulkan_error if resource creation fails. - /// - Epsilon(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs1, - Core::Image inImg2, - std::optional optImg, - size_t genc); - - /// - /// Dispatch the shaderchain. - /// - /// @param buf The command buffer to use for dispatching. - /// @param pass The pass number. - /// - /// @throws std::logic_error if the command buffer is not recording. - /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t pass); - - /// Get the output image. - [[nodiscard]] const auto& getOutImage() const { return this->outImg; } - - /// Trivially copyable, moveable and destructible - Epsilon(const Epsilon&) noexcept = default; - Epsilon& operator=(const Epsilon&) noexcept = default; - Epsilon(Epsilon&&) noexcept = default; - Epsilon& operator=(Epsilon&&) noexcept = default; - ~Epsilon() = default; - private: - std::array shaderModules; - std::array pipelines; - std::array descriptorSets; - std::vector nDescriptorSets; - std::vector buffers; - - std::array inImgs1; - Core::Image inImg2; - std::optional optImg; - - std::array tempImgs1; - std::array tempImgs2; - - Core::Image outImg; - }; - -} - -#endif // EPSILON_HPP diff --git a/lsfg-vk-gen/include/shaderchains/extract.hpp b/lsfg-vk-gen/include/shaderchains/extract.hpp deleted file mode 100644 index 1fdc5ec..0000000 --- a/lsfg-vk-gen/include/shaderchains/extract.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef EXTRACT_HPP -#define EXTRACT_HPP - -#include "pool/shaderpool.hpp" -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -namespace LSFG::Shaderchains { - - /// - /// Shader chain extract. - /// - /// Takes two half-res 16-bit RGBA textures, producing - /// an full-res 8-bit RGBA texture. - /// - class Extract { - public: - Extract() = default; - - /// - /// Initialize the shaderchain. - /// - /// @param device The Vulkan device to create the resources on. - /// @param shaderpool The shader pool to use for shader modules. - /// @param pool The descriptor pool to use for descriptor sets. - /// @param inImg1 The first set of input images to process. - /// @param inImg2 The second type image to process. - /// @param outExtent The extent of the output image. - /// @param genc Amount of frames to generate. - /// - /// @throws LSFG::vulkan_error if resource creation fails. - /// - Extract(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - Core::Image inImg1, - Core::Image inImg2, - VkExtent2D outExtent, - size_t genc); - - /// - /// Dispatch the shaderchain. - /// - /// @param buf The command buffer to use for dispatching. - /// @param pass The pass number. - /// - /// @throws std::logic_error if the command buffer is not recording. - /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t pass); - - /// Get the output image. - [[nodiscard]] const auto& getOutImage() const { return this->outImg; } - - /// Trivially copyable, moveable and destructible - Extract(const Extract&) noexcept = default; - Extract& operator=(const Extract&) noexcept = default; - Extract(Extract&&) noexcept = default; - Extract& operator=(Extract&&) noexcept = default; - ~Extract() = default; - private: - Core::ShaderModule shaderModule; - Core::Pipeline pipeline; - std::vector nDescriptorSets; - std::vector buffers; - - Core::Image inImg1; - Core::Image inImg2; - - Core::Image whiteImg; - - Core::Image outImg; - }; - -} - -#endif // EXTRACT_HPP diff --git a/lsfg-vk-gen/include/shaderchains/gamma.hpp b/lsfg-vk-gen/include/shaderchains/gamma.hpp deleted file mode 100644 index 8ae424b..0000000 --- a/lsfg-vk-gen/include/shaderchains/gamma.hpp +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef GAMMA_HPP -#define GAMMA_HPP - -#include "pool/shaderpool.hpp" -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -#include - -namespace LSFG::Shaderchains { - - /// - /// Shader chain gamma. - /// - /// Takes four temporal 8-bit RGBA images, as well as four output images from a given alpha stage. - /// Also takes the corresponding (smallest if oob) output image from the beta pass. - /// On non-first passes optionally takes 2 output images from previous gamma pass. - /// Creates two images, one at twice the resolution of input images and the other with R16G16B16A16_FLOAT. - /// - class Gamma { - public: - Gamma() = default; - - /// - /// Initialize the shaderchain. - /// - /// @param device The Vulkan device to create the resources on. - /// @param shaderpool The shader pool to use for shader modules. - /// @param pool The descriptor pool to allocate in. - /// @param inImgs1_0 The next input images to process (when fc % 3 == 0). - /// @param inImgs1_1 The prev input images to process (when fc % 3 == 0). - /// @param inImgs1_2 Initially unprocessed prev prev input images (when fc % 3 == 0). - /// @param inImg2 The second input image to process, next step up the resolution. - /// @param optImg1 An optional additional input from the previous pass. - /// @param optImg2 An optional additional input image for processing non-first passes. - /// @param outExtent The extent of the output image. - /// @param genc Amount of frames to generate. - /// - /// @throws LSFG::vulkan_error if resource creation fails. - /// - Gamma(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs1_0, - std::array inImgs1_1, - std::array inImgs1_2, - Core::Image inImg2, - std::optional optImg1, - std::optional optImg2, - VkExtent2D outExtent, - size_t genc); - - /// - /// Dispatch the shaderchain. - /// - /// @param buf The command buffer to use for dispatching. - /// @param fc The frame count, used to select the input images. - /// @param pass The pass number. - /// - /// @throws std::logic_error if the command buffer is not recording. - /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass); - - /// 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 - Gamma(const Gamma&) noexcept = default; - Gamma& operator=(const Gamma&) noexcept = default; - Gamma(Gamma&&) noexcept = default; - Gamma& operator=(Gamma&&) noexcept = default; - ~Gamma() = default; - private: - std::array shaderModules; - std::array pipelines; - std::array descriptorSets; // first shader has special logic - std::vector n1DescriptorSets; - std::vector n2DescriptorSets; - std::vector> nSpecialDescriptorSets; - std::vector buffers; - - std::array inImgs1_0; - std::array inImgs1_1; - std::array inImgs1_2; - Core::Image inImg2; - Core::Image optImg1; // specified or created black - std::optional optImg2; - - std::array tempImgs1; - std::array tempImgs2; - Core::Image whiteImg; - - Core::Image outImg1; - Core::Image outImg2; - }; - -} - -#endif // GAMMA_HPP diff --git a/lsfg-vk-gen/include/shaderchains/magic.hpp b/lsfg-vk-gen/include/shaderchains/magic.hpp deleted file mode 100644 index 545285a..0000000 --- a/lsfg-vk-gen/include/shaderchains/magic.hpp +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef MAGIC_HPP -#define MAGIC_HPP - -#include "pool/shaderpool.hpp" -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -#include - -namespace LSFG::Shaderchains { - - /// - /// Shader chain magic. - /// - /// Takes textures similar to gamma shader chain, produces intermediary - /// results in groups of 3, 2, 2. - /// - class Magic { - public: - Magic() = default; - - /// - /// Initialize the shaderchain. - /// - /// @param device The Vulkan device to create the resources on. - /// @param shaderpool The shader pool to use for shader modules. - /// @param pool The descriptor pool to use for descriptor sets. - /// @param inImgs1_0 The next input images to process (when fc % 3 == 0). - /// @param inImgs1_1 The prev input images to process (when fc % 3 == 0). - /// @param inImgs1_2 Initially unprocessed prev prev input images (when fc % 3 == 0). - /// @param inImg2 The second input image to process. - /// @param inImg3 The third input image to process, next step up the resolution. - /// @param optImg An optional additional input from the previous pass. - /// @param genc Amount of frames to generate. - /// - /// @throws LSFG::vulkan_error if resource creation fails. - /// - Magic(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs1_0, - std::array inImgs1_1, - std::array inImgs1_2, - Core::Image inImg2, - Core::Image inImg3, - std::optional optImg, - size_t genc); - - /// - /// Dispatch the shaderchain. - /// - /// @param buf The command buffer to use for dispatching. - /// @param fc The frame count, used to select the input images. - /// @param pass The pass number. - /// - /// @throws std::logic_error if the command buffer is not recording. - /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass); - - /// Get the first set of output images - [[nodiscard]] const auto& getOutImages1() const { return this->outImgs1; } - /// Get the second set of output images - [[nodiscard]] const auto& getOutImages2() const { return this->outImgs2; } - /// Get the third set of output images - [[nodiscard]] const auto& getOutImages3() const { return this->outImgs3; } - - /// Trivially copyable, moveable and destructible - Magic(const Magic&) noexcept = default; - Magic& operator=(const Magic&) noexcept = default; - Magic(Magic&&) noexcept = default; - Magic& operator=(Magic&&) noexcept = default; - ~Magic() = default; - private: - Core::ShaderModule shaderModule; - Core::Pipeline pipeline; - std::vector> nDescriptorSets; - std::vector buffers; - - std::array inImgs1_0; - std::array inImgs1_1; - std::array inImgs1_2; - Core::Image inImg2; - Core::Image inImg3; - std::optional optImg; - - std::array outImgs1; - std::array outImgs2; - std::array outImgs3; - }; - -} - -#endif // MAGIC_HPP diff --git a/lsfg-vk-gen/include/shaderchains/merge.hpp b/lsfg-vk-gen/include/shaderchains/merge.hpp deleted file mode 100644 index 3ef81bc..0000000 --- a/lsfg-vk-gen/include/shaderchains/merge.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef MERGE_HPP -#define MERGE_HPP - -#include "pool/shaderpool.hpp" -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -#include - -namespace LSFG::Shaderchains { - - /// - /// Shader chain merge. - /// - /// Takes the two previous frames as well as related resources - /// and merges them into a new frame. - /// - class Merge { - public: - Merge() = default; - - /// - /// Initialize the shaderchain. - /// - /// @param device The Vulkan device to create the resources on. - /// @param shaderpool The shader pool to use for shader modules. - /// @param pool The descriptor pool to use for descriptor sets. - /// @param inImg1 The prev full image when fc % 2 == 0 - /// @param inImg2 The next full image when fc % 2 == 0 - /// @param inImg3 The first related input texture - /// @param inImg4 The second related input texture - /// @param inImg5 The third related input texture - /// @param outFds File descriptors for the output images. - /// @param genc The amount of frames to generaten. - /// - /// @throws LSFG::vulkan_error if resource creation fails. - /// - Merge(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - Core::Image inImg1, - Core::Image inImg2, - Core::Image inImg3, - Core::Image inImg4, - Core::Image inImg5, - const std::vector& outFds, - size_t genc); - - /// - /// Dispatch the shaderchain. - /// - /// @param buf The command buffer to use for dispatching. - /// @param fc The frame count, used to select the input images. - /// @param pass The pass number. - /// - /// @throws std::logic_error if the command buffer is not recording. - /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass); - - /// Get the output image - [[nodiscard]] const auto& getOutImage(size_t pass) const { return this->outImgs.at(pass); } - - /// Trivially copyable, moveable and destructible - Merge(const Merge&) noexcept = default; - Merge& operator=(const Merge&) noexcept = default; - Merge(Merge&&) noexcept = default; - Merge& operator=(Merge&&) noexcept = default; - ~Merge() = default; - private: - Core::ShaderModule shaderModule; - Core::Pipeline pipeline; - std::vector> nDescriptorSets; // per combo - std::vector buffers; - - Core::Image inImg1; - Core::Image inImg2; - Core::Image inImg3; - Core::Image inImg4; - Core::Image inImg5; - - std::vector outImgs; - }; - -} - -#endif // MERGE_HPP diff --git a/lsfg-vk-gen/include/shaderchains/zeta.hpp b/lsfg-vk-gen/include/shaderchains/zeta.hpp deleted file mode 100644 index 642a6d9..0000000 --- a/lsfg-vk-gen/include/shaderchains/zeta.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef ZETA_HPP -#define ZETA_HPP - -#include "pool/shaderpool.hpp" -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/descriptorset.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -#include - -namespace LSFG::Shaderchains { - - /// - /// Shader chain zeta. - /// - /// Takes three 8-bit RGBA textures, a fourth 8-bit R texture, a fifth - /// half-res 16-bit RGBA texture and produces a full-res 16-bit RGBA texture. - /// - class Zeta { - public: - Zeta() = default; - - /// - /// Initialize the shaderchain. - /// - /// @param device The Vulkan device to create the resources on. - /// @param shaderpool The shader pool to use for shader modules. - /// @param pool The descriptor pool to use for descriptor sets. - /// @param inImgs1 The first set of input images to process. - /// @param inImg2 The second type image to process. - /// @param inImg3 The third type image to process. - /// @param genc The amount of frames to generate. - /// - /// @throws LSFG::vulkan_error if resource creation fails. - /// - Zeta(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs1, - Core::Image inImg2, - Core::Image inImg3, - size_t genc); - - /// - /// Dispatch the shaderchain. - /// - /// @param buf The command buffer to use for dispatching. - /// @param pass The pass number. - /// - /// @throws std::logic_error if the command buffer is not recording. - /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t pass); - - /// Get the output image. - [[nodiscard]] const auto& getOutImage() const { return this->outImg; } - - /// Trivially copyable, moveable and destructible - Zeta(const Zeta&) noexcept = default; - Zeta& operator=(const Zeta&) noexcept = default; - Zeta(Zeta&&) noexcept = default; - Zeta& operator=(Zeta&&) noexcept = default; - ~Zeta() = default; - private: - std::array shaderModules; - std::array pipelines; - std::array descriptorSets; - std::vector nDescriptorSets; - std::vector buffers; - - std::array inImgs1; - Core::Image inImg2; - Core::Image inImg3; - - std::array tempImgs1; - std::array tempImgs2; - - Core::Image outImg; - }; - -} - -#endif // ZETA_HPP diff --git a/lsfg-vk-gen/src/context.cpp b/lsfg-vk-gen/src/context.cpp deleted file mode 100644 index 8c9b3da..0000000 --- a/lsfg-vk-gen/src/context.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include "context.hpp" -#include "core/fence.hpp" -#include "core/semaphore.hpp" -#include "pool/shaderpool.hpp" -#include "lsfg.hpp" - -#include - -#include -#include - -using namespace LSFG; - -Context::Context(const Core::Device& device, Pool::ShaderPool& shaderpool, - uint32_t width, uint32_t height, int in0, int in1, - const std::vector& outN) { - const VkFormat format = getenv("LSFG_HDR") == nullptr - ? VK_FORMAT_R8G8B8A8_UNORM - : VK_FORMAT_R16G16B16A16_SFLOAT; - - // import images - this->inImg_0 = Core::Image(device, { width, height }, - format, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, - in0); - this->inImg_1 = Core::Image(device, { width, height }, - format, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, - in1); - - // prepare render infos - this->descPool = Core::DescriptorPool(device); - this->cmdPool = Core::CommandPool(device); - for (size_t i = 0; i < 8; i++) { - auto& info = this->renderInfos.at(i); - info.internalSemaphores.resize(outN.size()); - info.cmdBuffers2.resize(outN.size()); - info.outSemaphores.resize(outN.size()); - } - - // create shader chains - this->downsampleChain = Shaderchains::Downsample(device, shaderpool, this->descPool, - this->inImg_0, this->inImg_1, outN.size()); - for (size_t i = 0; i < 7; i++) - this->alphaChains.at(i) = Shaderchains::Alpha(device, shaderpool, this->descPool, - this->downsampleChain.getOutImages().at(i)); - this->betaChain = Shaderchains::Beta(device, shaderpool, this->descPool, - this->alphaChains.at(0).getOutImages0(), - this->alphaChains.at(0).getOutImages1(), - this->alphaChains.at(0).getOutImages2(), outN.size()); - for (size_t i = 0; i < 7; i++) { - if (i < 4) { - this->gammaChains.at(i) = Shaderchains::Gamma(device, shaderpool, this->descPool, - this->alphaChains.at(6 - i).getOutImages0(), - this->alphaChains.at(6 - i).getOutImages1(), - this->alphaChains.at(6 - i).getOutImages2(), - this->betaChain.getOutImages().at(std::min(5UL, 6 - i)), - i == 0 ? std::nullopt - : std::optional{this->gammaChains.at(i - 1).getOutImage2()}, - i == 0 ? std::nullopt - : std::optional{this->gammaChains.at(i - 1).getOutImage1()}, - this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent(), - outN.size() - ); - } else { - this->magicChains.at(i - 4) = Shaderchains::Magic(device, shaderpool, this->descPool, - this->alphaChains.at(6 - i).getOutImages0(), - this->alphaChains.at(6 - i).getOutImages1(), - this->alphaChains.at(6 - i).getOutImages2(), - i == 4 ? this->gammaChains.at(i - 1).getOutImage2() - : this->extractChains.at(i - 5).getOutImage(), - i == 4 ? this->gammaChains.at(i - 1).getOutImage1() - : this->zetaChains.at(i - 5).getOutImage(), - i == 4 ? std::nullopt : std::optional{this->epsilonChains.at(i - 5).getOutImage()}, - outN.size() - ); - this->deltaChains.at(i - 4) = Shaderchains::Delta(device, shaderpool, this->descPool, - this->magicChains.at(i - 4).getOutImages1(), - i == 4 ? std::nullopt - : std::optional{this->deltaChains.at(i - 5).getOutImage()}, - outN.size() - ); - this->epsilonChains.at(i - 4) = Shaderchains::Epsilon(device, shaderpool, this->descPool, - this->magicChains.at(i - 4).getOutImages2(), - this->betaChain.getOutImages().at(6 - i), - i == 4 ? std::nullopt - : std::optional{this->epsilonChains.at(i - 5).getOutImage()}, - outN.size() - ); - this->zetaChains.at(i - 4) = Shaderchains::Zeta(device, shaderpool, this->descPool, - this->magicChains.at(i - 4).getOutImages3(), - i == 4 ? this->gammaChains.at(i - 1).getOutImage1() - : this->zetaChains.at(i - 5).getOutImage(), - this->betaChain.getOutImages().at(6 - i), - outN.size() - ); - if (i >= 6) - continue; // no extract for i >= 6 - this->extractChains.at(i - 4) = Shaderchains::Extract(device, shaderpool, this->descPool, - this->zetaChains.at(i - 4).getOutImage(), - this->epsilonChains.at(i - 4).getOutImage(), - this->alphaChains.at(6 - i - 1).getOutImages0().at(0).getExtent(), - outN.size() - ); - } - } - this->mergeChain = Shaderchains::Merge(device, shaderpool, this->descPool, - this->inImg_1, - this->inImg_0, - this->zetaChains.at(2).getOutImage(), - this->epsilonChains.at(2).getOutImage(), - this->deltaChains.at(2).getOutImage(), - outN, - outN.size() - ); -} - -void Context::present(const Core::Device& device, int inSem, - const std::vector& outSem) { - auto& info = this->renderInfos.at(this->frameIdx % 8); - - // 3. wait for completion of previous frame in this slot - if (info.completionFences.has_value()) - for (auto& fence : *info.completionFences) - if (!fence.wait(device, UINT64_MAX)) // should not take any time - throw vulkan_error(VK_ERROR_DEVICE_LOST, "Fence wait timed out"); - - // 1. downsample and process input image - info.inSemaphore = Core::Semaphore(device, inSem); - for (size_t i = 0; i < outSem.size(); i++) - info.internalSemaphores.at(i) = Core::Semaphore(device); - - info.cmdBuffer1 = Core::CommandBuffer(device, this->cmdPool); - info.cmdBuffer1.begin(); - - this->downsampleChain.Dispatch(info.cmdBuffer1, this->frameIdx); - for (size_t i = 0; i < 7; i++) - this->alphaChains.at(6 - i).Dispatch(info.cmdBuffer1, this->frameIdx); - - info.cmdBuffer1.end(); - info.cmdBuffer1.submit(device.getComputeQueue(), std::nullopt, - { info.inSemaphore }, std::nullopt, - info.internalSemaphores, std::nullopt); - - // 2. generate intermediary frames - info.completionFences.emplace(); - for (size_t pass = 0; pass < outSem.size(); pass++) { - auto& completionFence = info.completionFences->emplace_back(device); - auto& outSemaphore = info.outSemaphores.at(pass); - outSemaphore = Core::Semaphore(device, outSem.at(pass)); - - auto& cmdBuffer2 = info.cmdBuffers2.at(pass); - cmdBuffer2 = Core::CommandBuffer(device, this->cmdPool); - cmdBuffer2.begin(); - - this->betaChain.Dispatch(cmdBuffer2, this->frameIdx, pass); - for (size_t i = 0; i < 4; i++) - this->gammaChains.at(i).Dispatch(cmdBuffer2, this->frameIdx, pass); - for (size_t i = 0; i < 3; i++) { - this->magicChains.at(i).Dispatch(cmdBuffer2, this->frameIdx, pass); - this->deltaChains.at(i).Dispatch(cmdBuffer2, pass); - this->epsilonChains.at(i).Dispatch(cmdBuffer2, pass); - this->zetaChains.at(i).Dispatch(cmdBuffer2, pass); - if (i < 2) - this->extractChains.at(i).Dispatch(cmdBuffer2, pass); - } - this->mergeChain.Dispatch(cmdBuffer2, this->frameIdx, pass); - - cmdBuffer2.end(); - - cmdBuffer2.submit(device.getComputeQueue(), completionFence, - { info.internalSemaphores.at(pass) }, std::nullopt, - { outSemaphore }, std::nullopt); - } - - this->frameIdx++; -} - -vulkan_error::vulkan_error(VkResult result, const std::string& message) - : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} - -vulkan_error::~vulkan_error() noexcept = default; diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp deleted file mode 100644 index dfad72f..0000000 --- a/lsfg-vk-gen/src/lsfg.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "lsfg.hpp" -#include "core/device.hpp" -#include "core/instance.hpp" -#include "context.hpp" -#include "pool/shaderpool.hpp" -#include "utils/utils.hpp" - -#include -#include -#include -#include -#include - -using namespace LSFG; - -namespace { - std::optional instance; - std::optional device; - std::optional pool; - std::unordered_map contexts; -} - -void LSFG::initialize() { - if (instance.has_value() || device.has_value()) - return; - - char* dllPath = getenv("LSFG_DLL_PATH"); - std::string dllPathStr; // (absolutely beautiful code) - if (dllPath && *dllPath != '\0') { - dllPathStr = std::string(dllPath); - } else { - const char* dataDir = getenv("XDG_DATA_HOME"); - if (dataDir && *dataDir != '\0') { - dllPathStr = std::string(dataDir) + - "/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; - } else { - const char* homeDir = getenv("HOME"); - if (homeDir && *homeDir != '\0') { - dllPathStr = std::string(homeDir) + - "/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; - } else { - dllPathStr = "Lossless.dll"; - } - } - } - - instance.emplace(); - device.emplace(*instance); - pool.emplace(dllPathStr); - - Globals::initializeGlobals(*device); - - std::srand(static_cast(std::time(nullptr))); -} - -int32_t LSFG::createContext(uint32_t width, uint32_t height, int in0, int in1, - const std::vector& outN) { - if (!instance.has_value() || !device.has_value() || !pool.has_value()) - throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); - - auto id = std::rand(); - contexts.emplace(id, Context(*device, *pool, width, height, in0, in1, outN)); - return id; -} - -void LSFG::presentContext(int32_t id, int inSem, const std::vector& outSem) { - if (!instance.has_value() || !device.has_value() || !pool.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"); - - Context& context = it->second; - context.present(*device, inSem, outSem); -} - -void LSFG::deleteContext(int32_t id) { - if (!instance.has_value() || !device.has_value() || !pool.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->handle()); - contexts.erase(it); -} - -void LSFG::finalize() { - if (!instance.has_value() || !device.has_value() || !pool.has_value()) - return; - - Globals::uninitializeGlobals(); - - vkDeviceWaitIdle(device->handle()); - pool.reset(); - device.reset(); - instance.reset(); -} diff --git a/lsfg-vk-gen/src/pool/extract.cpp b/lsfg-vk-gen/src/pool/extract.cpp deleted file mode 100644 index a7dc114..0000000 --- a/lsfg-vk-gen/src/pool/extract.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "pool/extract.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -using namespace LSFG; -using namespace LSFG::Pool; - -namespace { - - using ResourceMap = std::unordered_map>; - - uint32_t fnv1a_hash(const std::vector& data) { - // does not need be secure - uint32_t hash = 0x811C9DC5; - for (auto byte : data) { - hash ^= byte; - hash *= 0x01000193; - } - return hash; - } - - /// Callback function for each resource. - int on_resource(void* data, const peparse::resource& res) { - if (res.type != peparse::RT_RCDATA || res.buf == nullptr || res.buf->bufLen <= 0) - return 0; - std::vector resource_data(res.buf->bufLen); - std::copy_n(res.buf->buf, res.buf->bufLen, resource_data.data()); - - const uint32_t hash = fnv1a_hash(resource_data); - - auto* map = reinterpret_cast(data); - (*map)[hash] = resource_data; - - return 0; - } - -} - -Extractor::Extractor(const std::string& path) { - peparse::parsed_pe* pe = peparse::ParsePEFromFile(path.c_str()); - if (!pe) - throw std::runtime_error("Unable to parse PE file: " + path); - - peparse::IterRsrc(pe, on_resource, &this->resources); - peparse::DestructParsedPE(pe); -} - -std::vector Extractor::getResource(uint32_t hash) const { - auto it = this->resources.find(hash); - if (it != this->resources.end()) - return it->second; - throw std::runtime_error("Resource not found."); -} - -std::vector Pool::dxbcToSpirv(const std::vector& dxbc) { - // compile the shader - dxvk::DxbcReader reader(reinterpret_cast(dxbc.data()), dxbc.size()); - dxvk::DxbcModule module(reader); - const dxvk::DxbcModuleInfo info{}; - auto shader = module.compile(info, "CS"); - - // extract spir-v from d3d11 shader - auto code = shader->getRawCode(); - - // patch binding offsets -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" - for (size_t i = 0; i < shader->m_bindingOffsets.size(); i++) - code.data()[shader->m_bindingOffsets.at(i).bindingOffset] = static_cast(i); // NOLINT -#pragma clang diagnostic pop - - std::vector spirv(code.size()); - std::copy_n(reinterpret_cast(code.data()), - code.size(), spirv.data()); - return spirv; -} diff --git a/lsfg-vk-gen/src/pool/shaderpool.cpp b/lsfg-vk-gen/src/pool/shaderpool.cpp deleted file mode 100644 index 33cce03..0000000 --- a/lsfg-vk-gen/src/pool/shaderpool.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "pool/shaderpool.hpp" - -using namespace LSFG; -using namespace LSFG::Pool; - -const std::unordered_map SHADERS = { - { "downsample.spv", 0xe365474d }, - { "alpha/0.spv", 0x35f63c83 }, - { "alpha/1.spv", 0x83e5240d }, - { "alpha/2.spv", 0x5d64d9f1 }, - { "alpha/3.spv", 0xad77afe1 }, - { "beta/0.spv", 0xa986ccbb }, - { "beta/1.spv", 0x60944cf5 }, - { "beta/2.spv", 0xb1c8f69b }, - { "beta/3.spv", 0x87cbe880 }, - { "beta/4.spv", 0xc2c5507d }, - { "gamma/0.spv", 0xccce9dab }, - { "gamma/1.spv", 0x7719e229 }, - { "gamma/2.spv", 0xfb1a7643 }, - { "gamma/3.spv", 0xe0553cd8 }, - { "gamma/4.spv", 0xf73c136f }, - { "gamma/5.spv", 0xa34959c }, - { "magic.spv", 0x443ea7a1 }, - { "delta/0.spv", 0x141daaac }, - { "delta/1.spv", 0x2a0ed691 }, - { "delta/2.spv", 0x23bdc583 }, - { "delta/3.spv", 0x52bc5e0f }, - { "epsilon/0.spv", 0x128eb7d7 }, - { "epsilon/1.spv", 0xbab811ad }, - { "epsilon/2.spv", 0x1d4b902d }, - { "epsilon/3.spv", 0x91236549 }, - { "zeta/0.spv", 0x7719e229 }, - { "zeta/1.spv", 0xfb1a7643 }, - { "zeta/2.spv", 0xe0553cd8 }, - { "zeta/3.spv", 0xf73c136f }, - { "extract.spv", 0xb6cb084a }, - { "merge.spv", 0xfc0aedfa } -}; - -Core::ShaderModule ShaderPool::getShader( - const Core::Device& device, const std::string& name, - const std::vector>& types) { - auto it = shaders.find(name); - if (it != shaders.end()) - return it->second; - - // grab the shader - auto hit = SHADERS.find(name); - if (hit == SHADERS.end()) - throw std::runtime_error("Shader not found: " + name); - auto hash = hit->second; - - auto dxbc = this->extractor.getResource(hash); - if (dxbc.empty()) - throw std::runtime_error("Shader code is empty: " + name); - - // create the translated shader module - auto spirv = dxbcToSpirv(dxbc); - if (spirv.empty()) - throw std::runtime_error("Shader code translation failed: " + name); - - Core::ShaderModule shader(device, spirv, types); - shaders[name] = shader; - return shader; -} - -Core::Pipeline ShaderPool::getPipeline( - const Core::Device& device, const std::string& name) { - auto it = pipelines.find(name); - if (it != pipelines.end()) - return it->second; - - // grab the shader module - auto shader = this->getShader(device, name, {}); - - // create the pipeline - Core::Pipeline pipeline(device, shader); - pipelines[name] = pipeline; - return pipeline; -} diff --git a/lsfg-vk-gen/src/shaderchains/alpha.cpp b/lsfg-vk-gen/src/shaderchains/alpha.cpp deleted file mode 100644 index 3b8b10c..0000000 --- a/lsfg-vk-gen/src/shaderchains/alpha.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include "shaderchains/alpha.hpp" -#include "utils/utils.hpp" - -using namespace LSFG::Shaderchains; - -Alpha::Alpha(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - Core::Image inImg) - : inImg(std::move(inImg)) { - this->shaderModules = {{ - shaderpool.getShader(device, "alpha/0.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "alpha/1.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "alpha/2.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "alpha/3.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) - }}; - this->pipelines = {{ - shaderpool.getPipeline(device, "alpha/0.spv"), - shaderpool.getPipeline(device, "alpha/1.spv"), - shaderpool.getPipeline(device, "alpha/2.spv"), - shaderpool.getPipeline(device, "alpha/3.spv") - }}; - for (size_t i = 0; i < 3; i++) - this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, - this->shaderModules.at(i)); - for (size_t i = 0; i < 3; i++) - this->specialDescriptorSets.at(i) = Core::DescriptorSet(device, pool, - this->shaderModules.at(3)); - - const auto 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(device, - halfExtent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - this->tempImgs2.at(i) = Core::Image(device, - halfExtent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - } - - const VkExtent2D quarterExtent = { - .width = (extent.width + 3) >> 2, - .height = (extent.height + 3) >> 2 - }; - for (size_t i = 0; i < 4; i++) { - this->tempImgs3.at(i) = Core::Image(device, - quarterExtent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - this->outImgs_0.at(i) = Core::Image(device, - quarterExtent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT - | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_ASPECT_COLOR_BIT); - this->outImgs_1.at(i) = Core::Image(device, - quarterExtent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT - | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_ASPECT_COLOR_BIT); - this->outImgs_2.at(i) = Core::Image(device, - quarterExtent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT - | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_ASPECT_COLOR_BIT); - } - - this->descriptorSets.at(0).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - this->descriptorSets.at(1).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .build(); - this->descriptorSets.at(2).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs3) - .build(); - for (size_t fc = 0; fc < 3; fc++) { - auto* outImgs = &this->outImgs_0; - if (fc == 1) outImgs = &this->outImgs_1; - else if (fc == 2) outImgs = &this->outImgs_2; - this->specialDescriptorSets.at(fc).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, *outImgs) - .build(); - } - - // clear the output images (so they're not undefined) - for (size_t i = 0; i < 4; i++) { - Utils::clearImage(device, this->outImgs_0.at(i)); - Utils::clearImage(device, this->outImgs_1.at(i)); - Utils::clearImage(device, this->outImgs_2.at(i)); - } -} - -void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { - const auto halfExtent = this->tempImgs1.at(0).getExtent(); - const auto quarterExtent = this->tempImgs3.at(0).getExtent(); - - // first pass - 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 - 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 - auto* outImgs = &this->outImgs_0; - if ((fc % 3) == 1) outImgs = &this->outImgs_1; - else if ((fc % 3) == 2) outImgs = &this->outImgs_2; - Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs3) - .addR2W(*outImgs) - .build(); - - this->pipelines.at(3).bind(buf); - this->specialDescriptorSets.at(fc % 3).bind(buf, this->pipelines.at(3)); - buf.dispatch(threadsX, threadsY, 1); -} diff --git a/lsfg-vk-gen/src/shaderchains/beta.cpp b/lsfg-vk-gen/src/shaderchains/beta.cpp deleted file mode 100644 index 55e8f82..0000000 --- a/lsfg-vk-gen/src/shaderchains/beta.cpp +++ /dev/null @@ -1,189 +0,0 @@ -#include "shaderchains/beta.hpp" -#include "utils/utils.hpp" - -using namespace LSFG::Shaderchains; - -Beta::Beta(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs_0, - std::array inImgs_1, - std::array inImgs_2, - size_t genc) - : inImgs_0(std::move(inImgs_0)), - inImgs_1(std::move(inImgs_1)), - inImgs_2(std::move(inImgs_2)) { - this->shaderModules = {{ - shaderpool.getShader(device, "beta/0.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 8+4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "beta/1.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "beta/2.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "beta/3.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "beta/4.spv", - { { 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 = {{ - shaderpool.getPipeline(device, "beta/0.spv"), - shaderpool.getPipeline(device, "beta/1.spv"), - shaderpool.getPipeline(device, "beta/2.spv"), - shaderpool.getPipeline(device, "beta/3.spv"), - shaderpool.getPipeline(device, "beta/4.spv") - }}; - for (size_t i = 1; i < 4; i++) - this->descriptorSets.at(i - 1) = Core::DescriptorSet(device, pool, - this->shaderModules.at(i)); - for (size_t i = 0; i < 3; i++) - this->specialDescriptorSets.at(i) = Core::DescriptorSet(device, pool, - this->shaderModules.at(0)); - for (size_t i = 0; i < genc; i++) - this->nDescriptorSets.emplace_back(device, pool, - this->shaderModules.at(4)); - for (size_t i = 0; i < genc; i++) { - auto data = Globals::fgBuffer; - data.timestamp = static_cast(i + 1) / static_cast(genc + 1); - this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - } - - const auto extent = this->inImgs_0.at(0).getExtent(); - - for (size_t i = 0; i < 2; i++) { - this->tempImgs1.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - this->tempImgs2.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - } - - for (size_t i = 0; i < 6; i++) { - this->outImgs.at(i) = Core::Image(device, - { extent.width >> i, extent.height >> i }, - VK_FORMAT_R8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - } - - for (size_t fc = 0; fc < 3; fc++) { - auto* nextImgs = &this->inImgs_0; - auto* prevImgs = &this->inImgs_2; - auto* pprevImgs = &this->inImgs_1; - if (fc == 1) { - nextImgs = &this->inImgs_1; - prevImgs = &this->inImgs_0; - pprevImgs = &this->inImgs_2; - } else if (fc == 2) { - nextImgs = &this->inImgs_2; - prevImgs = &this->inImgs_1; - pprevImgs = &this->inImgs_0; - } - this->specialDescriptorSets.at(fc).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *pprevImgs) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *nextImgs) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - } - this->descriptorSets.at(0).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .build(); - this->descriptorSets.at(1).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - this->descriptorSets.at(2).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .build(); - for (size_t i = 0; i < genc; i++) { - this->nDescriptorSets.at(i).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .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 fc, uint64_t pass) { - const auto extent = this->tempImgs1.at(0).getExtent(); - - // first pass - uint32_t threadsX = (extent.width + 7) >> 3; - uint32_t threadsY = (extent.height + 7) >> 3; - - Utils::BarrierBuilder(buf) - .addW2R(this->inImgs_0) - .addW2R(this->inImgs_1) - .addW2R(this->inImgs_2) - .addR2W(this->tempImgs1) - .build(); - - this->pipelines.at(0).bind(buf); - this->specialDescriptorSets.at(fc % 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->nDescriptorSets.at(pass).bind(buf, this->pipelines.at(4)); - buf.dispatch(threadsX, threadsY, 1); -} diff --git a/lsfg-vk-gen/src/shaderchains/delta.cpp b/lsfg-vk-gen/src/shaderchains/delta.cpp deleted file mode 100644 index 67c3eae..0000000 --- a/lsfg-vk-gen/src/shaderchains/delta.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include "shaderchains/delta.hpp" -#include "utils/utils.hpp" - -using namespace LSFG::Shaderchains; - -Delta::Delta(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs, - std::optional optImg, - size_t genc) - : inImgs(std::move(inImgs)), - optImg(std::move(optImg)) { - this->shaderModules = {{ - shaderpool.getShader(device, "delta/0.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "delta/1.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "delta/2.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "delta/3.spv", - { { 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 = {{ - shaderpool.getPipeline(device, "delta/0.spv"), - shaderpool.getPipeline(device, "delta/1.spv"), - shaderpool.getPipeline(device, "delta/2.spv"), - shaderpool.getPipeline(device, "delta/3.spv") - }}; - for (size_t i = 0; i < 3; i++) - this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, - this->shaderModules.at(i)); - for (size_t i = 0; i < genc; i++) - this->nDescriptorSets.emplace_back(device, pool, - this->shaderModules.at(3)); - for (size_t i = 0; i < genc; i++) { - auto data = Globals::fgBuffer; - data.timestamp = static_cast(i + 1) / static_cast(genc + 1); - data.firstIterS = !this->optImg.has_value(); - this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - } - - const auto extent = this->inImgs.at(0).getExtent(); - - for (size_t i = 0; i < 2; i++) { - this->tempImgs1.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - this->tempImgs2.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - } - - this->outImg = Core::Image(device, - extent, - VK_FORMAT_R16G16B16A16_SFLOAT, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - this->descriptorSets.at(0).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - this->descriptorSets.at(1).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .build(); - this->descriptorSets.at(2).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - for (size_t i = 0; i < genc; i++) { - this->nDescriptorSets.at(i).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .build(); - } -} - -void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t pass) { - const auto extent = this->tempImgs1.at(0).getExtent(); - - // first pass - const uint32_t threadsX = (extent.width + 7) >> 3; - const uint32_t threadsY = (extent.height + 7) >> 3; - - Utils::BarrierBuilder(buf) - .addW2R(this->inImgs) - .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 - Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs2) - .addR2W(this->tempImgs1) - .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->tempImgs1) - .addW2R(this->optImg) - .addR2W(this->outImg) - .build(); - - this->pipelines.at(3).bind(buf); - this->nDescriptorSets.at(pass).bind(buf, this->pipelines.at(3)); - buf.dispatch(threadsX, threadsY, 1); -} diff --git a/lsfg-vk-gen/src/shaderchains/downsample.cpp b/lsfg-vk-gen/src/shaderchains/downsample.cpp deleted file mode 100644 index 4d54283..0000000 --- a/lsfg-vk-gen/src/shaderchains/downsample.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "shaderchains/downsample.hpp" -#include "utils/utils.hpp" - -using namespace LSFG::Shaderchains; - -Downsample::Downsample(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - Core::Image inImg_0, Core::Image inImg_1, - size_t genc) - : inImg_0(std::move(inImg_0)), - inImg_1(std::move(inImg_1)) { - this->shaderModule = shaderpool.getShader(device, "downsample.spv", - { { 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 = shaderpool.getPipeline(device, "downsample.spv"); - for (size_t i = 0; i < 2; i++) - this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, this->shaderModule); - - auto data = Globals::fgBuffer; - data.timestamp = 1.0F / static_cast(genc + 1); - this->buffer = Core::Buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - - auto extent = this->inImg_0.getExtent(); - for (size_t i = 0; i < 7; i++) - this->outImgs.at(i) = Core::Image(device, - { extent.width >> i, extent.height >> i }, - VK_FORMAT_R8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - for (size_t fc = 0; fc < 2; fc++) { - auto& inImg = (fc % 2 == 0) ? this->inImg_0 : this->inImg_1; - this->descriptorSets.at(fc).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, inImg) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) - .build(); - } -} - -void Downsample::Dispatch(const Core::CommandBuffer& buf, uint64_t fc) { - auto extent = this->inImg_0.getExtent(); - - // first pass - const uint32_t threadsX = (extent.width + 63) >> 6; - const uint32_t threadsY = (extent.height + 63) >> 6; - - auto& inImg = (fc % 2 == 0) ? this->inImg_0 : this->inImg_1; - Utils::BarrierBuilder(buf) - .addW2R(inImg) - .addR2W(this->outImgs) - .build(); - - this->pipeline.bind(buf); - this->descriptorSets.at(fc % 2).bind(buf, this->pipeline); - buf.dispatch(threadsX, threadsY, 1); -} diff --git a/lsfg-vk-gen/src/shaderchains/epsilon.cpp b/lsfg-vk-gen/src/shaderchains/epsilon.cpp deleted file mode 100644 index b9cdf43..0000000 --- a/lsfg-vk-gen/src/shaderchains/epsilon.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include "shaderchains/epsilon.hpp" -#include "utils/utils.hpp" - -using namespace LSFG::Shaderchains; - -Epsilon::Epsilon(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs1, - Core::Image inImg2, - std::optional optImg, - size_t genc) - : inImgs1(std::move(inImgs1)), - inImg2(std::move(inImg2)), - optImg(std::move(optImg)) { - this->shaderModules = {{ - shaderpool.getShader(device, "epsilon/0.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "epsilon/1.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "epsilon/2.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "epsilon/3.spv", - { { 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 = {{ - shaderpool.getPipeline(device, "epsilon/0.spv"), - shaderpool.getPipeline(device, "epsilon/1.spv"), - shaderpool.getPipeline(device, "epsilon/2.spv"), - shaderpool.getPipeline(device, "epsilon/3.spv") - }}; - for (size_t i = 0; i < 3; i++) - this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, - this->shaderModules.at(i)); - for (size_t i = 0; i < genc; i++) - this->nDescriptorSets.emplace_back(device, pool, - this->shaderModules.at(3)); - for (size_t i = 0; i < genc; i++) { - auto data = Globals::fgBuffer; - data.timestamp = static_cast(i + 1) / static_cast(genc + 1); - this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - } - - const auto extent = this->inImgs1.at(0).getExtent(); - - for (size_t i = 0; i < 4; i++) { - this->tempImgs1.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - this->tempImgs2.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - } - - this->outImg = Core::Image(device, - extent, - VK_FORMAT_R16G16B16A16_SFLOAT, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - this->descriptorSets.at(0).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - this->descriptorSets.at(1).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .build(); - this->descriptorSets.at(2).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - for (size_t i = 0; i < genc; i++) { - this->nDescriptorSets.at(i).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .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 Epsilon::Dispatch(const Core::CommandBuffer& buf, uint64_t pass) { - const auto extent = this->tempImgs1.at(0).getExtent(); - - // first pass - const uint32_t threadsX = (extent.width + 7) >> 3; - const uint32_t threadsY = (extent.height + 7) >> 3; - - Utils::BarrierBuilder(buf) - .addW2R(this->inImgs1) - .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 - Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs2) - .addR2W(this->tempImgs1) - .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->tempImgs1) - .addW2R(this->optImg) - .addW2R(this->inImg2) - .addR2W(this->outImg) - .build(); - - this->pipelines.at(3).bind(buf); - this->nDescriptorSets.at(pass).bind(buf, this->pipelines.at(3)); - buf.dispatch(threadsX, threadsY, 1); -} diff --git a/lsfg-vk-gen/src/shaderchains/extract.cpp b/lsfg-vk-gen/src/shaderchains/extract.cpp deleted file mode 100644 index 041bd72..0000000 --- a/lsfg-vk-gen/src/shaderchains/extract.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "shaderchains/extract.hpp" -#include "utils/utils.hpp" - -using namespace LSFG::Shaderchains; - -Extract::Extract(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - Core::Image inImg1, - Core::Image inImg2, - VkExtent2D outExtent, - size_t genc) - : inImg1(std::move(inImg1)), - inImg2(std::move(inImg2)) { - this->shaderModule = shaderpool.getShader(device, "extract.spv", - { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); - this->pipeline = shaderpool.getPipeline(device, "extract.spv"); - for (size_t i = 0; i < genc; i++) - this->nDescriptorSets.emplace_back(device, pool, - this->shaderModule); - for (size_t i = 0; i < genc; i++) { - auto data = Globals::fgBuffer; - data.timestamp = static_cast(i + 1) / static_cast(genc + 1); - this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - } - - this->whiteImg = Core::Image(device, - outExtent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT - | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - this->outImg = Core::Image(device, - outExtent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - for (size_t i = 0; i < genc; i++) { - this->nDescriptorSets.at(i).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .build(); - } - - // clear white image - Utils::clearImage(device, this->whiteImg, true); -} - -void Extract::Dispatch(const Core::CommandBuffer& buf, uint64_t pass) { - auto extent = this->whiteImg.getExtent(); - - // first pass - const uint32_t threadsX = (extent.width + 7) >> 3; - const uint32_t threadsY = (extent.height + 7) >> 3; - - Utils::BarrierBuilder(buf) - .addW2R(this->whiteImg) - .addW2R(this->inImg1) - .addW2R(this->inImg2) - .addR2W(this->outImg) - .build(); - - this->pipeline.bind(buf); - this->nDescriptorSets.at(pass).bind(buf, this->pipeline); - buf.dispatch(threadsX, threadsY, 1); -} diff --git a/lsfg-vk-gen/src/shaderchains/gamma.cpp b/lsfg-vk-gen/src/shaderchains/gamma.cpp deleted file mode 100644 index 4e73c60..0000000 --- a/lsfg-vk-gen/src/shaderchains/gamma.cpp +++ /dev/null @@ -1,272 +0,0 @@ -#include "shaderchains/gamma.hpp" -#include "utils/utils.hpp" - -using namespace LSFG::Shaderchains; - -Gamma::Gamma(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs1_0, - std::array inImgs1_1, - std::array inImgs1_2, - Core::Image inImg2, - std::optional optImg1, // NOLINT - std::optional optImg2, - VkExtent2D outExtent, - size_t genc) - : inImgs1_0(std::move(inImgs1_0)), - inImgs1_1(std::move(inImgs1_1)), - inImgs1_2(std::move(inImgs1_2)), - inImg2(std::move(inImg2)), - optImg2(std::move(optImg2)) { - this->shaderModules = {{ - shaderpool.getShader(device, "gamma/0.spv", - { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 10, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "gamma/1.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "gamma/2.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "gamma/3.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "gamma/4.spv", - { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "gamma/5.spv", - { { 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 = {{ - shaderpool.getPipeline(device, "gamma/0.spv"), - shaderpool.getPipeline(device, "gamma/1.spv"), - shaderpool.getPipeline(device, "gamma/2.spv"), - shaderpool.getPipeline(device, "gamma/3.spv"), - shaderpool.getPipeline(device, "gamma/4.spv"), - shaderpool.getPipeline(device, "gamma/5.spv") - }}; - for (size_t i = 1; i < 4; i++) - this->descriptorSets.at(i - 1) = Core::DescriptorSet(device, pool, - this->shaderModules.at(i)); - for (size_t i = 0; i < genc; i++) - this->n1DescriptorSets.emplace_back(device, pool, - this->shaderModules.at(4)); - for (size_t i = 0; i < genc; i++) - this->n2DescriptorSets.emplace_back(device, pool, - this->shaderModules.at(5)); - for (size_t i = 0; i < genc; i++) { - this->nSpecialDescriptorSets.emplace_back(); - for (size_t j = 0; j < 3; j++) - this->nSpecialDescriptorSets.at(i).at(j) = Core::DescriptorSet(device, pool, - this->shaderModules.at(0)); - } - for (size_t i = 0; i < genc; i++) { - auto data = Globals::fgBuffer; - data.timestamp = static_cast(i + 1) / static_cast(genc + 1); - data.firstIter = !optImg1.has_value(); - this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - } - - const auto extent = this->inImgs1_0.at(0).getExtent(); - - this->optImg1 = optImg1.value_or(Core::Image(device, extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT - | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT)); - - for (size_t i = 0; i < 4; i++) { - this->tempImgs1.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - this->tempImgs2.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - } - this->whiteImg = Core::Image(device, outExtent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT - | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - this->outImg1 = Core::Image(device, - extent, - VK_FORMAT_R16G16B16A16_SFLOAT, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - this->outImg2 = Core::Image(device, - outExtent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - for (size_t fc = 0; fc < 3; fc++) { - auto* nextImgs1 = &this->inImgs1_0; - auto* prevImgs1 = &this->inImgs1_2; - if (fc == 1) { - nextImgs1 = &this->inImgs1_1; - prevImgs1 = &this->inImgs1_0; - } else if (fc == 2) { - nextImgs1 = &this->inImgs1_2; - prevImgs1 = &this->inImgs1_1; - } - for (size_t i = 0; i < genc; i++) { - this->nSpecialDescriptorSets.at(i).at(fc).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *nextImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) - .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(); - } - } - this->descriptorSets.at(0).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .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(); - this->descriptorSets.at(1).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - this->descriptorSets.at(2).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .build(); - for (size_t i = 0; i < genc; i++) { - this->n1DescriptorSets.at(i).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1) - .build(); - this->n2DescriptorSets.at(i).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->whiteImg) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->outImg1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2) - .build(); - } - - // clear white image and optImg1 if needed - Utils::clearImage(device, this->whiteImg, true); - if (!optImg1.has_value()) - Utils::clearImage(device, this->optImg1); -} - -void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass) { - const auto extent = this->tempImgs1.at(0).getExtent(); - - // first pass - uint32_t threadsX = (extent.width + 7) >> 3; - uint32_t threadsY = (extent.height + 7) >> 3; - - auto* nextImgs1 = &this->inImgs1_0; - auto* prevImgs1 = &this->inImgs1_2; - if ((fc % 3) == 1) { - nextImgs1 = &this->inImgs1_1; - prevImgs1 = &this->inImgs1_0; - } else if ((fc % 3) == 2) { - nextImgs1 = &this->inImgs1_2; - prevImgs1 = &this->inImgs1_1; - } - Utils::BarrierBuilder(buf) - .addW2R(*prevImgs1) - .addW2R(*nextImgs1) - .addW2R(this->optImg1) - .addW2R(this->optImg2) - .addR2W(this->tempImgs1.at(0)) - .addR2W(this->tempImgs1.at(1)) - .addR2W(this->tempImgs1.at(2)) - .build(); - - this->pipelines.at(0).bind(buf); - this->nSpecialDescriptorSets.at(pass).at(fc % 3).bind(buf, this->pipelines.at(0)); - buf.dispatch(threadsX, threadsY, 1); - - // second pass - 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); - 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 - Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs2) - .addW2R(this->optImg2) - .addW2R(this->inImg2) - .addR2W(this->outImg1) - .build(); - - this->pipelines.at(4).bind(buf); - this->n1DescriptorSets.at(pass).bind(buf, this->pipelines.at(4)); - buf.dispatch(threadsX, threadsY, 1); - - // sixth pass - threadsX = (extent.width + 3) >> 2; - threadsY = (extent.height + 3) >> 2; - - Utils::BarrierBuilder(buf) - .addW2R(this->whiteImg) - .addW2R(this->outImg1) - .addR2W(this->outImg2) - .build(); - - this->pipelines.at(5).bind(buf); - this->n2DescriptorSets.at(pass).bind(buf, this->pipelines.at(5)); - buf.dispatch(threadsX, threadsY, 1); -} diff --git a/lsfg-vk-gen/src/shaderchains/magic.cpp b/lsfg-vk-gen/src/shaderchains/magic.cpp deleted file mode 100644 index e87f26b..0000000 --- a/lsfg-vk-gen/src/shaderchains/magic.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "shaderchains/magic.hpp" -#include "utils/utils.hpp" - -using namespace LSFG::Shaderchains; - -Magic::Magic(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs1_0, - std::array inImgs1_1, - std::array inImgs1_2, - Core::Image inImg2, - Core::Image inImg3, - std::optional optImg, - size_t genc) - : inImgs1_0(std::move(inImgs1_0)), - inImgs1_1(std::move(inImgs1_1)), - inImgs1_2(std::move(inImgs1_2)), - inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), - optImg(std::move(optImg)) { - this->shaderModule = shaderpool.getShader(device, "magic.spv", - { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, - { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 4+4+2+1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 3+3+2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); - this->pipeline = shaderpool.getPipeline(device, "magic.spv"); - for (size_t i = 0; i < genc; i++) { - this->nDescriptorSets.emplace_back(); - for (size_t j = 0; j < 3; j++) - this->nDescriptorSets.at(i).at(j) = Core::DescriptorSet(device, pool, this->shaderModule); - } - for (size_t i = 0; i < genc; i++) { - auto data = Globals::fgBuffer; - data.timestamp = static_cast(i + 1) / static_cast(genc + 1); - data.firstIterS = !this->optImg.has_value(); - this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - } - - auto extent = this->inImgs1_0.at(0).getExtent(); - - for (size_t i = 0; i < 2; i++) - this->outImgs1.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - for (size_t i = 0; i < 3; i++) - this->outImgs2.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - for (size_t i = 0; i < 3; i++) - this->outImgs3.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - for (size_t fc = 0; fc < 3; fc++) { - auto* nextImgs1 = &this->inImgs1_0; - auto* prevImgs1 = &this->inImgs1_2; - if (fc == 1) { - nextImgs1 = &this->inImgs1_1; - prevImgs1 = &this->inImgs1_0; - } else if (fc == 2) { - nextImgs1 = &this->inImgs1_2; - prevImgs1 = &this->inImgs1_1; - } - for (size_t i = 0; i < genc; i++) { - this->nDescriptorSets.at(i).at(fc).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *prevImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, *nextImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs3) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs1) - .build(); - } - } -} - -void Magic::Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass) { - auto extent = this->inImgs1_0.at(0).getExtent(); - - // first pass - const uint32_t threadsX = (extent.width + 7) >> 3; - const uint32_t threadsY = (extent.height + 7) >> 3; - - auto* nextImgs1 = &this->inImgs1_0; - auto* prevImgs1 = &this->inImgs1_2; - if ((fc % 3) == 1) { - nextImgs1 = &this->inImgs1_1; - prevImgs1 = &this->inImgs1_0; - } else if ((fc % 3) == 2) { - nextImgs1 = &this->inImgs1_2; - prevImgs1 = &this->inImgs1_1; - } - Utils::BarrierBuilder(buf) - .addW2R(*prevImgs1) - .addW2R(*nextImgs1) - .addW2R(this->inImg2) - .addW2R(this->inImg3) - .addW2R(this->optImg) - .addR2W(this->outImgs3) - .addR2W(this->outImgs2) - .addR2W(this->outImgs1) - .build(); - - this->pipeline.bind(buf); - this->nDescriptorSets.at(pass).at(fc % 3).bind(buf, this->pipeline); - buf.dispatch(threadsX, threadsY, 1); -} diff --git a/lsfg-vk-gen/src/shaderchains/merge.cpp b/lsfg-vk-gen/src/shaderchains/merge.cpp deleted file mode 100644 index 0d8716a..0000000 --- a/lsfg-vk-gen/src/shaderchains/merge.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "shaderchains/merge.hpp" -#include "utils/utils.hpp" - -using namespace LSFG::Shaderchains; - -Merge::Merge(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - Core::Image inImg1, - Core::Image inImg2, - Core::Image inImg3, - Core::Image inImg4, - Core::Image inImg5, - const std::vector& outFds, - size_t genc) - : inImg1(std::move(inImg1)), - inImg2(std::move(inImg2)), - inImg3(std::move(inImg3)), - inImg4(std::move(inImg4)), - inImg5(std::move(inImg5)) { - this->shaderModule = shaderpool.getShader(device, "merge.spv", - { { 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 = shaderpool.getPipeline(device, "merge.spv"); - for (size_t i = 0; i < genc; i++) { - this->nDescriptorSets.emplace_back(); - for (size_t j = 0; j < 2; j++) - this->nDescriptorSets.at(i).at(j) = Core::DescriptorSet(device, pool, this->shaderModule); - } - for (size_t i = 0; i < genc; i++) { - auto data = Globals::fgBuffer; - data.timestamp = static_cast(i + 1) / static_cast(genc + 1); - this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - } - - auto extent = this->inImg1.getExtent(); - - const VkFormat format = getenv("LSFG_HDR") == nullptr - ? VK_FORMAT_R8G8B8A8_UNORM - : VK_FORMAT_R16G16B16A16_SFLOAT; - for (size_t i = 0; i < genc; i++) - this->outImgs.emplace_back(device, - extent, format, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, - outFds.at(i)); - - for (size_t fc = 0; fc < 2; fc++) { - for (size_t i = 0; i < genc; i++) { - this->nDescriptorSets.at(i).at(fc).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i)) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg1 : this->inImg2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg2 : this->inImg1) - .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 Merge::Dispatch(const Core::CommandBuffer& buf, uint64_t fc, uint64_t pass) { - auto extent = this->inImg1.getExtent(); - - // first pass - 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)) - .build(); - - this->pipeline.bind(buf); - this->nDescriptorSets.at(pass).at(fc % 2).bind(buf, this->pipeline); - buf.dispatch(threadsX, threadsY, 1); -} diff --git a/lsfg-vk-gen/src/shaderchains/zeta.cpp b/lsfg-vk-gen/src/shaderchains/zeta.cpp deleted file mode 100644 index 7f740c3..0000000 --- a/lsfg-vk-gen/src/shaderchains/zeta.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include "shaderchains/zeta.hpp" -#include "utils/utils.hpp" - -using namespace LSFG::Shaderchains; - -Zeta::Zeta(const Core::Device& device, Pool::ShaderPool& shaderpool, - const Core::DescriptorPool& pool, - std::array inImgs1, - Core::Image inImg2, - Core::Image inImg3, - size_t genc) - : inImgs1(std::move(inImgs1)), - inImg2(std::move(inImg2)), - inImg3(std::move(inImg3)) { - this->shaderModules = {{ - shaderpool.getShader(device, "zeta/0.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "zeta/1.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "zeta/2.spv", - { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), - shaderpool.getShader(device, "zeta/3.spv", - { { 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 = {{ - shaderpool.getPipeline(device, "zeta/0.spv"), - shaderpool.getPipeline(device, "zeta/1.spv"), - shaderpool.getPipeline(device, "zeta/2.spv"), - shaderpool.getPipeline(device, "zeta/3.spv") - }}; - for (size_t i = 0; i < 3; i++) - this->descriptorSets.at(i) = Core::DescriptorSet(device, pool, - this->shaderModules.at(i)); - for (size_t i = 0; i < genc; i++) - this->nDescriptorSets.emplace_back(device, pool, - this->shaderModules.at(3)); - for (size_t i = 0; i < genc; i++) { - auto data = Globals::fgBuffer; - data.timestamp = static_cast(i + 1) / static_cast(genc + 1); - this->buffers.emplace_back(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); - } - - const auto extent = this->inImgs1.at(0).getExtent(); - - for (size_t i = 0; i < 4; i++) { - this->tempImgs1.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - this->tempImgs2.at(i) = Core::Image(device, - extent, - VK_FORMAT_R8G8B8A8_UNORM, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - } - - this->outImg = Core::Image(device, - extent, - VK_FORMAT_R16G16B16A16_SFLOAT, - VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - this->descriptorSets.at(0).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - this->descriptorSets.at(1).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) - .build(); - this->descriptorSets.at(2).update(device) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) - .build(); - for (size_t i = 0; i < genc; i++) { - this->nDescriptorSets.at(i).update(device) - .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffers.at(i) ) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampBorder) - .add(VK_DESCRIPTOR_TYPE_SAMPLER, Globals::samplerClampEdge) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) - .build(); - } -} - -void Zeta::Dispatch(const Core::CommandBuffer& buf, uint64_t pass) { - const auto extent = this->tempImgs1.at(0).getExtent(); - - // first pass - const uint32_t threadsX = (extent.width + 7) >> 3; - const uint32_t threadsY = (extent.height + 7) >> 3; - - Utils::BarrierBuilder(buf) - .addW2R(this->inImgs1) - .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 - Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs2) - .addR2W(this->tempImgs1) - .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->tempImgs1) - .addW2R(this->inImg2) - .addW2R(this->inImg3) - .addR2W(this->outImg) - .build(); - - this->pipelines.at(3).bind(buf); - this->nDescriptorSets.at(pass).bind(buf, this->pipelines.at(3)); - buf.dispatch(threadsX, threadsY, 1); -} diff --git a/lsfg-vk-v3.1/.clang-tidy-no b/lsfg-vk-v3.1/.clang-tidy-no new file mode 100644 index 0000000..5ac5468 --- /dev/null +++ b/lsfg-vk-v3.1/.clang-tidy-no @@ -0,0 +1,25 @@ +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" +- "-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" diff --git a/lsfg-vk-v3.1/.gitattributes b/lsfg-vk-v3.1/.gitattributes new file mode 100644 index 0000000..8d476d4 --- /dev/null +++ b/lsfg-vk-v3.1/.gitattributes @@ -0,0 +1,3 @@ +*.cpp diff=cpp eol=lf +*.hpp diff=cpp eol=lf +*.md diff=markdown eol=lf diff --git a/lsfg-vk-v3.1/.gitignore b/lsfg-vk-v3.1/.gitignore new file mode 100644 index 0000000..43ab8ae --- /dev/null +++ b/lsfg-vk-v3.1/.gitignore @@ -0,0 +1,9 @@ +# cmake files +/build + +# ide/lsp files +/.zed +/.vscode +/.clangd +/.cache +/.ccls diff --git a/lsfg-vk-v3.1/CMakeLists.txt b/lsfg-vk-v3.1/CMakeLists.txt new file mode 100644 index 0000000..e40d1f6 --- /dev/null +++ b/lsfg-vk-v3.1/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.29) + +# project +project(lsfg-vk-v3.1 + DESCRIPTION "Lossless Scaling Frame Generation v3.1" + LANGUAGES CXX) + +file(GLOB SOURCES + "src/core/*.cpp" + "src/pool/*.cpp" + "src/shaders/*.cpp" + "src/utils/*.cpp" + "src/*.cpp" +) + +add_library(lsfg-vk-v3.1 STATIC ${SOURCES}) + +# target +set_target_properties(lsfg-vk-v3.1 PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON) +target_include_directories(lsfg-vk-v3.1 + PRIVATE include + PUBLIC public) +target_link_libraries(lsfg-vk-v3.1 + PRIVATE vulkan) +target_compile_options(lsfg-vk-v3.1 PRIVATE + -fPIC) + +# diagnostics +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set_target_properties(lsfg-vk-v3.1 PROPERTIES + EXPORT_COMPILE_COMMANDS ON) +endif() + +if(LSFGVK_EXCESS_DEBUG STREQUAL "ON") + target_compile_options(lsfg-vk-v3.1 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-v3.1 PROPERTIES + CMAKE_CXX_CLANG_TIDY clang-tidy) +endif() diff --git a/lsfg-vk-v3.1/LICENSE.md b/lsfg-vk-v3.1/LICENSE.md new file mode 100644 index 0000000..b5c8a3e --- /dev/null +++ b/lsfg-vk-v3.1/LICENSE.md @@ -0,0 +1,21 @@ +## MIT License + +Copyright (c) 2025 lsfg-vk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lsfg-vk-v3.1/README.md b/lsfg-vk-v3.1/README.md new file mode 100644 index 0000000..016bfa4 --- /dev/null +++ b/lsfg-vk-v3.1/README.md @@ -0,0 +1,14 @@ +## lsfg-vk-v3.1 +Version 3.1 of Lossless Scaling Frame Generation + +This is a subproject of lsfg-vk and contains the external 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-v3.1 is done via `lsfg.hpp` header. The internal Vulkan instance is created using `LSFG::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::finalize()` after which `LSFG::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::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::deleteContext()`. + +Presenting the context can be done via `LSFG::presentContext()`. Before calling the function a second time, make sure the outgoing semaphores have been signaled. diff --git a/lsfg-vk-v3.1/include/context.hpp b/lsfg-vk-v3.1/include/context.hpp new file mode 100644 index 0000000..df1dae1 --- /dev/null +++ b/lsfg-vk-v3.1/include/context.hpp @@ -0,0 +1,75 @@ +#ifndef CONTEXT_HPP +#define CONTEXT_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" + +namespace LSFG { + + class Context { + public: + /// + /// Create a context + /// + /// @param vk The Vulkan instance to use. + /// @param in0 File descriptor for the first input image. + /// @param in1 File descriptor for the second input image. + /// @param outN File descriptors for the output images. + /// @param extent The size of the images. + /// @param format The format of the images. + /// + /// @throws LSFG::vulkan_error if the context fails to initialize. + /// + Context(Vulkan& vk, + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format); + + /// + /// Present on the context. + /// + /// @param inSem Semaphore to wait on before starting the generation. + /// @param outSem Semaphores to signal after each generation is done. + /// + /// @throws LSFG::vulkan_error if the context fails to present. + /// + void present(Vulkan& vk, + int inSem, const std::vector& outSem); + + // Trivially copyable, moveable and destructible + Context(const Context&) = default; + Context& operator=(const Context&) = default; + Context(Context&&) = default; + Context& operator=(Context&&) = default; + ~Context() = default; + private: + Core::Image inImg_0, inImg_1; // inImg_0 is next when fc % 2 == 0 + uint64_t frameIdx{0}; + + struct RenderData { + Core::Semaphore inSemaphore; // signaled when input is ready + std::vector internalSemaphores; // signaled when first step is done + std::vector outSemaphores; // signaled when each pass is done + std::vector completionFences; // fence for completion of each pass + + Core::CommandBuffer cmdBuffer1; + std::vector cmdBuffers2; // command buffers for second step + + bool shouldWait{false}; + }; + std::array data; + + Shaders::Mipmaps mipmaps; + std::array alpha; + Shaders::Beta beta; + std::array gamma; + std::array delta; + Shaders::Generate generate; + }; + +} + +#endif // CONTEXT_HPP diff --git a/lsfg-vk-gen/include/core/buffer.hpp b/lsfg-vk-v3.1/include/core/buffer.hpp similarity index 100% rename from lsfg-vk-gen/include/core/buffer.hpp rename to lsfg-vk-v3.1/include/core/buffer.hpp diff --git a/lsfg-vk-gen/include/core/commandbuffer.hpp b/lsfg-vk-v3.1/include/core/commandbuffer.hpp similarity index 100% rename from lsfg-vk-gen/include/core/commandbuffer.hpp rename to lsfg-vk-v3.1/include/core/commandbuffer.hpp diff --git a/lsfg-vk-gen/include/core/commandpool.hpp b/lsfg-vk-v3.1/include/core/commandpool.hpp similarity index 100% rename from lsfg-vk-gen/include/core/commandpool.hpp rename to lsfg-vk-v3.1/include/core/commandpool.hpp diff --git a/lsfg-vk-gen/include/core/descriptorpool.hpp b/lsfg-vk-v3.1/include/core/descriptorpool.hpp similarity index 100% rename from lsfg-vk-gen/include/core/descriptorpool.hpp rename to lsfg-vk-v3.1/include/core/descriptorpool.hpp diff --git a/lsfg-vk-gen/include/core/descriptorset.hpp b/lsfg-vk-v3.1/include/core/descriptorset.hpp similarity index 100% rename from lsfg-vk-gen/include/core/descriptorset.hpp rename to lsfg-vk-v3.1/include/core/descriptorset.hpp diff --git a/lsfg-vk-gen/include/core/device.hpp b/lsfg-vk-v3.1/include/core/device.hpp similarity index 92% rename from lsfg-vk-gen/include/core/device.hpp rename to lsfg-vk-v3.1/include/core/device.hpp index 2916e86..b0bdcf7 100644 --- a/lsfg-vk-gen/include/core/device.hpp +++ b/lsfg-vk-v3.1/include/core/device.hpp @@ -21,10 +21,11 @@ namespace LSFG::Core { /// 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); + Device(const Instance& instance, uint64_t deviceUUID); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->device; } diff --git a/lsfg-vk-gen/include/core/fence.hpp b/lsfg-vk-v3.1/include/core/fence.hpp similarity index 100% rename from lsfg-vk-gen/include/core/fence.hpp rename to lsfg-vk-v3.1/include/core/fence.hpp diff --git a/lsfg-vk-gen/include/core/image.hpp b/lsfg-vk-v3.1/include/core/image.hpp similarity index 91% rename from lsfg-vk-gen/include/core/image.hpp rename to lsfg-vk-v3.1/include/core/image.hpp index 540e043..2d93854 100644 --- a/lsfg-vk-gen/include/core/image.hpp +++ b/lsfg-vk-v3.1/include/core/image.hpp @@ -29,8 +29,10 @@ namespace LSFG::Core { /// /// @throws LSFG::vulkan_error if object creation fails. /// - Image(const Core::Device& device, VkExtent2D extent, VkFormat format, - VkImageUsageFlags usage, VkImageAspectFlags aspectFlags); + 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. diff --git a/lsfg-vk-gen/include/core/instance.hpp b/lsfg-vk-v3.1/include/core/instance.hpp similarity index 100% rename from lsfg-vk-gen/include/core/instance.hpp rename to lsfg-vk-v3.1/include/core/instance.hpp diff --git a/lsfg-vk-gen/include/core/pipeline.hpp b/lsfg-vk-v3.1/include/core/pipeline.hpp similarity index 100% rename from lsfg-vk-gen/include/core/pipeline.hpp rename to lsfg-vk-v3.1/include/core/pipeline.hpp diff --git a/lsfg-vk-gen/include/core/sampler.hpp b/lsfg-vk-v3.1/include/core/sampler.hpp similarity index 80% rename from lsfg-vk-gen/include/core/sampler.hpp rename to lsfg-vk-v3.1/include/core/sampler.hpp index 7890606..dd6ab15 100644 --- a/lsfg-vk-gen/include/core/sampler.hpp +++ b/lsfg-vk-v3.1/include/core/sampler.hpp @@ -23,10 +23,15 @@ namespace LSFG::Core { /// /// @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); + Sampler(const Core::Device& device, + VkSamplerAddressMode mode, + VkCompareOp compare, + bool isWhite); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->sampler; } diff --git a/lsfg-vk-gen/include/core/semaphore.hpp b/lsfg-vk-v3.1/include/core/semaphore.hpp similarity index 100% rename from lsfg-vk-gen/include/core/semaphore.hpp rename to lsfg-vk-v3.1/include/core/semaphore.hpp diff --git a/lsfg-vk-gen/include/core/shadermodule.hpp b/lsfg-vk-v3.1/include/core/shadermodule.hpp similarity index 100% rename from lsfg-vk-gen/include/core/shadermodule.hpp rename to lsfg-vk-v3.1/include/core/shadermodule.hpp diff --git a/lsfg-vk-v3.1/include/pool/resourcepool.hpp b/lsfg-vk-v3.1/include/pool/resourcepool.hpp new file mode 100644 index 0000000..baa165f --- /dev/null +++ b/lsfg-vk-v3.1/include/pool/resourcepool.hpp @@ -0,0 +1,70 @@ +#ifndef RESOURCEPOOL_HPP +#define RESOURCEPOOL_HPP + +#include "core/device.hpp" +#include "core/buffer.hpp" +#include "core/sampler.hpp" +#include "vulkan/vulkan_core.h" + +#include + +namespace LSFG::Pool { + + /// + /// Resource pool for each Vulkan device. + /// + class ResourcePool { + public: + ResourcePool() noexcept = default; + + /// + /// Create the resource pool. + /// + /// @param isHdr HDR support stored in buffers. + /// @param flowScale Scale factor stored in buffers. + /// + /// @throws std::runtime_error if the resource pool cannot be created. + /// + ResourcePool(bool isHdr, float flowScale) + : isHdr(isHdr), flowScale(flowScale) {} + + /// + /// Retrieve a buffer with given parameters or create it. + /// + /// @param timestamp Timestamp stored in buffer + /// @param firstIter First iteration stored in buffer + /// @param firstIterS First special iteration stored in buffer + /// @return Created or cached buffer + /// + /// @throws LSFG::vulkan_error if the buffer cannot be created. + /// + Core::Buffer getBuffer( + const Core::Device& device, + float timestamp = 0.0F, bool firstIter = false, bool firstIterS = false); + + /// + /// Retrieve a sampler by type or create it. + /// + /// @param type Type of the sampler + /// @param compare Compare operation for the sampler + /// @param isWhite Whether the sampler is white + /// @return Created or cached sampler + /// + /// @throws LSFG::vulkan_error if the sampler cannot be created. + /// + Core::Sampler getSampler( + const Core::Device& device, + VkSamplerAddressMode type = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + VkCompareOp compare = VK_COMPARE_OP_NEVER, + bool isWhite = false); + + private: + std::unordered_map buffers; + std::unordered_map samplers; + bool isHdr{}; + float flowScale{}; + }; + +} + +#endif // RESOURCEPOOL_HPP diff --git a/lsfg-vk-gen/include/pool/shaderpool.hpp b/lsfg-vk-v3.1/include/pool/shaderpool.hpp similarity index 83% rename from lsfg-vk-gen/include/pool/shaderpool.hpp rename to lsfg-vk-v3.1/include/pool/shaderpool.hpp index 8477887..db42533 100644 --- a/lsfg-vk-gen/include/pool/shaderpool.hpp +++ b/lsfg-vk-v3.1/include/pool/shaderpool.hpp @@ -4,10 +4,11 @@ #include "core/device.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" -#include "pool/extract.hpp" +#include #include #include +#include namespace LSFG::Pool { @@ -21,19 +22,19 @@ namespace LSFG::Pool { /// /// Create the shader pool. /// - /// @param path Path to the shader dll + /// @param source Function to retrieve shader source code by name. /// /// @throws std::runtime_error if the shader pool cannot be created. /// - ShaderPool(const std::string& path) : extractor(path) {} + ShaderPool(const std::function(const std::string&)>& source) + : source(source) {} /// /// Retrieve a shader module by name or create it. /// - /// @param device Vulkan device /// @param name Name of the shader module /// @param types Descriptor types for the shader module - /// @return Shader module or empty + /// @return Shader module /// /// @throws LSFG::vulkan_error if the shader module cannot be created. /// @@ -44,7 +45,6 @@ namespace LSFG::Pool { /// /// Retrieve a pipeline shader module by name or create it. /// - /// @param device Vulkan device /// @param name Name of the shader module /// @return Pipeline shader module or empty /// @@ -53,7 +53,7 @@ namespace LSFG::Pool { Core::Pipeline getPipeline( const Core::Device& device, const std::string& name); private: - Extractor extractor; + std::function(const std::string&)> source; std::unordered_map shaders; std::unordered_map pipelines; }; diff --git a/lsfg-vk-v3.1/include/shaders/alpha.hpp b/lsfg-vk-v3.1/include/shaders/alpha.hpp new file mode 100644 index 0000000..8f254e5 --- /dev/null +++ b/lsfg-vk-v3.1/include/shaders/alpha.hpp @@ -0,0 +1,61 @@ +#ifndef ALPHA_HPP +#define ALPHA_HPP + +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "utils/utils.hpp" + +#include + +namespace LSFG::Shaders { + + /// + /// Alpha shader. + /// + class Alpha { + public: + Alpha() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg One mipmap level + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Alpha(Vulkan& vk, Core::Image inImg); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Alpha(const Alpha&) noexcept = default; + Alpha& operator=(const Alpha&) noexcept = default; + Alpha(Alpha&&) noexcept = default; + Alpha& operator=(Alpha&&) noexcept = default; + ~Alpha() = default; + private: + std::array shaderModules; + std::array pipelines; + Core::Sampler sampler; + std::array descriptorSets; + std::array lastDescriptorSet; + + Core::Image inImg; + std::array tempImgs1; + std::array tempImgs2; + std::array tempImgs3; + std::array, 3> outImgs; + }; + +} + +#endif // ALPHA_HPP diff --git a/lsfg-vk-v3.1/include/shaders/beta.hpp b/lsfg-vk-v3.1/include/shaders/beta.hpp new file mode 100644 index 0000000..dccb04f --- /dev/null +++ b/lsfg-vk-v3.1/include/shaders/beta.hpp @@ -0,0 +1,61 @@ +#ifndef BETA_HPP +#define BETA_HPP + +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "utils/utils.hpp" + +#include + +namespace LSFG::Shaders { + + /// + /// Beta shader. + /// + class Beta { + public: + Beta() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs Three sets of four RGBA images, corresponding to a frame count % 3. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Beta(Vulkan& vk, std::array, 3> inImgs); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Beta(const Beta&) noexcept = default; + Beta& operator=(const Beta&) noexcept = default; + Beta(Beta&&) noexcept = default; + Beta& operator=(Beta&&) noexcept = default; + ~Beta() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + + std::array, 3> inImgs; + std::array tempImgs1; + std::array tempImgs2; + std::array outImgs; + }; + +} + +#endif // BETA_HPP diff --git a/lsfg-vk-v3.1/include/shaders/delta.hpp b/lsfg-vk-v3.1/include/shaders/delta.hpp new file mode 100644 index 0000000..93fd127 --- /dev/null +++ b/lsfg-vk-v3.1/include/shaders/delta.hpp @@ -0,0 +1,78 @@ +#ifndef DELTA_HPP +#define DELTA_HPP + +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "utils/utils.hpp" + +#include +#include + +namespace LSFG::Shaders { + + /// + /// Delta shader. + /// + class Delta { + public: + Delta() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs1 Three sets of four RGBA images, corresponding to a frame count % 3. + /// @param inImg2 Second Input image + /// @param optImg1 Optional image for non-first passes. + /// @param optImg2 Second optional image for non-first passes. + /// @param optImg3 Third optional image for non-first passes. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Delta(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg1, + std::optional optImg2, + std::optional optImg3); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Get the first output image + [[nodiscard]] const auto& getOutImage1() const { return this->outImg1; } + /// Get the second output image + [[nodiscard]] const auto& getOutImage2() const { return this->outImg2; } + + /// Trivially copyable, moveable and destructible + Delta(const Delta&) noexcept = default; + Delta& operator=(const Delta&) noexcept = default; + Delta(Delta&&) noexcept = default; + Delta& operator=(Delta&&) noexcept = default; + ~Delta() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + struct DeltaPass { + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + std::array sixthDescriptorSet; + }; + std::vector passes; + + std::array, 3> inImgs1; + Core::Image inImg2; + std::optional optImg1, optImg2, optImg3; + std::array tempImgs1; + std::array tempImgs2; + Core::Image outImg1, outImg2; + }; + +} + +#endif // DELTA_HPP diff --git a/lsfg-vk-v3.1/include/shaders/gamma.hpp b/lsfg-vk-v3.1/include/shaders/gamma.hpp new file mode 100644 index 0000000..a4b70f8 --- /dev/null +++ b/lsfg-vk-v3.1/include/shaders/gamma.hpp @@ -0,0 +1,70 @@ +#ifndef GAMMA_HPP +#define GAMMA_HPP + +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "utils/utils.hpp" + +#include +#include + +namespace LSFG::Shaders { + + /// + /// Gamma shader. + /// + class Gamma { + public: + Gamma() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs1 Three sets of four RGBA images, corresponding to a frame count % 3. + /// @param inImg2 Second Input image + /// @param optImg Optional image for non-first passes. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Gamma(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, std::optional optImg); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Get the output image + [[nodiscard]] const auto& getOutImage() const { return this->outImg; } + + /// Trivially copyable, moveable and destructible + Gamma(const Gamma&) noexcept = default; + Gamma& operator=(const Gamma&) noexcept = default; + Gamma(Gamma&&) noexcept = default; + Gamma& operator=(Gamma&&) noexcept = default; + ~Gamma() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + struct GammaPass { + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + }; + std::vector passes; + + std::array, 3> inImgs1; + Core::Image inImg2; + std::optional optImg; + std::array tempImgs1; + std::array tempImgs2; + Core::Image outImg; + }; + +} + +#endif // GAMMA_HPP diff --git a/lsfg-vk-v3.1/include/shaders/generate.hpp b/lsfg-vk-v3.1/include/shaders/generate.hpp new file mode 100644 index 0000000..6ad6538 --- /dev/null +++ b/lsfg-vk-v3.1/include/shaders/generate.hpp @@ -0,0 +1,67 @@ +#ifndef GENERATE_HPP +#define GENERATE_HPP + +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "utils/utils.hpp" + +#include + +namespace LSFG::Shaders { + + /// + /// Generate shader. + /// + class Generate { + public: + Generate() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg1 Input image 1. + /// @param inImg2 Input image 2. + /// @param inImg3 Input image 3. + /// @param inImg4 Input image 4. + /// @param inImg5 Input image 5. + /// @param fds File descriptors for the output images. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Generate(Vulkan& vk, + Core::Image inImg1, Core::Image inImg2, + Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, + const std::vector& fds); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Trivially copyable, moveable and destructible + Generate(const Generate&) noexcept = default; + Generate& operator=(const Generate&) noexcept = default; + Generate(Generate&&) noexcept = default; + Generate& operator=(Generate&&) noexcept = default; + ~Generate() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + std::array samplers; + struct GeneratePass { + Core::Buffer buffer; + std::array descriptorSet; + }; + std::vector passes; + + Core::Image inImg1, inImg2; + Core::Image inImg3, inImg4, inImg5; + std::vector outImgs; + }; + +} + +#endif // GENERATE_HPP diff --git a/lsfg-vk-v3.1/include/shaders/mipmaps.hpp b/lsfg-vk-v3.1/include/shaders/mipmaps.hpp new file mode 100644 index 0000000..8d1f75e --- /dev/null +++ b/lsfg-vk-v3.1/include/shaders/mipmaps.hpp @@ -0,0 +1,60 @@ +#ifndef MIPMAPS_HPP +#define MIPMAPS_HPP + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/shadermodule.hpp" +#include "utils/utils.hpp" + +#include + +namespace LSFG::Shaders { + + /// + /// Mipmaps shader. + /// + class Mipmaps { + public: + Mipmaps() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg_0 The next frame (when fc % 2 == 0) + /// @param inImg_1 The next frame (when fc % 2 == 1) + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Mipmaps(Vulkan& vk, Core::Image inImg_0, Core::Image inImg_1); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images. + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Mipmaps(const Mipmaps&) noexcept = default; + Mipmaps& operator=(const Mipmaps&) noexcept = default; + Mipmaps(Mipmaps&&) noexcept = default; + Mipmaps& operator=(Mipmaps&&) noexcept = default; + ~Mipmaps() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + Core::Buffer buffer; + Core::Sampler sampler; + std::array descriptorSets; + + Core::Image inImg_0, inImg_1; + std::array outImgs; + }; + +} + +#endif // MIPMAPS_HPP diff --git a/lsfg-vk-v3.1/include/utils/trans.hpp b/lsfg-vk-v3.1/include/utils/trans.hpp new file mode 100644 index 0000000..5541892 --- /dev/null +++ b/lsfg-vk-v3.1/include/utils/trans.hpp @@ -0,0 +1,19 @@ +#ifndef TRANS_HPP +#define TRANS_HPP + +#include +#include + +namespace LSFG::Utils::Trans { + + /// + /// Translate shader bytecode to SPIR-V. + /// + /// @param bytecode The shader bytecode to translate. + /// @return A vector containing the translated SPIR-V bytecode. + /// + [[nodiscard]] std::vector translateShader(std::vector bytecode); + +} + +#endif // TRANS_HPP diff --git a/lsfg-vk-gen/include/utils/utils.hpp b/lsfg-vk-v3.1/include/utils/utils.hpp similarity index 75% rename from lsfg-vk-gen/include/utils/utils.hpp rename to lsfg-vk-v3.1/include/utils/utils.hpp index 15c6ce1..ee29f15 100644 --- a/lsfg-vk-gen/include/utils/utils.hpp +++ b/lsfg-vk-v3.1/include/utils/utils.hpp @@ -2,9 +2,11 @@ #define UTILS_HPP #include "core/commandbuffer.hpp" +#include "core/descriptorpool.hpp" #include "core/image.hpp" -#include "core/sampler.hpp" #include "core/device.hpp" +#include "pool/resourcepool.hpp" +#include "pool/shaderpool.hpp" #include #include @@ -84,37 +86,19 @@ namespace LSFG::Utils { } -namespace LSFG::Globals { +namespace LSFG { + struct Vulkan { + Core::Device device; + Core::CommandPool commandPool; + Core::DescriptorPool descriptorPool; - /// Global sampler with address mode set to clamp to border. - extern Core::Sampler samplerClampBorder; - /// Global sampler with address mode set to clamp to edge. - extern Core::Sampler samplerClampEdge; + uint64_t generationCount; + float flowScale; + bool isHdr; - /// Commonly used constant buffer structure for shaders. - struct FgBuffer { - std::array inputOffset; - uint32_t firstIter; - uint32_t firstIterS; - uint32_t advancedColorKind; - uint32_t hdrSupport; - float resolutionInvScale; - float timestamp; - float uiThreshold; - std::array pad; + Pool::ShaderPool shaders; + Pool::ResourcePool resources; }; - - /// Default instance of the FgBuffer. - extern FgBuffer fgBuffer; - - static_assert(sizeof(FgBuffer) == 48, "FgBuffer must be 48 bytes in size."); - - /// Initialize global resources. - void initializeGlobals(const Core::Device& device); - - /// Uninitialize global resources. - void uninitializeGlobals() noexcept; - } #endif // UTILS_HPP diff --git a/lsfg-vk-gen/public/lsfg.hpp b/lsfg-vk-v3.1/public/lsfg.hpp similarity index 73% rename from lsfg-vk-gen/public/lsfg.hpp rename to lsfg-vk-v3.1/public/lsfg.hpp index 2c3db5e..d55e185 100644 --- a/lsfg-vk-gen/public/lsfg.hpp +++ b/lsfg-vk-v3.1/public/lsfg.hpp @@ -1,34 +1,45 @@ -#ifndef PUBLIC_LSFG_HPP -#define PUBLIC_LSFG_HPP +#ifndef LSFG_3_1_HPP +#define LSFG_3_1_HPP -#include - -#include #include +#include +#include +#include + namespace LSFG { /// /// 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(); + void initialize(uint64_t deviceUUID, + bool isHdr, float flowScale, uint64_t generationCount, + const std::function(const std::string&)>& loader + ); /// /// Create a new LSFG context on a swapchain. /// - /// @param width Width of the input images. - /// @param height Height of the input images. /// @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(uint32_t width, uint32_t height, int in0, int in1, - const std::vector& outN); + int32_t createContext( + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format); /// /// Present a context. @@ -79,4 +90,4 @@ namespace LSFG { } -#endif // PUBLIC_LSFG_HPP +#endif // LSFG_3_1_HPP diff --git a/lsfg-vk-v3.1/src/context.cpp b/lsfg-vk-v3.1/src/context.cpp new file mode 100644 index 0000000..eb803cb --- /dev/null +++ b/lsfg-vk-v3.1/src/context.cpp @@ -0,0 +1,110 @@ +#include "context.hpp" +#include "lsfg.hpp" +#include "utils/utils.hpp" + +#include + +#include +#include +#include +#include +#include + +using namespace LSFG; + +Context::Context(Vulkan& vk, + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format) { + // import input images + this->inImg_0 = Core::Image(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, in0); + this->inImg_1 = Core::Image(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, in1); + + // prepare render data + for (size_t i = 0; i < 8; i++) { + auto& data = this->data.at(i); + data.internalSemaphores.resize(outN.size()); + data.outSemaphores.resize(outN.size()); + data.completionFences.resize(outN.size()); + data.cmdBuffers2.resize(outN.size()); + } + + // create shader chains + this->mipmaps = Shaders::Mipmaps(vk, this->inImg_0, this->inImg_1); + for (size_t i = 0; i < 7; i++) + this->alpha.at(i) = Shaders::Alpha(vk, this->mipmaps.getOutImages().at(i)); + this->beta = Shaders::Beta(vk, this->alpha.at(0).getOutImages()); + for (size_t i = 0; i < 7; i++) { + this->gamma.at(i) = Shaders::Gamma(vk, + this->alpha.at(6 - i).getOutImages(), + this->beta.getOutImages().at(std::min(6 - i, 5)), + (i == 0) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage())); + if (i < 4) continue; + + this->delta.at(i - 4) = Shaders::Delta(vk, + this->alpha.at(6 - i).getOutImages(), + this->beta.getOutImages().at(6 - i), + (i == 4) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()), + (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage1()), + (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage2())); + } + this->generate = Shaders::Generate(vk, + this->inImg_0, this->inImg_1, + this->gamma.at(6).getOutImage(), + this->delta.at(2).getOutImage1(), + this->delta.at(2).getOutImage2(), + outN); +} + +void Context::present(Vulkan& vk, + int inSem, const std::vector& outSem) { + auto& data = this->data.at(this->frameIdx % 8); + + // 3. wait for completion of previous frame in this slot + if (data.shouldWait) + for (auto& fence : data.completionFences) + if (!fence.wait(vk.device, UINT64_MAX)) + throw LSFG::vulkan_error(VK_TIMEOUT, "Fence wait timed out"); + + // 1. create mipmaps and process input image + data.inSemaphore = Core::Semaphore(vk.device, inSem); + for (size_t i = 0; i < outSem.size(); i++) + data.internalSemaphores.at(i) = Core::Semaphore(vk.device, outSem.at(i)); + + 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(); + data.cmdBuffer1.submit(vk.device.getComputeQueue(), std::nullopt, + { data.inSemaphore }, std::nullopt, + data.internalSemaphores, std::nullopt); + + // 2. generate intermediary frames + for (size_t i = 0; i < 7; i++) { + data.outSemaphores.at(i) = Core::Semaphore(vk.device, outSem.at(i)); + data.completionFences.at(i) = Core::Fence(vk.device); + + data.cmdBuffers2.at(i) = Core::CommandBuffer(vk.device, vk.commandPool); + data.cmdBuffers2.at(i).begin(); + + this->gamma.at(i).Dispatch(data.cmdBuffers2.at(i), this->frameIdx, i); + if (i >= 4) + this->delta.at(i - 4).Dispatch(data.cmdBuffers2.at(i), this->frameIdx, i); + this->generate.Dispatch(data.cmdBuffers2.at(i), this->frameIdx, i); + + data.cmdBuffers2.at(i).end(); + data.cmdBuffers2.at(i).submit(vk.device.getComputeQueue(), std::nullopt, + { data.internalSemaphores.at(i) }, std::nullopt, + data.outSemaphores, std::nullopt); + } + + this->frameIdx++; +} diff --git a/lsfg-vk-gen/src/core/buffer.cpp b/lsfg-vk-v3.1/src/core/buffer.cpp similarity index 100% rename from lsfg-vk-gen/src/core/buffer.cpp rename to lsfg-vk-v3.1/src/core/buffer.cpp diff --git a/lsfg-vk-gen/src/core/commandbuffer.cpp b/lsfg-vk-v3.1/src/core/commandbuffer.cpp similarity index 100% rename from lsfg-vk-gen/src/core/commandbuffer.cpp rename to lsfg-vk-v3.1/src/core/commandbuffer.cpp diff --git a/lsfg-vk-gen/src/core/commandpool.cpp b/lsfg-vk-v3.1/src/core/commandpool.cpp similarity index 100% rename from lsfg-vk-gen/src/core/commandpool.cpp rename to lsfg-vk-v3.1/src/core/commandpool.cpp diff --git a/lsfg-vk-gen/src/core/descriptorpool.cpp b/lsfg-vk-v3.1/src/core/descriptorpool.cpp similarity index 100% rename from lsfg-vk-gen/src/core/descriptorpool.cpp rename to lsfg-vk-v3.1/src/core/descriptorpool.cpp diff --git a/lsfg-vk-gen/src/core/descriptorset.cpp b/lsfg-vk-v3.1/src/core/descriptorset.cpp similarity index 100% rename from lsfg-vk-gen/src/core/descriptorset.cpp rename to lsfg-vk-v3.1/src/core/descriptorset.cpp diff --git a/lsfg-vk-gen/src/core/device.cpp b/lsfg-vk-v3.1/src/core/device.cpp similarity index 89% rename from lsfg-vk-gen/src/core/device.cpp rename to lsfg-vk-v3.1/src/core/device.cpp index 098ebbf..80594f5 100644 --- a/lsfg-vk-gen/src/core/device.cpp +++ b/lsfg-vk-v3.1/src/core/device.cpp @@ -12,7 +12,7 @@ const std::vector requiredExtensions = { "VK_EXT_robustness2", }; -Device::Device(const Instance& instance) { +Device::Device(const Instance& instance, uint64_t deviceUUID) { // get all physical devices uint32_t deviceCount{}; auto res = vkEnumeratePhysicalDevices(instance.handle(), &deviceCount, nullptr); @@ -24,20 +24,14 @@ Device::Device(const Instance& instance) { if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Failed to get physical devices"); - // get uuid env vars - const char* deviceUUIDEnv = std::getenv("LSFG_DEVICE_UUID"); - if (!deviceUUIDEnv) - throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, - "LSFG_DEVICE_UUID environment variable not set"); - const uint64_t deviceUUID = std::stoull(deviceUUIDEnv); - - // find first discrete GPU + // get device by uuid std::optional physicalDevice; for (const auto& device : devices) { VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(device, &properties); - const uint64_t uuid = static_cast(properties.vendorID) << 32 | properties.deviceID; + const uint64_t uuid = + static_cast(properties.vendorID) << 32 | properties.deviceID; if (deviceUUID == uuid) { physicalDevice = device; break; diff --git a/lsfg-vk-gen/src/core/fence.cpp b/lsfg-vk-v3.1/src/core/fence.cpp similarity index 100% rename from lsfg-vk-gen/src/core/fence.cpp rename to lsfg-vk-v3.1/src/core/fence.cpp diff --git a/lsfg-vk-gen/src/core/image.cpp b/lsfg-vk-v3.1/src/core/image.cpp similarity index 100% rename from lsfg-vk-gen/src/core/image.cpp rename to lsfg-vk-v3.1/src/core/image.cpp diff --git a/lsfg-vk-gen/src/core/instance.cpp b/lsfg-vk-v3.1/src/core/instance.cpp similarity index 100% rename from lsfg-vk-gen/src/core/instance.cpp rename to lsfg-vk-v3.1/src/core/instance.cpp diff --git a/lsfg-vk-gen/src/core/pipeline.cpp b/lsfg-vk-v3.1/src/core/pipeline.cpp similarity index 100% rename from lsfg-vk-gen/src/core/pipeline.cpp rename to lsfg-vk-v3.1/src/core/pipeline.cpp diff --git a/lsfg-vk-gen/src/core/sampler.cpp b/lsfg-vk-v3.1/src/core/sampler.cpp similarity index 71% rename from lsfg-vk-gen/src/core/sampler.cpp rename to lsfg-vk-v3.1/src/core/sampler.cpp index 6965145..961d832 100644 --- a/lsfg-vk-gen/src/core/sampler.cpp +++ b/lsfg-vk-v3.1/src/core/sampler.cpp @@ -1,9 +1,13 @@ #include "core/sampler.hpp" #include "lsfg.hpp" +#include using namespace LSFG::Core; -Sampler::Sampler(const Core::Device& device, VkSamplerAddressMode mode) { +Sampler::Sampler(const Core::Device& device, + VkSamplerAddressMode mode, + VkCompareOp compare, + bool isWhite) { // create sampler const VkSamplerCreateInfo desc{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, @@ -13,8 +17,11 @@ Sampler::Sampler(const Core::Device& device, VkSamplerAddressMode mode) { .addressModeU = mode, .addressModeV = mode, .addressModeW = mode, - .compareOp = mode == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE ? VK_COMPARE_OP_ALWAYS : VK_COMPARE_OP_NEVER, - .maxLod = 15.99609F + .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); diff --git a/lsfg-vk-gen/src/core/semaphore.cpp b/lsfg-vk-v3.1/src/core/semaphore.cpp similarity index 100% rename from lsfg-vk-gen/src/core/semaphore.cpp rename to lsfg-vk-v3.1/src/core/semaphore.cpp diff --git a/lsfg-vk-gen/src/core/shadermodule.cpp b/lsfg-vk-v3.1/src/core/shadermodule.cpp similarity index 100% rename from lsfg-vk-gen/src/core/shadermodule.cpp rename to lsfg-vk-v3.1/src/core/shadermodule.cpp diff --git a/lsfg-vk-v3.1/src/lsfg.cpp b/lsfg-vk-v3.1/src/lsfg.cpp new file mode 100644 index 0000000..4a80619 --- /dev/null +++ b/lsfg-vk-v3.1/src/lsfg.cpp @@ -0,0 +1,100 @@ +#include "lsfg.hpp" +#include "context.hpp" +#include "core/commandpool.hpp" +#include "core/descriptorpool.hpp" +#include "core/instance.hpp" +#include "pool/shaderpool.hpp" +#include "utils/utils.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace LSFG; + +namespace { + std::optional instance; + std::optional device; + std::unordered_map contexts; +} + +void LSFG::initialize(uint64_t deviceUUID, + bool isHdr, float flowScale, uint64_t generationCount, + const std::function(const std::string&)>& loader) { + if (instance.has_value() || device.has_value()) + return; + + instance.emplace(); + device.emplace(Vulkan { + .device{*instance, deviceUUID}, + .generationCount = generationCount, + .flowScale = flowScale, + .isHdr = isHdr + }); + + device->commandPool = Core::CommandPool(device->device); + device->descriptorPool = Core::DescriptorPool(device->device); + + device->resources = Pool::ResourcePool(device->isHdr, device->flowScale); + device->shaders = Pool::ShaderPool(loader); + + std::srand(static_cast(std::time(nullptr))); +} + +int32_t LSFG::createContext( + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format) { + if (!instance.has_value() || !device.has_value()) + throw 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::presentContext(int32_t id, int inSem, const std::vector& outSem) { + if (!instance.has_value() || !device.has_value()) + throw vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + auto it = contexts.find(id); + if (it == contexts.end()) + throw vulkan_error(VK_ERROR_UNKNOWN, "Context not found"); + + it->second.present(*device, inSem, outSem); +} + +void LSFG::deleteContext(int32_t id) { + if (!instance.has_value() || !device.has_value()) + throw vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + auto it = contexts.find(id); + if (it == contexts.end()) + throw vulkan_error(VK_ERROR_DEVICE_LOST, "No such context"); + + vkDeviceWaitIdle(device->device.handle()); + contexts.erase(it); +} + +void LSFG::finalize() { + if (!instance.has_value() || !device.has_value()) + return; + + vkDeviceWaitIdle(device->device.handle()); + contexts.clear(); + device.reset(); + instance.reset(); +} + +vulkan_error::vulkan_error(VkResult result, const std::string& message) + : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} + +vulkan_error::~vulkan_error() noexcept = default; diff --git a/lsfg-vk-v3.1/src/pool/resourcepool.cpp b/lsfg-vk-v3.1/src/pool/resourcepool.cpp new file mode 100644 index 0000000..30b5dbe --- /dev/null +++ b/lsfg-vk-v3.1/src/pool/resourcepool.cpp @@ -0,0 +1,67 @@ +#include "pool/resourcepool.hpp" +#include "core/buffer.hpp" + +#include + +using namespace LSFG; +using namespace LSFG::Pool; + +struct ConstantBuffer { + std::array inputOffset; + uint32_t firstIter; + uint32_t firstIterS; + uint32_t advancedColorKind; + uint32_t hdrSupport; + float resolutionInvScale; + float timestamp; + float uiThreshold; + std::array pad; +}; + +Core::Buffer ResourcePool::getBuffer( + const Core::Device& device, + float timestamp, bool firstIter, bool firstIterS) { + uint64_t hash = 0; + const union { float f; uint32_t i; } u{ + .f = timestamp }; + hash |= u.i; // NOLINT + hash |= static_cast(firstIter) << 32; + hash |= static_cast(firstIterS) << 33; + + auto it = buffers.find(hash); + if (it != buffers.end()) + return it->second; + + // create the buffer + const ConstantBuffer data{ + .inputOffset = { 0, 0 }, + .advancedColorKind = this->isHdr ? 2U : 0U, + .hdrSupport = this->isHdr, + .resolutionInvScale = this->flowScale, + .timestamp = timestamp, + .uiThreshold = 0.5F, + }; + Core::Buffer buffer(device, data, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); + buffers[hash] = buffer; + return buffer; +} + +Core::Sampler ResourcePool::getSampler( + const Core::Device& device, + VkSamplerAddressMode type, + VkCompareOp compare, + bool isWhite) { + uint64_t hash = 0; + hash |= static_cast(type) << 0; + hash |= static_cast(compare) << 8; + hash |= static_cast(isWhite) << 16; + + auto it = samplers.find(hash); + if (it != samplers.end()) + return it->second; + + // create the sampler + Core::Sampler sampler(device, type, compare, isWhite); + samplers[hash] = sampler; + return sampler; +} diff --git a/lsfg-vk-v3.1/src/pool/shaderpool.cpp b/lsfg-vk-v3.1/src/pool/shaderpool.cpp new file mode 100644 index 0000000..be9af62 --- /dev/null +++ b/lsfg-vk-v3.1/src/pool/shaderpool.cpp @@ -0,0 +1,44 @@ +#include "pool/shaderpool.hpp" +#include "utils/trans.hpp" + +#include + +using namespace LSFG; +using namespace LSFG::Pool; + +Core::ShaderModule ShaderPool::getShader( + const Core::Device& device, const std::string& name, + const std::vector>& types) { + auto it = shaders.find(name); + if (it != shaders.end()) + return it->second; + + // grab the shader + auto bytecode = this->source(name); + if (bytecode.empty()) + throw std::runtime_error("Shader code is empty: " + name); + + // create the translated shader module + auto spirvBytecode = Utils::Trans::translateShader(bytecode); + if (spirvBytecode.empty()) + throw std::runtime_error("Shader code translation failed: " + name); + + Core::ShaderModule shader(device, spirvBytecode, types); + shaders[name] = shader; + return shader; +} + +Core::Pipeline ShaderPool::getPipeline( + const Core::Device& device, const std::string& name) { + auto it = pipelines.find(name); + if (it != pipelines.end()) + return it->second; + + // grab the shader module + auto shader = this->getShader(device, name, {}); + + // create the pipeline + Core::Pipeline pipeline(device, shader); + pipelines[name] = pipeline; + return pipeline; +} diff --git a/lsfg-vk-v3.1/src/shaders/alpha.cpp b/lsfg-vk-v3.1/src/shaders/alpha.cpp new file mode 100644 index 0000000..198ed02 --- /dev/null +++ b/lsfg-vk-v3.1/src/shaders/alpha.cpp @@ -0,0 +1,130 @@ +#include "shaders/alpha.hpp" + +using namespace LSFG::Shaders; + +Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "alpha[0]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "alpha[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "alpha[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "alpha[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "alpha[0]"), + vk.shaders.getPipeline(vk.device, "alpha[1]"), + vk.shaders.getPipeline(vk.device, "alpha[2]"), + vk.shaders.getPipeline(vk.device, "alpha[3]") + }}; + this->sampler = vk.resources.getSampler(vk.device); + for (size_t i = 0; i < 3; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i)); + for (size_t i = 0; i < 3; i++) + this->lastDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(3)); + + // create internal images/outputs + const VkExtent2D extent = this->inImg.getExtent(); + const VkExtent2D halfExtent = { + .width = (extent.width + 1) >> 1, + .height = (extent.height + 1) >> 1 + }; + for (size_t i = 0; i < 2; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, halfExtent); + this->tempImgs2.at(i) = Core::Image(vk.device, halfExtent); + } + + const VkExtent2D quarterExtent = { + .width = (halfExtent.width + 1) >> 1, + .height = (halfExtent.height + 1) >> 1 + }; + for (size_t i = 0; i < 4; i++) { + this->tempImgs3.at(i) = Core::Image(vk.device, quarterExtent); + for (size_t j = 0; j < 3; j++) + this->outImgs.at(j).at(i) = Core::Image(vk.device, quarterExtent); + } + + // hook up shaders + this->descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + this->descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs3) + .build(); + for (size_t i = 0; i < 3; i++) + this->lastDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i)) + .build(); +} + +void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto halfExtent = this->tempImgs1.at(0).getExtent(); + uint32_t threadsX = (halfExtent.width + 7) >> 3; + uint32_t threadsY = (halfExtent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImg) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(0).bind(buf); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third pass + const auto quarterExtent = this->tempImgs3.at(0).getExtent(); + threadsX = (quarterExtent.width + 7) >> 3; + threadsY = (quarterExtent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs3) + .build(); + + this->pipelines.at(2).bind(buf); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs3) + .addR2W(this->outImgs.at(frameCount % 3)) + .build(); + + this->pipelines.at(3).bind(buf); + this->lastDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1/src/shaders/beta.cpp b/lsfg-vk-v3.1/src/shaders/beta.cpp new file mode 100644 index 0000000..151ae3f --- /dev/null +++ b/lsfg-vk-v3.1/src/shaders/beta.cpp @@ -0,0 +1,151 @@ +#include "shaders/beta.hpp" + +using namespace LSFG::Shaders; + +Beta::Beta(Vulkan& vk, std::array, 3> inImgs) + : inImgs(std::move(inImgs)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "beta[0]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 12, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "beta[0]"), + vk.shaders.getPipeline(vk.device, "beta[1]"), + vk.shaders.getPipeline(vk.device, "beta[2]"), + vk.shaders.getPipeline(vk.device, "beta[3]"), + vk.shaders.getPipeline(vk.device, "beta[4]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + for (size_t i = 0; i < 3; i++) + this->firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(0)); + for (size_t i = 0; i < 4; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i + 1)); + this->buffer = vk.resources.getBuffer(vk.device, 0.5F); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs.at(0).at(0).getExtent(); + for (size_t i = 0; i < 2; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + } + + for (size_t i = 0; i < 6; i++) + this->outImgs.at(i) = Core::Image(vk.device, + { extent.width >> i, extent.height >> i }, + VK_FORMAT_R8_UNORM); + + // hook up shaders + for (size_t i = 0; i < 3; i++) { + this->firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 1) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + } + this->descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + this->descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .build(); +} + +void Beta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto extent = this->tempImgs1.at(0).getExtent(); + uint32_t threadsX = (extent.width + 7) >> 3; + uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs.at(0)) + .addW2R(this->inImgs.at(1)) + .addW2R(this->inImgs.at(2)) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(0).bind(buf); + this->firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth pass + threadsX = (extent.width + 31) >> 5; + threadsY = (extent.height + 31) >> 5; + + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->outImgs) + .build(); + + this->pipelines.at(4).bind(buf); + this->descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1/src/shaders/delta.cpp b/lsfg-vk-v3.1/src/shaders/delta.cpp new file mode 100644 index 0000000..ca83cb0 --- /dev/null +++ b/lsfg-vk-v3.1/src/shaders/delta.cpp @@ -0,0 +1,329 @@ +#include "shaders/delta.hpp" + +using namespace LSFG::Shaders; + +Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg1, + std::optional optImg2, + std::optional optImg3) + : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), + optImg1(std::move(optImg1)), optImg2(std::move(optImg2)), + optImg3(std::move(optImg3)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "delta[0]", + { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 9, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[5]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 10, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[6]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[7]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[8]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[9]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "delta[0]"), + vk.shaders.getPipeline(vk.device, "delta[1]"), + vk.shaders.getPipeline(vk.device, "delta[2]"), + vk.shaders.getPipeline(vk.device, "delta[3]"), + vk.shaders.getPipeline(vk.device, "delta[4]"), + vk.shaders.getPipeline(vk.device, "delta[5]"), + vk.shaders.getPipeline(vk.device, "delta[6]"), + vk.shaders.getPipeline(vk.device, "delta[7]"), + vk.shaders.getPipeline(vk.device, "delta[8]"), + vk.shaders.getPipeline(vk.device, "delta[9]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + this->samplers.at(2) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); + for (size_t i = 0; i < 4; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + } + + this->outImg1 = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + this->outImg2 = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + + // hook up shaders + for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(pass_idx + 1) / static_cast(vk.generationCount + 1), + false, !this->optImg1.has_value()); + for (size_t i = 0; i < 3; i++) { + pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(0)); + pass.firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(2)) + .build(); + } + pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(1)); + pass.descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(2)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(2)); + pass.descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(3)); + pass.descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(4)); + pass.descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1) + .build(); + for (size_t i = 0; i < 3; i++) { + pass.sixthDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(5)); + pass.sixthDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(1)) + .build(); + } + pass.descriptorSets.at(4) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(6)); + pass.descriptorSets.at(4).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .build(); + pass.descriptorSets.at(5) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(7)); + pass.descriptorSets.at(5).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(1)) + .build(); + pass.descriptorSets.at(6) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(8)); + pass.descriptorSets.at(6).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .build(); + pass.descriptorSets.at(7) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(9)); + pass.descriptorSets.at(7).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2) + .build(); + } +} + +void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first shader + const auto extent = this->tempImgs1.at(0).getExtent(); + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg1) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .addR2W(this->tempImgs1.at(2)) + .build(); + + this->pipelines.at(0).bind(buf); + pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addW2R(this->tempImgs1.at(2)) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addW2R(this->optImg1) + .addW2R(this->inImg2) + .addR2W(this->outImg1) + .build(); + + this->pipelines.at(4).bind(buf); + pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); + + // sixth shader + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg1) + .addW2R(this->optImg2) + .addR2W(this->tempImgs2.at(0)) + .addR2W(this->tempImgs2.at(1)) + .build(); + + this->pipelines.at(5).bind(buf); + pass.sixthDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(5)); + buf.dispatch(threadsX, threadsY, 1); + + // seventh shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2.at(0)) + .addW2R(this->tempImgs2.at(1)) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .build(); + + this->pipelines.at(6).bind(buf); + pass.descriptorSets.at(4).bind(buf, this->pipelines.at(6)); + buf.dispatch(threadsX, threadsY, 1); + + // eighth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addR2W(this->tempImgs2.at(0)) + .addR2W(this->tempImgs2.at(1)) + .build(); + this->pipelines.at(7).bind(buf); + pass.descriptorSets.at(5).bind(buf, this->pipelines.at(7)); + buf.dispatch(threadsX, threadsY, 1); + + // ninth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2.at(0)) + .addW2R(this->tempImgs2.at(1)) + .addW2R(this->optImg3) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .build(); + + this->pipelines.at(8).bind(buf); + pass.descriptorSets.at(6).bind(buf, this->pipelines.at(8)); + buf.dispatch(threadsX, threadsY, 1); + + // tenth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addW2R(this->optImg3) + .addR2W(this->outImg2) + .build(); + + this->pipelines.at(9).bind(buf); + pass.descriptorSets.at(7).bind(buf, this->pipelines.at(9)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1/src/shaders/gamma.cpp b/lsfg-vk-v3.1/src/shaders/gamma.cpp new file mode 100644 index 0000000..0898f2c --- /dev/null +++ b/lsfg-vk-v3.1/src/shaders/gamma.cpp @@ -0,0 +1,181 @@ +#include "shaders/gamma.hpp" + +using namespace LSFG::Shaders; + +Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg) + : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), + optImg(std::move(optImg)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "gamma[0]", + { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 9, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "gamma[0]"), + vk.shaders.getPipeline(vk.device, "gamma[1]"), + vk.shaders.getPipeline(vk.device, "gamma[2]"), + vk.shaders.getPipeline(vk.device, "gamma[3]"), + vk.shaders.getPipeline(vk.device, "gamma[4]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + this->samplers.at(2) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); + for (size_t i = 0; i < 4; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + } + + this->outImg = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + + // hook up shaders + for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(pass_idx + 1) / static_cast(vk.generationCount + 1), + !this->optImg.has_value()); + for (size_t i = 0; i < 3; i++) { + pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(0)); + pass.firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(2)) + .build(); + } + pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(1)); + pass.descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(2)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(2)); + pass.descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(3)); + pass.descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(4)); + pass.descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .build(); + } +} + +void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first shader + const auto extent = this->tempImgs1.at(0).getExtent(); + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .addR2W(this->tempImgs1.at(2)) + .build(); + + this->pipelines.at(0).bind(buf); + pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addW2R(this->tempImgs1.at(2)) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addW2R(this->optImg) + .addW2R(this->inImg2) + .addR2W(this->outImg) + .build(); + + this->pipelines.at(4).bind(buf); + pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1/src/shaders/generate.cpp b/lsfg-vk-v3.1/src/shaders/generate.cpp new file mode 100644 index 0000000..2e21b7f --- /dev/null +++ b/lsfg-vk-v3.1/src/shaders/generate.cpp @@ -0,0 +1,73 @@ +#include "shaders/generate.hpp" + +using namespace LSFG::Shaders; + +Generate::Generate(Vulkan& vk, + Core::Image inImg1, Core::Image inImg2, + Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, + const std::vector& fds) + : 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, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, fds.at(i)); + + // hook up shaders + for (size_t i = 0; i < vk.generationCount; i++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(i + 1) / static_cast(vk.generationCount + 1)); + for (size_t i = 0; i < 2; i++) { + pass.descriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModule); + pass.descriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, i == 0 ? this->inImg1 : this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, i == 0 ? this->inImg2 : this->inImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg4) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg5) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i)) + .build(); + } + } +} + +void Generate::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first pass + const auto extent = this->inImg1.getExtent(); + const uint32_t threadsX = (extent.width + 15) >> 4; + const uint32_t threadsY = (extent.height + 15) >> 4; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImg1) + .addW2R(this->inImg2) + .addW2R(this->inImg3) + .addW2R(this->inImg4) + .addW2R(this->inImg5) + .addR2W(this->outImgs.at(pass_idx)) + .build(); + + this->pipeline.bind(buf); + pass.descriptorSet.at(frameCount % 2).bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1/src/shaders/mipmaps.cpp b/lsfg-vk-v3.1/src/shaders/mipmaps.cpp new file mode 100644 index 0000000..894c6f8 --- /dev/null +++ b/lsfg-vk-v3.1/src/shaders/mipmaps.cpp @@ -0,0 +1,56 @@ +#include "shaders/mipmaps.hpp" + +using namespace LSFG::Shaders; + +Mipmaps::Mipmaps(Vulkan& vk, + Core::Image inImg_0, Core::Image inImg_1) + : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) { + // create resources + this->shaderModule = vk.shaders.getShader(vk.device, "mipmaps", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); + this->pipeline = vk.shaders.getPipeline(vk.device, "mipmaps"); + this->buffer = vk.resources.getBuffer(vk.device); + this->sampler = vk.resources.getSampler(vk.device); + for (size_t i = 0; i < 2; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModule); + + // create outputs + const VkExtent2D flowExtent{ + .width = static_cast( + static_cast(this->inImg_0.getExtent().width) / vk.flowScale), + .height = static_cast( + static_cast(this->inImg_0.getExtent().height) / vk.flowScale) + }; + for (size_t i = 0; i < 7; i++) + this->outImgs.at(i) = Core::Image(vk.device, + { flowExtent.width >> i, flowExtent.height >> i }, + VK_FORMAT_R8_UNORM); + + // hook up shaders + for (size_t fc = 0; fc < 2; fc++) + this->descriptorSets.at(fc).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg_0 : this->inImg_1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .build(); +} + +void Mipmaps::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto flowExtent = this->outImgs.at(0).getExtent(); + const uint32_t threadsX = (flowExtent.width + 63) >> 6; + const uint32_t threadsY = (flowExtent.height + 63) >> 6; + + Utils::BarrierBuilder(buf) + .addW2R((frameCount % 2 == 0) ? this->inImg_0 : this->inImg_1) + .addR2W(this->outImgs) + .build(); + + this->pipeline.bind(buf); + this->descriptorSets.at(frameCount % 2).bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1/src/utils/trans.cpp b/lsfg-vk-v3.1/src/utils/trans.cpp new file mode 100644 index 0000000..a730d96 --- /dev/null +++ b/lsfg-vk-v3.1/src/utils/trans.cpp @@ -0,0 +1,28 @@ +#include "utils/trans.hpp" + +using namespace LSFG::Utils; + +std::vector Trans::translateShader(std::vector bytecode) { + return bytecode; // on windows we expect the bytecode to be spir-v +// // compile the shader +// dxvk::DxbcReader reader(reinterpret_cast(bytecode.data()), bytecode.size()); +// dxvk::DxbcModule module(reader); +// const dxvk::DxbcModuleInfo info{}; +// auto shader = module.compile(info, "CS"); + +// // extract spir-v from d3d11 shader +// auto code = shader->getRawCode(); + +// // patch binding offsets +// #pragma clang diagnostic push +// #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +// for (size_t i = 0; i < shader->m_bindingOffsets.size(); i++) +// code.data()[shader->m_bindingOffsets.at(i).bindingOffset] = static_cast(i); // NOLINT +// #pragma clang diagnostic pop + +// std::vector spirvBytecode(code.size()); +// std::copy_n(reinterpret_cast(code.data()), +// code.size(), spirvBytecode.data()); +// return spirvBytecode; +// #endif +} diff --git a/lsfg-vk-gen/src/utils/utils.cpp b/lsfg-vk-v3.1/src/utils/utils.cpp similarity index 86% rename from lsfg-vk-gen/src/utils/utils.cpp rename to lsfg-vk-v3.1/src/utils/utils.cpp index 4260e0d..5aea8fd 100644 --- a/lsfg-vk-gen/src/utils/utils.cpp +++ b/lsfg-vk-v3.1/src/utils/utils.cpp @@ -1,9 +1,21 @@ #include "utils/utils.hpp" #include "core/buffer.hpp" +#include "core/image.hpp" +#include "core/device.hpp" +#include "core/commandpool.hpp" +#include "core/fence.hpp" #include "lsfg.hpp" +#include + +#include +#include #include #include +#include +#include +#include +#include using namespace LSFG; using namespace LSFG::Utils; @@ -176,32 +188,3 @@ void Utils::clearImage(const Core::Device& device, Core::Image& image, bool whit if (!fence.wait(device)) throw LSFG::vulkan_error(VK_TIMEOUT, "Failed to wait for clearing fence."); } - -Core::Sampler Globals::samplerClampBorder; -Core::Sampler Globals::samplerClampEdge; -Globals::FgBuffer Globals::fgBuffer; - -void Globals::initializeGlobals(const Core::Device& device) { - // initialize global samplers - samplerClampBorder = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER); - samplerClampEdge = Core::Sampler(device, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); - - // initialize global constant buffer - fgBuffer = { - .inputOffset = { 0, 29 }, - .advancedColorKind = getenv("LSFG_HDR") == nullptr ? 0U : 2U, - .hdrSupport = getenv("LSFG_HDR") != nullptr, - .resolutionInvScale = 1.0F, - .timestamp = 0.5F, - .uiThreshold = 0.1F, - }; -} - -void Globals::uninitializeGlobals() noexcept { - // uninitialize global samplers - samplerClampBorder = Core::Sampler(); - samplerClampEdge = Core::Sampler(); - - // uninitialize global constant buffer - fgBuffer = Globals::FgBuffer(); -} From 794fbcf28cf404c540cdd0c53cb1294fb040af12 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 02:12:58 +0200 Subject: [PATCH 103/253] lsfg-vk-v3.1: cleaning up headers --- lsfg-vk-v3.1/{.clang-tidy-no => .clang-tidy} | 0 lsfg-vk-v3.1/CMakeLists.txt | 7 +++++-- lsfg-vk-v3.1/include/context.hpp | 16 ++++++++++++---- lsfg-vk-v3.1/include/core/buffer.hpp | 6 ++---- lsfg-vk-v3.1/include/core/commandbuffer.hpp | 6 ++---- lsfg-vk-v3.1/include/core/commandpool.hpp | 5 +---- lsfg-vk-v3.1/include/core/descriptorpool.hpp | 5 +---- lsfg-vk-v3.1/include/core/descriptorset.hpp | 9 +++++---- lsfg-vk-v3.1/include/core/device.hpp | 5 +---- lsfg-vk-v3.1/include/core/fence.hpp | 6 ++---- lsfg-vk-v3.1/include/core/image.hpp | 5 +---- lsfg-vk-v3.1/include/core/instance.hpp | 5 +---- lsfg-vk-v3.1/include/core/pipeline.hpp | 5 +---- lsfg-vk-v3.1/include/core/sampler.hpp | 5 +---- lsfg-vk-v3.1/include/core/semaphore.hpp | 6 ++---- lsfg-vk-v3.1/include/core/shadermodule.hpp | 7 +++---- lsfg-vk-v3.1/include/pool/resourcepool.hpp | 7 +++---- lsfg-vk-v3.1/include/pool/shaderpool.hpp | 10 ++++++---- lsfg-vk-v3.1/include/shaders/alpha.hpp | 7 +++---- lsfg-vk-v3.1/include/shaders/beta.hpp | 8 ++++---- lsfg-vk-v3.1/include/shaders/delta.hpp | 9 +++++---- lsfg-vk-v3.1/include/shaders/gamma.hpp | 9 +++++---- lsfg-vk-v3.1/include/shaders/generate.hpp | 9 +++++---- lsfg-vk-v3.1/include/shaders/mipmaps.hpp | 7 +++---- lsfg-vk-v3.1/include/utils/trans.hpp | 5 +---- lsfg-vk-v3.1/include/utils/utils.hpp | 12 ++++++++---- lsfg-vk-v3.1/public/lsfg.hpp | 10 ++++------ lsfg-vk-v3.1/src/core/buffer.cpp | 5 +++++ lsfg-vk-v3.1/src/core/commandbuffer.cpp | 12 ++++++++++++ lsfg-vk-v3.1/src/core/commandpool.cpp | 5 +++++ lsfg-vk-v3.1/src/core/descriptorpool.cpp | 5 +++++ lsfg-vk-v3.1/src/core/descriptorset.cpp | 13 +++++++++++++ lsfg-vk-v3.1/src/core/device.cpp | 5 +++++ lsfg-vk-v3.1/src/core/fence.cpp | 6 ++++++ lsfg-vk-v3.1/src/core/image.cpp | 5 +++++ lsfg-vk-v3.1/src/core/instance.cpp | 3 +++ lsfg-vk-v3.1/src/core/pipeline.cpp | 7 +++++++ lsfg-vk-v3.1/src/core/sampler.cpp | 4 ++++ lsfg-vk-v3.1/src/core/semaphore.cpp | 8 ++++++++ lsfg-vk-v3.1/src/core/shadermodule.cpp | 9 +++++++++ lsfg-vk-v3.1/src/pool/resourcepool.cpp | 5 +++++ lsfg-vk-v3.1/src/pool/shaderpool.cpp | 9 +++++++++ lsfg-vk-v3.1/src/shaders/alpha.cpp | 9 +++++++++ lsfg-vk-v3.1/src/shaders/beta.cpp | 10 ++++++++++ lsfg-vk-v3.1/src/shaders/delta.cpp | 11 +++++++++++ lsfg-vk-v3.1/src/shaders/gamma.cpp | 11 +++++++++++ lsfg-vk-v3.1/src/shaders/generate.cpp | 10 ++++++++++ lsfg-vk-v3.1/src/shaders/mipmaps.cpp | 9 +++++++++ lsfg-vk-v3.1/src/utils/trans.cpp | 3 +++ 49 files changed, 251 insertions(+), 104 deletions(-) rename lsfg-vk-v3.1/{.clang-tidy-no => .clang-tidy} (100%) diff --git a/lsfg-vk-v3.1/.clang-tidy-no b/lsfg-vk-v3.1/.clang-tidy similarity index 100% rename from lsfg-vk-v3.1/.clang-tidy-no rename to lsfg-vk-v3.1/.clang-tidy diff --git a/lsfg-vk-v3.1/CMakeLists.txt b/lsfg-vk-v3.1/CMakeLists.txt index e40d1f6..dad352d 100644 --- a/lsfg-vk-v3.1/CMakeLists.txt +++ b/lsfg-vk-v3.1/CMakeLists.txt @@ -5,6 +5,8 @@ project(lsfg-vk-v3.1 DESCRIPTION "Lossless Scaling Frame Generation v3.1" LANGUAGES CXX) +option(LDFGVK_EXCESS_DEBUG "Enable excessive debug output" OFF) + file(GLOB SOURCES "src/core/*.cpp" "src/pool/*.cpp" @@ -33,7 +35,8 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") EXPORT_COMPILE_COMMANDS ON) endif() -if(LSFGVK_EXCESS_DEBUG STREQUAL "ON") +if(LSFGVK_EXCESS_DEBUG) + message(STATUS "LSFGVK_EXCESS_DEBUG is only compatible with clang") target_compile_options(lsfg-vk-v3.1 PRIVATE -Weverything # disable compat c++ flags @@ -53,5 +56,5 @@ if(LSFGVK_EXCESS_DEBUG STREQUAL "ON") ) set_target_properties(lsfg-vk-v3.1 PROPERTIES - CMAKE_CXX_CLANG_TIDY clang-tidy) + CXX_CLANG_TIDY clang-tidy) endif() diff --git a/lsfg-vk-v3.1/include/context.hpp b/lsfg-vk-v3.1/include/context.hpp index df1dae1..6283236 100644 --- a/lsfg-vk-v3.1/include/context.hpp +++ b/lsfg-vk-v3.1/include/context.hpp @@ -1,12 +1,22 @@ -#ifndef CONTEXT_HPP -#define CONTEXT_HPP +#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 "utils/utils.hpp" + +#include + +#include +#include +#include namespace LSFG { @@ -71,5 +81,3 @@ namespace LSFG { }; } - -#endif // CONTEXT_HPP diff --git a/lsfg-vk-v3.1/include/core/buffer.hpp b/lsfg-vk-v3.1/include/core/buffer.hpp index a2c369e..1aa1b49 100644 --- a/lsfg-vk-v3.1/include/core/buffer.hpp +++ b/lsfg-vk-v3.1/include/core/buffer.hpp @@ -1,10 +1,10 @@ -#ifndef BUFFER_HPP -#define BUFFER_HPP +#pragma once #include "core/device.hpp" #include +#include #include namespace LSFG::Core { @@ -69,5 +69,3 @@ namespace LSFG::Core { }; } - -#endif // BUFFER_HPP diff --git a/lsfg-vk-v3.1/include/core/commandbuffer.hpp b/lsfg-vk-v3.1/include/core/commandbuffer.hpp index d9395db..35eeb57 100644 --- a/lsfg-vk-v3.1/include/core/commandbuffer.hpp +++ b/lsfg-vk-v3.1/include/core/commandbuffer.hpp @@ -1,5 +1,4 @@ -#ifndef COMMANDBUFFER_HPP -#define COMMANDBUFFER_HPP +#pragma once #include "core/commandpool.hpp" #include "core/fence.hpp" @@ -8,6 +7,7 @@ #include +#include #include #include #include @@ -110,5 +110,3 @@ namespace LSFG::Core { }; } - -#endif // COMMANDBUFFER_HPP diff --git a/lsfg-vk-v3.1/include/core/commandpool.hpp b/lsfg-vk-v3.1/include/core/commandpool.hpp index 9946176..7f4539d 100644 --- a/lsfg-vk-v3.1/include/core/commandpool.hpp +++ b/lsfg-vk-v3.1/include/core/commandpool.hpp @@ -1,5 +1,4 @@ -#ifndef COMMANDPOOL_HPP -#define COMMANDPOOL_HPP +#pragma once #include "core/device.hpp" @@ -41,5 +40,3 @@ namespace LSFG::Core { }; } - -#endif // COMMANDPOOL_HPP diff --git a/lsfg-vk-v3.1/include/core/descriptorpool.hpp b/lsfg-vk-v3.1/include/core/descriptorpool.hpp index 76ac03c..792f843 100644 --- a/lsfg-vk-v3.1/include/core/descriptorpool.hpp +++ b/lsfg-vk-v3.1/include/core/descriptorpool.hpp @@ -1,5 +1,4 @@ -#ifndef DESCRIPTORPOOL_HPP -#define DESCRIPTORPOOL_HPP +#pragma once #include "core/device.hpp" @@ -41,5 +40,3 @@ namespace LSFG::Core { }; } - -#endif // DESCRIPTORPOOL_HPP diff --git a/lsfg-vk-v3.1/include/core/descriptorset.hpp b/lsfg-vk-v3.1/include/core/descriptorset.hpp index cdd15ad..719035d 100644 --- a/lsfg-vk-v3.1/include/core/descriptorset.hpp +++ b/lsfg-vk-v3.1/include/core/descriptorset.hpp @@ -1,5 +1,4 @@ -#ifndef DESCRIPTORSET_HPP -#define DESCRIPTORSET_HPP +#pragma once #include "core/buffer.hpp" #include "core/commandbuffer.hpp" @@ -12,6 +11,10 @@ #include +#include +#include +#include +#include #include namespace LSFG::Core { @@ -119,5 +122,3 @@ namespace LSFG::Core { }; } - -#endif // DESCRIPTORSET_HPP diff --git a/lsfg-vk-v3.1/include/core/device.hpp b/lsfg-vk-v3.1/include/core/device.hpp index b0bdcf7..d03a89c 100644 --- a/lsfg-vk-v3.1/include/core/device.hpp +++ b/lsfg-vk-v3.1/include/core/device.hpp @@ -1,5 +1,4 @@ -#ifndef DEVICE_HPP -#define DEVICE_HPP +#pragma once #include "core/instance.hpp" @@ -52,5 +51,3 @@ namespace LSFG::Core { }; } - -#endif // DEVICE_HPP diff --git a/lsfg-vk-v3.1/include/core/fence.hpp b/lsfg-vk-v3.1/include/core/fence.hpp index 6a50eb1..1739220 100644 --- a/lsfg-vk-v3.1/include/core/fence.hpp +++ b/lsfg-vk-v3.1/include/core/fence.hpp @@ -1,10 +1,10 @@ -#ifndef FENCE_HPP -#define FENCE_HPP +#pragma once #include "core/device.hpp" #include +#include #include namespace LSFG::Core { @@ -61,5 +61,3 @@ namespace LSFG::Core { }; } - -#endif // FENCE_HPP diff --git a/lsfg-vk-v3.1/include/core/image.hpp b/lsfg-vk-v3.1/include/core/image.hpp index 2d93854..9731bdf 100644 --- a/lsfg-vk-v3.1/include/core/image.hpp +++ b/lsfg-vk-v3.1/include/core/image.hpp @@ -1,5 +1,4 @@ -#ifndef IMAGE_HPP -#define IMAGE_HPP +#pragma once #include "core/device.hpp" @@ -86,5 +85,3 @@ namespace LSFG::Core { }; } - -#endif // IMAGE_HPP diff --git a/lsfg-vk-v3.1/include/core/instance.hpp b/lsfg-vk-v3.1/include/core/instance.hpp index 3fb7d26..24cba8a 100644 --- a/lsfg-vk-v3.1/include/core/instance.hpp +++ b/lsfg-vk-v3.1/include/core/instance.hpp @@ -1,5 +1,4 @@ -#ifndef INSTANCE_HPP -#define INSTANCE_HPP +#pragma once #include @@ -35,5 +34,3 @@ namespace LSFG::Core { }; } - -#endif // INSTANCE_HPP diff --git a/lsfg-vk-v3.1/include/core/pipeline.hpp b/lsfg-vk-v3.1/include/core/pipeline.hpp index 6e2f981..6eddb5a 100644 --- a/lsfg-vk-v3.1/include/core/pipeline.hpp +++ b/lsfg-vk-v3.1/include/core/pipeline.hpp @@ -1,5 +1,4 @@ -#ifndef PIPELINE_HPP -#define PIPELINE_HPP +#pragma once #include "core/commandbuffer.hpp" #include "core/shadermodule.hpp" @@ -54,5 +53,3 @@ namespace LSFG::Core { }; } - -#endif // PIPELINE_HPP diff --git a/lsfg-vk-v3.1/include/core/sampler.hpp b/lsfg-vk-v3.1/include/core/sampler.hpp index dd6ab15..981077c 100644 --- a/lsfg-vk-v3.1/include/core/sampler.hpp +++ b/lsfg-vk-v3.1/include/core/sampler.hpp @@ -1,5 +1,4 @@ -#ifndef SAMPLER_HPP -#define SAMPLER_HPP +#pragma once #include "core/device.hpp" @@ -47,5 +46,3 @@ namespace LSFG::Core { }; } - -#endif // SAMPLER_HPP diff --git a/lsfg-vk-v3.1/include/core/semaphore.hpp b/lsfg-vk-v3.1/include/core/semaphore.hpp index 2060c33..773ca0c 100644 --- a/lsfg-vk-v3.1/include/core/semaphore.hpp +++ b/lsfg-vk-v3.1/include/core/semaphore.hpp @@ -1,10 +1,10 @@ -#ifndef SEMAPHORE_HPP -#define SEMAPHORE_HPP +#pragma once #include "core/device.hpp" #include +#include #include #include @@ -78,5 +78,3 @@ namespace LSFG::Core { }; } - -#endif // SEMAPHORE_HPP diff --git a/lsfg-vk-v3.1/include/core/shadermodule.hpp b/lsfg-vk-v3.1/include/core/shadermodule.hpp index d71e7e4..ac38234 100644 --- a/lsfg-vk-v3.1/include/core/shadermodule.hpp +++ b/lsfg-vk-v3.1/include/core/shadermodule.hpp @@ -1,10 +1,11 @@ -#ifndef SHADERMODULE_HPP -#define SHADERMODULE_HPP +#pragma once #include "core/device.hpp" #include +#include +#include #include #include #include @@ -49,5 +50,3 @@ namespace LSFG::Core { }; } - -#endif // SHADERMODULE_HPP diff --git a/lsfg-vk-v3.1/include/pool/resourcepool.hpp b/lsfg-vk-v3.1/include/pool/resourcepool.hpp index baa165f..729dfb3 100644 --- a/lsfg-vk-v3.1/include/pool/resourcepool.hpp +++ b/lsfg-vk-v3.1/include/pool/resourcepool.hpp @@ -1,11 +1,12 @@ -#ifndef RESOURCEPOOL_HPP -#define RESOURCEPOOL_HPP +#pragma once #include "core/device.hpp" #include "core/buffer.hpp" #include "core/sampler.hpp" + #include "vulkan/vulkan_core.h" +#include #include namespace LSFG::Pool { @@ -66,5 +67,3 @@ namespace LSFG::Pool { }; } - -#endif // RESOURCEPOOL_HPP diff --git a/lsfg-vk-v3.1/include/pool/shaderpool.hpp b/lsfg-vk-v3.1/include/pool/shaderpool.hpp index db42533..e572154 100644 --- a/lsfg-vk-v3.1/include/pool/shaderpool.hpp +++ b/lsfg-vk-v3.1/include/pool/shaderpool.hpp @@ -1,13 +1,17 @@ -#ifndef SHADERPOOL_HPP -#define SHADERPOOL_HPP +#pragma once #include "core/device.hpp" #include "core/pipeline.hpp" #include "core/shadermodule.hpp" +#include + +#include +#include #include #include #include +#include #include namespace LSFG::Pool { @@ -59,5 +63,3 @@ namespace LSFG::Pool { }; } - -#endif // SHADERPOOL_HPP diff --git a/lsfg-vk-v3.1/include/shaders/alpha.hpp b/lsfg-vk-v3.1/include/shaders/alpha.hpp index 8f254e5..fa48ec2 100644 --- a/lsfg-vk-v3.1/include/shaders/alpha.hpp +++ b/lsfg-vk-v3.1/include/shaders/alpha.hpp @@ -1,14 +1,15 @@ -#ifndef ALPHA_HPP -#define ALPHA_HPP +#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 "utils/utils.hpp" #include +#include namespace LSFG::Shaders { @@ -57,5 +58,3 @@ namespace LSFG::Shaders { }; } - -#endif // ALPHA_HPP diff --git a/lsfg-vk-v3.1/include/shaders/beta.hpp b/lsfg-vk-v3.1/include/shaders/beta.hpp index dccb04f..b8e2a2b 100644 --- a/lsfg-vk-v3.1/include/shaders/beta.hpp +++ b/lsfg-vk-v3.1/include/shaders/beta.hpp @@ -1,14 +1,16 @@ -#ifndef BETA_HPP -#define BETA_HPP +#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 "utils/utils.hpp" #include +#include namespace LSFG::Shaders { @@ -57,5 +59,3 @@ namespace LSFG::Shaders { }; } - -#endif // BETA_HPP diff --git a/lsfg-vk-v3.1/include/shaders/delta.hpp b/lsfg-vk-v3.1/include/shaders/delta.hpp index 93fd127..5e81aec 100644 --- a/lsfg-vk-v3.1/include/shaders/delta.hpp +++ b/lsfg-vk-v3.1/include/shaders/delta.hpp @@ -1,15 +1,18 @@ -#ifndef DELTA_HPP -#define DELTA_HPP +#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 "utils/utils.hpp" #include +#include #include +#include namespace LSFG::Shaders { @@ -74,5 +77,3 @@ namespace LSFG::Shaders { }; } - -#endif // DELTA_HPP diff --git a/lsfg-vk-v3.1/include/shaders/gamma.hpp b/lsfg-vk-v3.1/include/shaders/gamma.hpp index a4b70f8..0f09ab3 100644 --- a/lsfg-vk-v3.1/include/shaders/gamma.hpp +++ b/lsfg-vk-v3.1/include/shaders/gamma.hpp @@ -1,15 +1,18 @@ -#ifndef GAMMA_HPP -#define GAMMA_HPP +#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 "utils/utils.hpp" #include +#include #include +#include namespace LSFG::Shaders { @@ -66,5 +69,3 @@ namespace LSFG::Shaders { }; } - -#endif // GAMMA_HPP diff --git a/lsfg-vk-v3.1/include/shaders/generate.hpp b/lsfg-vk-v3.1/include/shaders/generate.hpp index 6ad6538..a478172 100644 --- a/lsfg-vk-v3.1/include/shaders/generate.hpp +++ b/lsfg-vk-v3.1/include/shaders/generate.hpp @@ -1,14 +1,17 @@ -#ifndef GENERATE_HPP -#define GENERATE_HPP +#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 "utils/utils.hpp" #include +#include +#include namespace LSFG::Shaders { @@ -63,5 +66,3 @@ namespace LSFG::Shaders { }; } - -#endif // GENERATE_HPP diff --git a/lsfg-vk-v3.1/include/shaders/mipmaps.hpp b/lsfg-vk-v3.1/include/shaders/mipmaps.hpp index 8d1f75e..741c93c 100644 --- a/lsfg-vk-v3.1/include/shaders/mipmaps.hpp +++ b/lsfg-vk-v3.1/include/shaders/mipmaps.hpp @@ -1,15 +1,16 @@ -#ifndef MIPMAPS_HPP -#define MIPMAPS_HPP +#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 "utils/utils.hpp" #include +#include namespace LSFG::Shaders { @@ -56,5 +57,3 @@ namespace LSFG::Shaders { }; } - -#endif // MIPMAPS_HPP diff --git a/lsfg-vk-v3.1/include/utils/trans.hpp b/lsfg-vk-v3.1/include/utils/trans.hpp index 5541892..71e28be 100644 --- a/lsfg-vk-v3.1/include/utils/trans.hpp +++ b/lsfg-vk-v3.1/include/utils/trans.hpp @@ -1,5 +1,4 @@ -#ifndef TRANS_HPP -#define TRANS_HPP +#pragma once #include #include @@ -15,5 +14,3 @@ namespace LSFG::Utils::Trans { [[nodiscard]] std::vector translateShader(std::vector bytecode); } - -#endif // TRANS_HPP diff --git a/lsfg-vk-v3.1/include/utils/utils.hpp b/lsfg-vk-v3.1/include/utils/utils.hpp index ee29f15..82e7039 100644 --- a/lsfg-vk-v3.1/include/utils/utils.hpp +++ b/lsfg-vk-v3.1/include/utils/utils.hpp @@ -1,15 +1,21 @@ -#ifndef UTILS_HPP -#define UTILS_HPP +#pragma once #include "core/commandbuffer.hpp" +#include "core/commandpool.hpp" #include "core/descriptorpool.hpp" #include "core/image.hpp" #include "core/device.hpp" #include "pool/resourcepool.hpp" #include "pool/shaderpool.hpp" +#include + +#include +#include +#include #include #include +#include namespace LSFG::Utils { @@ -100,5 +106,3 @@ namespace LSFG { Pool::ResourcePool resources; }; } - -#endif // UTILS_HPP diff --git a/lsfg-vk-v3.1/public/lsfg.hpp b/lsfg-vk-v3.1/public/lsfg.hpp index d55e185..3ebfee8 100644 --- a/lsfg-vk-v3.1/public/lsfg.hpp +++ b/lsfg-vk-v3.1/public/lsfg.hpp @@ -1,10 +1,11 @@ -#ifndef LSFG_3_1_HPP -#define LSFG_3_1_HPP +#pragma once #include #include #include +#include +#include #include namespace LSFG { @@ -22,8 +23,7 @@ namespace LSFG { /// void initialize(uint64_t deviceUUID, bool isHdr, float flowScale, uint64_t generationCount, - const std::function(const std::string&)>& loader - ); + const std::function(const std::string&)>& loader); /// /// Create a new LSFG context on a swapchain. @@ -89,5 +89,3 @@ namespace LSFG { }; } - -#endif // LSFG_3_1_HPP diff --git a/lsfg-vk-v3.1/src/core/buffer.cpp b/lsfg-vk-v3.1/src/core/buffer.cpp index 6a5e225..c18a8ae 100644 --- a/lsfg-vk-v3.1/src/core/buffer.cpp +++ b/lsfg-vk-v3.1/src/core/buffer.cpp @@ -1,7 +1,12 @@ #include "core/buffer.hpp" +#include "core/device.hpp" #include "lsfg.hpp" +#include + #include +#include +#include #include using namespace LSFG::Core; diff --git a/lsfg-vk-v3.1/src/core/commandbuffer.cpp b/lsfg-vk-v3.1/src/core/commandbuffer.cpp index 86a1d0e..84999de 100644 --- a/lsfg-vk-v3.1/src/core/commandbuffer.cpp +++ b/lsfg-vk-v3.1/src/core/commandbuffer.cpp @@ -1,6 +1,18 @@ #include "core/commandbuffer.hpp" +#include "core/device.hpp" +#include "core/commandpool.hpp" +#include "core/fence.hpp" +#include "core/semaphore.hpp" #include "lsfg.hpp" +#include + +#include +#include +#include +#include +#include + using namespace LSFG::Core; CommandBuffer::CommandBuffer(const Core::Device& device, const CommandPool& pool) { diff --git a/lsfg-vk-v3.1/src/core/commandpool.cpp b/lsfg-vk-v3.1/src/core/commandpool.cpp index f1a6030..377de39 100644 --- a/lsfg-vk-v3.1/src/core/commandpool.cpp +++ b/lsfg-vk-v3.1/src/core/commandpool.cpp @@ -1,6 +1,11 @@ #include "core/commandpool.hpp" +#include "core/device.hpp" #include "lsfg.hpp" +#include + +#include + using namespace LSFG::Core; CommandPool::CommandPool(const Core::Device& device) { diff --git a/lsfg-vk-v3.1/src/core/descriptorpool.cpp b/lsfg-vk-v3.1/src/core/descriptorpool.cpp index 75eb627..ac08956 100644 --- a/lsfg-vk-v3.1/src/core/descriptorpool.cpp +++ b/lsfg-vk-v3.1/src/core/descriptorpool.cpp @@ -1,7 +1,12 @@ #include "core/descriptorpool.hpp" +#include "core/device.hpp" #include "lsfg.hpp" +#include + #include +#include +#include using namespace LSFG::Core; diff --git a/lsfg-vk-v3.1/src/core/descriptorset.cpp b/lsfg-vk-v3.1/src/core/descriptorset.cpp index c39454e..b8f95a2 100644 --- a/lsfg-vk-v3.1/src/core/descriptorset.cpp +++ b/lsfg-vk-v3.1/src/core/descriptorset.cpp @@ -1,6 +1,19 @@ #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 "lsfg.hpp" +#include + +#include +#include + using namespace LSFG::Core; DescriptorSet::DescriptorSet(const Core::Device& device, diff --git a/lsfg-vk-v3.1/src/core/device.cpp b/lsfg-vk-v3.1/src/core/device.cpp index 80594f5..b257517 100644 --- a/lsfg-vk-v3.1/src/core/device.cpp +++ b/lsfg-vk-v3.1/src/core/device.cpp @@ -1,6 +1,11 @@ #include "core/device.hpp" +#include "core/instance.hpp" #include "lsfg.hpp" +#include + +#include +#include #include #include diff --git a/lsfg-vk-v3.1/src/core/fence.cpp b/lsfg-vk-v3.1/src/core/fence.cpp index b543df0..52a05a2 100644 --- a/lsfg-vk-v3.1/src/core/fence.cpp +++ b/lsfg-vk-v3.1/src/core/fence.cpp @@ -1,6 +1,12 @@ #include "core/fence.hpp" +#include "core/device.hpp" #include "lsfg.hpp" +#include + +#include +#include + using namespace LSFG::Core; Fence::Fence(const Core::Device& device) { diff --git a/lsfg-vk-v3.1/src/core/image.cpp b/lsfg-vk-v3.1/src/core/image.cpp index 4affb03..c038373 100644 --- a/lsfg-vk-v3.1/src/core/image.cpp +++ b/lsfg-vk-v3.1/src/core/image.cpp @@ -1,6 +1,11 @@ #include "core/image.hpp" +#include "core/device.hpp" #include "lsfg.hpp" +#include + +#include +#include #include using namespace LSFG::Core; diff --git a/lsfg-vk-v3.1/src/core/instance.cpp b/lsfg-vk-v3.1/src/core/instance.cpp index c0e670f..29203fc 100644 --- a/lsfg-vk-v3.1/src/core/instance.cpp +++ b/lsfg-vk-v3.1/src/core/instance.cpp @@ -1,7 +1,10 @@ #include "core/instance.hpp" #include "lsfg.hpp" +#include +#include #include +#include using namespace LSFG::Core; diff --git a/lsfg-vk-v3.1/src/core/pipeline.cpp b/lsfg-vk-v3.1/src/core/pipeline.cpp index 9b2986f..cff1bd6 100644 --- a/lsfg-vk-v3.1/src/core/pipeline.cpp +++ b/lsfg-vk-v3.1/src/core/pipeline.cpp @@ -1,6 +1,13 @@ #include "core/pipeline.hpp" +#include "core/device.hpp" +#include "core/shadermodule.hpp" +#include "core/commandbuffer.hpp" #include "lsfg.hpp" +#include + +#include + using namespace LSFG::Core; Pipeline::Pipeline(const Core::Device& device, const ShaderModule& shader) { diff --git a/lsfg-vk-v3.1/src/core/sampler.cpp b/lsfg-vk-v3.1/src/core/sampler.cpp index 961d832..0b82a8f 100644 --- a/lsfg-vk-v3.1/src/core/sampler.cpp +++ b/lsfg-vk-v3.1/src/core/sampler.cpp @@ -1,7 +1,11 @@ #include "core/sampler.hpp" +#include "core/device.hpp" #include "lsfg.hpp" + #include +#include + using namespace LSFG::Core; Sampler::Sampler(const Core::Device& device, diff --git a/lsfg-vk-v3.1/src/core/semaphore.cpp b/lsfg-vk-v3.1/src/core/semaphore.cpp index 267c8f5..0ef4de9 100644 --- a/lsfg-vk-v3.1/src/core/semaphore.cpp +++ b/lsfg-vk-v3.1/src/core/semaphore.cpp @@ -1,6 +1,14 @@ #include "core/semaphore.hpp" +#include "core/device.hpp" #include "lsfg.hpp" +#include + +#include +#include +#include +#include + using namespace LSFG::Core; Semaphore::Semaphore(const Core::Device& device, std::optional initial) { diff --git a/lsfg-vk-v3.1/src/core/shadermodule.cpp b/lsfg-vk-v3.1/src/core/shadermodule.cpp index df9c2dd..50b36b2 100644 --- a/lsfg-vk-v3.1/src/core/shadermodule.cpp +++ b/lsfg-vk-v3.1/src/core/shadermodule.cpp @@ -1,6 +1,15 @@ #include "core/shadermodule.hpp" +#include "core/device.hpp" #include "lsfg.hpp" +#include + +#include +#include +#include +#include +#include + using namespace LSFG::Core; ShaderModule::ShaderModule(const Core::Device& device, const std::vector& code, diff --git a/lsfg-vk-v3.1/src/pool/resourcepool.cpp b/lsfg-vk-v3.1/src/pool/resourcepool.cpp index 30b5dbe..804217b 100644 --- a/lsfg-vk-v3.1/src/pool/resourcepool.cpp +++ b/lsfg-vk-v3.1/src/pool/resourcepool.cpp @@ -1,7 +1,12 @@ #include "pool/resourcepool.hpp" #include "core/buffer.hpp" +#include "core/device.hpp" +#include "core/sampler.hpp" + +#include #include +#include using namespace LSFG; using namespace LSFG::Pool; diff --git a/lsfg-vk-v3.1/src/pool/shaderpool.cpp b/lsfg-vk-v3.1/src/pool/shaderpool.cpp index be9af62..22b4246 100644 --- a/lsfg-vk-v3.1/src/pool/shaderpool.cpp +++ b/lsfg-vk-v3.1/src/pool/shaderpool.cpp @@ -1,7 +1,16 @@ #include "pool/shaderpool.hpp" +#include "core/shadermodule.hpp" +#include "core/device.hpp" +#include "core/pipeline.hpp" #include "utils/trans.hpp" +#include + +#include #include +#include +#include +#include using namespace LSFG; using namespace LSFG::Pool; diff --git a/lsfg-vk-v3.1/src/shaders/alpha.cpp b/lsfg-vk-v3.1/src/shaders/alpha.cpp index 198ed02..7eb3019 100644 --- a/lsfg-vk-v3.1/src/shaders/alpha.cpp +++ b/lsfg-vk-v3.1/src/shaders/alpha.cpp @@ -1,4 +1,13 @@ #include "shaders/alpha.hpp" +#include "utils/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include +#include +#include using namespace LSFG::Shaders; diff --git a/lsfg-vk-v3.1/src/shaders/beta.cpp b/lsfg-vk-v3.1/src/shaders/beta.cpp index 151ae3f..afa185a 100644 --- a/lsfg-vk-v3.1/src/shaders/beta.cpp +++ b/lsfg-vk-v3.1/src/shaders/beta.cpp @@ -1,4 +1,14 @@ #include "shaders/beta.hpp" +#include "utils/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include +#include +#include +#include using namespace LSFG::Shaders; diff --git a/lsfg-vk-v3.1/src/shaders/delta.cpp b/lsfg-vk-v3.1/src/shaders/delta.cpp index ca83cb0..548ab39 100644 --- a/lsfg-vk-v3.1/src/shaders/delta.cpp +++ b/lsfg-vk-v3.1/src/shaders/delta.cpp @@ -1,4 +1,15 @@ #include "shaders/delta.hpp" +#include "utils/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include +#include +#include +#include +#include using namespace LSFG::Shaders; diff --git a/lsfg-vk-v3.1/src/shaders/gamma.cpp b/lsfg-vk-v3.1/src/shaders/gamma.cpp index 0898f2c..647e31a 100644 --- a/lsfg-vk-v3.1/src/shaders/gamma.cpp +++ b/lsfg-vk-v3.1/src/shaders/gamma.cpp @@ -1,4 +1,15 @@ #include "shaders/gamma.hpp" +#include "utils/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include +#include +#include +#include +#include using namespace LSFG::Shaders; diff --git a/lsfg-vk-v3.1/src/shaders/generate.cpp b/lsfg-vk-v3.1/src/shaders/generate.cpp index 2e21b7f..3c18ed9 100644 --- a/lsfg-vk-v3.1/src/shaders/generate.cpp +++ b/lsfg-vk-v3.1/src/shaders/generate.cpp @@ -1,4 +1,14 @@ #include "shaders/generate.hpp" +#include "utils/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include +#include +#include +#include using namespace LSFG::Shaders; diff --git a/lsfg-vk-v3.1/src/shaders/mipmaps.cpp b/lsfg-vk-v3.1/src/shaders/mipmaps.cpp index 894c6f8..8bd4a40 100644 --- a/lsfg-vk-v3.1/src/shaders/mipmaps.cpp +++ b/lsfg-vk-v3.1/src/shaders/mipmaps.cpp @@ -1,4 +1,13 @@ #include "shaders/mipmaps.hpp" +#include "utils/utils.hpp" +#include "core/image.hpp" +#include "core/commandbuffer.hpp" + +#include + +#include +#include +#include using namespace LSFG::Shaders; diff --git a/lsfg-vk-v3.1/src/utils/trans.cpp b/lsfg-vk-v3.1/src/utils/trans.cpp index a730d96..25984e3 100644 --- a/lsfg-vk-v3.1/src/utils/trans.cpp +++ b/lsfg-vk-v3.1/src/utils/trans.cpp @@ -1,5 +1,8 @@ #include "utils/trans.hpp" +#include +#include + using namespace LSFG::Utils; std::vector Trans::translateShader(std::vector bytecode) { From 0d125a115dc5741489ab3d88d4dddbc672fe7561 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 16:11:46 +0200 Subject: [PATCH 104/253] fix validation headers and potential amd issues --- include/layer.hpp | 5 +++++ src/layer.cpp | 29 +++++++++++++++++++++++++++-- src/mini/commandbuffer.cpp | 1 + src/utils/utils.cpp | 4 ++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/include/layer.hpp b/include/layer.hpp index a7dd653..ad86679 100644 --- a/include/layer.hpp +++ b/include/layer.hpp @@ -25,6 +25,11 @@ namespace Layer { 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, diff --git a/src/layer.cpp b/src/layer.cpp index ea2003b..7bede45 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -16,6 +16,8 @@ namespace { PFN_vkCreateDevice next_vkCreateDevice{}; PFN_vkDestroyDevice next_vkDestroyDevice{}; + PFN_vkSetDeviceLoaderData next_vSetDeviceLoaderData{}; + PFN_vkGetInstanceProcAddr next_vkGetInstanceProcAddr{}; PFN_vkGetDeviceProcAddr next_vkGetDeviceProcAddr{}; @@ -152,11 +154,28 @@ namespace { return VK_ERROR_INITIALIZATION_FAILED; } - // advance link info (i don't really know what this does) - next_vkGetDeviceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetDeviceProcAddr; + next_vkGetDeviceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetDeviceProcAddr;; Log::debug("layer", "Next device proc addr: {:x}", reinterpret_cast(next_vkGetDeviceProcAddr)); + // find second layer creation info + auto* layerDesc2 = const_cast( + reinterpret_cast(pCreateInfo->pNext)); + while (layerDesc2 && (layerDesc2->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO + || layerDesc2->function != VK_LOADER_DATA_CALLBACK)) { + layerDesc2 = const_cast( + reinterpret_cast(layerDesc2->pNext)); + } + if (!layerDesc2) { + Log::error("layer", "No layer creation info found in pNext chain"); + return VK_ERROR_INITIALIZATION_FAILED; + } + + next_vSetDeviceLoaderData = layerDesc2->u.pfnSetDeviceLoaderData; + Log::debug("layer", "Next device loader data: {:x}", + reinterpret_cast(next_vSetDeviceLoaderData)); + + // advance link info (i don't really know what this does) layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; // create device @@ -317,6 +336,12 @@ void Layer::ovkDestroyDevice( reinterpret_cast(device)); } +VkResult Layer::ovkSetDeviceLoaderData(VkDevice device, void* object) { + Log::debug("vulkan", "vkSetDeviceLoaderData called for object {:x}", + reinterpret_cast(object)); + return next_vSetDeviceLoaderData(device, object); +} + PFN_vkVoidFunction Layer::ovkGetInstanceProcAddr( VkInstance instance, const char* pName) { diff --git a/src/mini/commandbuffer.cpp b/src/mini/commandbuffer.cpp index 5887f65..b8679b1 100644 --- a/src/mini/commandbuffer.cpp +++ b/src/mini/commandbuffer.cpp @@ -17,6 +17,7 @@ CommandBuffer::CommandBuffer(VkDevice device, const CommandPool& pool) { auto res = Layer::ovkAllocateCommandBuffers(device, &desc, &commandBufferHandle); if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE) throw LSFG::vulkan_error(res, "Unable to allocate command buffer"); + res = Layer::ovkSetDeviceLoaderData(device, commandBufferHandle); // store command buffer in shared ptr this->state = std::make_shared(CommandBufferState::Empty); diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 843acca..00322af 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -36,6 +36,10 @@ std::pair Utils::findQueue(VkDevice device, VkPhysicalDevice VkQueue queue{}; Layer::ovkGetDeviceQueue(device, *idx, 0, &queue); + auto res = Layer::ovkSetDeviceLoaderData(device, queue); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to set device loader data for queue"); + return { *idx, queue }; } From 659880ddde9f93a9af22561e186e93fda06eacc2 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 16:13:41 +0200 Subject: [PATCH 105/253] lsfg-vk-v3.1: pass format to context --- lsfg-vk-v3.1/include/shaders/generate.hpp | 2 +- lsfg-vk-v3.1/src/context.cpp | 2 +- lsfg-vk-v3.1/src/shaders/generate.cpp | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lsfg-vk-v3.1/include/shaders/generate.hpp b/lsfg-vk-v3.1/include/shaders/generate.hpp index a478172..34ccb56 100644 --- a/lsfg-vk-v3.1/include/shaders/generate.hpp +++ b/lsfg-vk-v3.1/include/shaders/generate.hpp @@ -37,7 +37,7 @@ namespace LSFG::Shaders { Generate(Vulkan& vk, Core::Image inImg1, Core::Image inImg2, Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, - const std::vector& fds); + const std::vector& fds, VkFormat format); /// /// Dispatch the shaderchain. diff --git a/lsfg-vk-v3.1/src/context.cpp b/lsfg-vk-v3.1/src/context.cpp index eb803cb..4d579db 100644 --- a/lsfg-vk-v3.1/src/context.cpp +++ b/lsfg-vk-v3.1/src/context.cpp @@ -56,7 +56,7 @@ Context::Context(Vulkan& vk, this->gamma.at(6).getOutImage(), this->delta.at(2).getOutImage1(), this->delta.at(2).getOutImage2(), - outN); + outN, format); } void Context::present(Vulkan& vk, diff --git a/lsfg-vk-v3.1/src/shaders/generate.cpp b/lsfg-vk-v3.1/src/shaders/generate.cpp index 3c18ed9..24349a3 100644 --- a/lsfg-vk-v3.1/src/shaders/generate.cpp +++ b/lsfg-vk-v3.1/src/shaders/generate.cpp @@ -15,7 +15,7 @@ using namespace LSFG::Shaders; Generate::Generate(Vulkan& vk, Core::Image inImg1, Core::Image inImg2, Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, - const std::vector& fds) + const std::vector& fds, VkFormat format) : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)), inImg3(std::move(inImg3)), inImg4(std::move(inImg4)), inImg5(std::move(inImg5)) { @@ -33,8 +33,7 @@ Generate::Generate(Vulkan& vk, // 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, - VK_FORMAT_R8G8B8A8_UNORM, + this->outImgs.emplace_back(vk.device, extent, format, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT, fds.at(i)); From e4c54450d35a852b9f2f774344530b4eee03c9e6 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 16:14:18 +0200 Subject: [PATCH 106/253] lsfg-vk-v3.1: fix synchronization --- lsfg-vk-v3.1/src/context.cpp | 45 +++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/lsfg-vk-v3.1/src/context.cpp b/lsfg-vk-v3.1/src/context.cpp index 4d579db..fb8b62b 100644 --- a/lsfg-vk-v3.1/src/context.cpp +++ b/lsfg-vk-v3.1/src/context.cpp @@ -26,10 +26,10 @@ Context::Context(Vulkan& vk, // prepare render data for (size_t i = 0; i < 8; i++) { auto& data = this->data.at(i); - data.internalSemaphores.resize(outN.size()); - data.outSemaphores.resize(outN.size()); - data.completionFences.resize(outN.size()); - data.cmdBuffers2.resize(outN.size()); + data.internalSemaphores.resize(vk.generationCount); + data.outSemaphores.resize(vk.generationCount); + data.completionFences.resize(vk.generationCount); + data.cmdBuffers2.resize(vk.generationCount); } // create shader chains @@ -68,11 +68,12 @@ void Context::present(Vulkan& vk, 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 data.inSemaphore = Core::Semaphore(vk.device, inSem); - for (size_t i = 0; i < outSem.size(); i++) - data.internalSemaphores.at(i) = Core::Semaphore(vk.device, outSem.at(i)); + 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(); @@ -88,22 +89,28 @@ void Context::present(Vulkan& vk, data.internalSemaphores, std::nullopt); // 2. generate intermediary frames - for (size_t i = 0; i < 7; i++) { - data.outSemaphores.at(i) = Core::Semaphore(vk.device, outSem.at(i)); - data.completionFences.at(i) = Core::Fence(vk.device); + for (size_t pass = 0; pass < vk.generationCount; pass++) { + auto& internalSemaphore = data.internalSemaphores.at(pass); + auto& outSemaphore = data.outSemaphores.at(pass); + outSemaphore = Core::Semaphore(vk.device, outSem.at(pass)); + auto& completionFence = data.completionFences.at(pass); + completionFence = Core::Fence(vk.device); - data.cmdBuffers2.at(i) = Core::CommandBuffer(vk.device, vk.commandPool); - data.cmdBuffers2.at(i).begin(); + auto& buf2 = data.cmdBuffers2.at(pass); + buf2 = Core::CommandBuffer(vk.device, vk.commandPool); + buf2.begin(); - this->gamma.at(i).Dispatch(data.cmdBuffers2.at(i), this->frameIdx, i); - if (i >= 4) - this->delta.at(i - 4).Dispatch(data.cmdBuffers2.at(i), this->frameIdx, i); - this->generate.Dispatch(data.cmdBuffers2.at(i), this->frameIdx, i); + 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); - data.cmdBuffers2.at(i).end(); - data.cmdBuffers2.at(i).submit(vk.device.getComputeQueue(), std::nullopt, - { data.internalSemaphores.at(i) }, std::nullopt, - data.outSemaphores, std::nullopt); + buf2.end(); + buf2.submit(vk.device.getComputeQueue(), completionFence, + { internalSemaphore }, std::nullopt, + { outSemaphore }, std::nullopt); } this->frameIdx++; From 53d73ce610ab17100236af24b6e6c1e4caa3036f Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 16:14:32 +0200 Subject: [PATCH 107/253] lsfg-vk-v3.1: fix shader inputs --- lsfg-vk-v3.1/src/shaders/generate.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lsfg-vk-v3.1/src/shaders/generate.cpp b/lsfg-vk-v3.1/src/shaders/generate.cpp index 24349a3..20a1098 100644 --- a/lsfg-vk-v3.1/src/shaders/generate.cpp +++ b/lsfg-vk-v3.1/src/shaders/generate.cpp @@ -42,14 +42,14 @@ Generate::Generate(Vulkan& vk, auto& pass = this->passes.emplace_back(); pass.buffer = vk.resources.getBuffer(vk.device, static_cast(i + 1) / static_cast(vk.generationCount + 1)); - for (size_t i = 0; i < 2; i++) { - pass.descriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + for (size_t j = 0; j < 2; j++) { + pass.descriptorSet.at(j) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModule); - pass.descriptorSet.at(i).update(vk.device) + 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, i == 0 ? this->inImg1 : this->inImg2) - .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, i == 0 ? this->inImg2 : this->inImg1) + .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) From 7e1d46189ec60a415e4f06e375dd26503b93d0f2 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 16:15:07 +0200 Subject: [PATCH 108/253] lsfg-vk-v3.1: move out shader extraction to surrounding project --- include/extract/extract.hpp | 26 ++++++ include/extract/trans.hpp | 16 ++++ lsfg-vk-v3.1/include/utils/trans.hpp | 16 ---- lsfg-vk-v3.1/src/pool/shaderpool.cpp | 9 +-- lsfg-vk-v3.1/src/utils/trans.cpp | 31 -------- src/extract/extract.cpp | 114 +++++++++++++++++++++++++++ src/extract/trans.cpp | 35 ++++++++ 7 files changed, 193 insertions(+), 54 deletions(-) create mode 100644 include/extract/extract.hpp create mode 100644 include/extract/trans.hpp delete mode 100644 lsfg-vk-v3.1/include/utils/trans.hpp delete mode 100644 lsfg-vk-v3.1/src/utils/trans.cpp create mode 100644 src/extract/extract.cpp create mode 100644 src/extract/trans.cpp diff --git a/include/extract/extract.hpp b/include/extract/extract.hpp new file mode 100644 index 0000000..812a2bd --- /dev/null +++ b/include/extract/extract.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +namespace Extract { + + /// + /// Extract all known shaders. + /// + /// @throws std::runtime_error if shader extraction fails. + /// + void extractShaders(); + + /// + /// Get a shader by name. + /// + /// @param name The name of the shader to get. + /// @return The shader bytecode. + /// + /// @throws std::runtime_error if the shader is not found. + /// + std::vector getShader(const std::string& name); + +} diff --git a/include/extract/trans.hpp b/include/extract/trans.hpp new file mode 100644 index 0000000..9da1df9 --- /dev/null +++ b/include/extract/trans.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace Extract { + + /// + /// Translate DXBC bytecode to SPIR-V bytecode. + /// + /// @param bytecode The DXBC bytecode to translate. + /// @return The translated SPIR-V bytecode. + /// + std::vector translateShader(std::vector bytecode); + +} diff --git a/lsfg-vk-v3.1/include/utils/trans.hpp b/lsfg-vk-v3.1/include/utils/trans.hpp deleted file mode 100644 index 71e28be..0000000 --- a/lsfg-vk-v3.1/include/utils/trans.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -namespace LSFG::Utils::Trans { - - /// - /// Translate shader bytecode to SPIR-V. - /// - /// @param bytecode The shader bytecode to translate. - /// @return A vector containing the translated SPIR-V bytecode. - /// - [[nodiscard]] std::vector translateShader(std::vector bytecode); - -} diff --git a/lsfg-vk-v3.1/src/pool/shaderpool.cpp b/lsfg-vk-v3.1/src/pool/shaderpool.cpp index 22b4246..7aaf00b 100644 --- a/lsfg-vk-v3.1/src/pool/shaderpool.cpp +++ b/lsfg-vk-v3.1/src/pool/shaderpool.cpp @@ -2,7 +2,6 @@ #include "core/shadermodule.hpp" #include "core/device.hpp" #include "core/pipeline.hpp" -#include "utils/trans.hpp" #include @@ -27,12 +26,8 @@ Core::ShaderModule ShaderPool::getShader( if (bytecode.empty()) throw std::runtime_error("Shader code is empty: " + name); - // create the translated shader module - auto spirvBytecode = Utils::Trans::translateShader(bytecode); - if (spirvBytecode.empty()) - throw std::runtime_error("Shader code translation failed: " + name); - - Core::ShaderModule shader(device, spirvBytecode, types); + // create the shader module + Core::ShaderModule shader(device, bytecode, types); shaders[name] = shader; return shader; } diff --git a/lsfg-vk-v3.1/src/utils/trans.cpp b/lsfg-vk-v3.1/src/utils/trans.cpp deleted file mode 100644 index 25984e3..0000000 --- a/lsfg-vk-v3.1/src/utils/trans.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "utils/trans.hpp" - -#include -#include - -using namespace LSFG::Utils; - -std::vector Trans::translateShader(std::vector bytecode) { - return bytecode; // on windows we expect the bytecode to be spir-v -// // compile the shader -// dxvk::DxbcReader reader(reinterpret_cast(bytecode.data()), bytecode.size()); -// dxvk::DxbcModule module(reader); -// const dxvk::DxbcModuleInfo info{}; -// auto shader = module.compile(info, "CS"); - -// // extract spir-v from d3d11 shader -// auto code = shader->getRawCode(); - -// // patch binding offsets -// #pragma clang diagnostic push -// #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" -// for (size_t i = 0; i < shader->m_bindingOffsets.size(); i++) -// code.data()[shader->m_bindingOffsets.at(i).bindingOffset] = static_cast(i); // NOLINT -// #pragma clang diagnostic pop - -// std::vector spirvBytecode(code.size()); -// std::copy_n(reinterpret_cast(code.data()), -// code.size(), spirvBytecode.data()); -// return spirvBytecode; -// #endif -} diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp new file mode 100644 index 0000000..2ba0c67 --- /dev/null +++ b/src/extract/extract.cpp @@ -0,0 +1,114 @@ +#include "extract/extract.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace Extract; + +const std::unordered_map nameHashTable = {{ + { "mipmaps", 0xe365474d }, + { "alpha[0]", 0xf37c8aa8 }, + { "alpha[1]", 0xeb7a52d4 }, + { "alpha[2]", 0x8901788e }, + { "alpha[3]", 0xa06a5e36 }, + { "beta[0]", 0x63c16b89 }, + { "beta[1]", 0xe3967ed5 }, + { "beta[2]", 0x570085ee }, + { "beta[3]", 0x4f4530db }, + { "beta[4]", 0x39727389 }, + { "gamma[0]", 0x94c4edf6 }, + { "gamma[1]", 0xf4e32702 }, + { "gamma[2]", 0xa3dc56fc }, + { "gamma[3]", 0x8b5ed8f6 }, + { "gamma[4]", 0x1cbf3c4d }, + { "delta[0]", 0x94c4edf6 }, + { "delta[1]", 0xacfd805b }, + { "delta[2]", 0x891dc48b }, + { "delta[3]", 0x98536d9d }, + { "delta[4]", 0x8e3f2155 }, + { "delta[5]", 0x8f0e70a1 }, + { "delta[6]", 0xd5eca8f1 }, + { "delta[7]", 0xa9e54e37 }, + { "delta[8]", 0x1dee8b84 }, + { "delta[9]", 0x1576c3f5 }, + { "generate", 0x5c040bd5 } +}}; + +namespace { + std::unordered_map> shaderData; + + uint32_t fnv1a_hash(const std::vector& data) { + uint32_t hash = 0x811C9DC5; + for (auto byte : data) { + hash ^= byte; + hash *= 0x01000193; + } + return hash; + } + + int on_resource(void* data, const peparse::resource& res) { // NOLINT + if (res.type != peparse::RT_RCDATA || res.buf == nullptr || res.buf->bufLen <= 0) + return 0; + std::vector resource_data(res.buf->bufLen); + std::copy_n(res.buf->buf, res.buf->bufLen, resource_data.data()); + + const uint32_t hash = fnv1a_hash(resource_data); + shaderData[hash] = resource_data; + return 0; + } +} + +void Extract::extractShaders() { + if (shaderData.size() > 0) + return; + + // find path to dll (absolutely beautiful code) + char* dllPath = getenv("LSFG_DLL_PATH"); + std::string dllPathStr; + if (dllPath && *dllPath != '\0') { + dllPathStr = std::string(dllPath); + } else { + const char* dataDir = getenv("XDG_DATA_HOME"); + if (dataDir && *dataDir != '\0') { + dllPathStr = std::string(dataDir) + + "/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; + } else { + const char* homeDir = getenv("HOME"); + if (homeDir && *homeDir != '\0') { + dllPathStr = std::string(homeDir) + + "/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; + } else { + dllPathStr = "Lossless.dll"; + } + } + } + + // parse the dll + peparse::parsed_pe* dll = peparse::ParsePEFromFile(dllPathStr.c_str()); + if (!dll) + throw std::runtime_error("Unable to read Lossless.dll, is it installed?"); + peparse::IterRsrc(dll, on_resource, nullptr); + peparse::DestructParsedPE(dll); +} + +std::vector Extract::getShader(const std::string& name) { + if (shaderData.empty()) + throw std::runtime_error("Shaders are not loaded."); + + auto hit = nameHashTable.find(name); + if (hit == nameHashTable.end()) + throw std::runtime_error("Shader not found: " + name); + + auto sit = shaderData.find(hit->second); + if (sit == shaderData.end()) + throw std::runtime_error("Shader not found: " + name); + + return sit->second; +} diff --git a/src/extract/trans.cpp b/src/extract/trans.cpp new file mode 100644 index 0000000..badb34c --- /dev/null +++ b/src/extract/trans.cpp @@ -0,0 +1,35 @@ +#include "extract/trans.hpp" + +#include +#include +#include + +#include +#include + +using namespace Extract; + +std::vector Extract::translateShader(std::vector bytecode) { + // compile the shader + dxvk::DxbcReader reader(reinterpret_cast(bytecode.data()), bytecode.size()); + dxvk::DxbcModule module(reader); + const dxvk::DxbcModuleInfo info{}; + auto shader = module.compile(info, "CS"); + + // extract spir-v from d3d11 shader + auto code = shader->getRawCode(); + + // patch binding offsets +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + for (size_t i = 0; i < shader->m_bindingOffsets.size(); i++) + code.data()[shader->m_bindingOffsets.at(i).bindingOffset] // NOLINT + = static_cast(i); +#pragma clang diagnostic pop + + // return the new bytecode + std::vector spirvBytecode(code.size()); + std::copy_n(reinterpret_cast(code.data()), + code.size(), spirvBytecode.data()); + return spirvBytecode; +} From 3b302e63d83eeb88a325e9d368283d5832f88f34 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 16:16:25 +0200 Subject: [PATCH 109/253] implement lsfg 3.1 into main project fixes #39 fixes #40 fixes #43 --- CMakeLists.txt | 74 +++++++++++++++++++++---------------- lsfg-vk-v3.1/CMakeLists.txt | 1 - src/context.cpp | 33 +++++++++++++---- 3 files changed, 68 insertions(+), 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 23c6afb..ade9f38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,62 +1,72 @@ cmake_minimum_required(VERSION 3.29) -# cmake options -set(CMAKE_CXX_COMPILER clang++) -set(CMAKE_CXX_CLANG_TIDY clang-tidy) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set(CMAKE_SKIP_RPATH ON) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - if(CMAKE_BUILD_TYPE STREQUAL "Release") add_definitions("-DLSFG_NO_DEBUG") endif() # subprojects +set(CMAKE_SKIP_RPATH ON) + include(cmake/FetchDXVK.cmake) include(cmake/FetchPeParse.cmake) -add_subdirectory(lsfg-vk-gen) +add_subdirectory(lsfg-vk-v3.1) # main project project(lsfg-vk VERSION 0.0.1 - DESCRIPTION "lsfg-vk: LSFG on Linux through Vulkan" + DESCRIPTION "LSFG on Linux" LANGUAGES CXX) file(GLOB SOURCES - "src/utils/*.cpp" + "src/extract/*.cpp" "src/mini/*.cpp" + "src/utils/*.cpp" "src/*.cpp" ) add_library(lsfg-vk SHARED ${SOURCES}) +# 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 lsfg-vk-gen vulkan) -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 - # required for vulkan - -Wno-cast-function-type-strict -) + PRIVATE lsfg-vk-v3.1 peparse dxvk vulkan) +# 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" diff --git a/lsfg-vk-v3.1/CMakeLists.txt b/lsfg-vk-v3.1/CMakeLists.txt index dad352d..d6cb670 100644 --- a/lsfg-vk-v3.1/CMakeLists.txt +++ b/lsfg-vk-v3.1/CMakeLists.txt @@ -36,7 +36,6 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") endif() if(LSFGVK_EXCESS_DEBUG) - message(STATUS "LSFGVK_EXCESS_DEBUG is only compatible with clang") target_compile_options(lsfg-vk-v3.1 PRIVATE -Weverything # disable compat c++ flags diff --git a/src/context.cpp b/src/context.cpp index 9b5d05e..91b0c2a 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -1,4 +1,6 @@ #include "context.hpp" +#include "extract/extract.hpp" +#include "extract/trans.hpp" #include "layer.hpp" #include "utils/log.hpp" #include "utils/utils.hpp" @@ -12,10 +14,20 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, VkExtent2D extent, const std::vector& swapchainImages) : swapchain(swapchain), swapchainImages(swapchainImages), extent(extent) { + // read environment variables + const char* lsfgFlowScaleStr = getenv("LSFG_FLOW_SCALE"); + const float flowScale = lsfgFlowScaleStr + ? std::stof(lsfgFlowScaleStr) + : 1.0F; + + const char* lsfgHdrStr = getenv("LSFG_HDR"); + const bool isHdr = lsfgHdrStr + ? *lsfgHdrStr == '1' + : false; // we could take the format from the swapchain, // but honestly this is safer. - const VkFormat format = getenv("LSFG_HDR") == nullptr + const VkFormat format = isHdr ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_R16G16B16A16_SFLOAT; @@ -53,19 +65,26 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, } // initialize lsfg - const uint64_t deviceUUID = Utils::getDeviceUUID(info.physicalDevice); - setenv("LSFG_DEVICE_UUID", std::to_string(deviceUUID).c_str(), 1); - Log::debug("context", "(entering LSFG initialization)"); setenv("DISABLE_LSFG", "1", 1); - LSFG::initialize(); + Extract::extractShaders(); + LSFG::initialize( + Utils::getDeviceUUID(info.physicalDevice), + isHdr, 1.0F / flowScale, info.frameGen, + [](const std::string& name) { + auto dxbc = Extract::getShader(name); + auto spirv = Extract::translateShader(dxbc); + return spirv; + } + ); unsetenv("DISABLE_LSFG"); Log::debug("context", "(exiting LSFG initialization)"); + // create lsfg context Log::debug("context", "(entering LSFG context creation)"); this->lsfgCtxId = std::shared_ptr( - new int32_t(LSFG::createContext(extent.width, extent.height, - frame_0_fd, frame_1_fd, out_n_fds)), + new int32_t(LSFG::createContext(frame_0_fd, frame_1_fd, out_n_fds, + extent, format)), [](const int32_t* id) { Log::info("context", "(entering LSFG context deletion with id: {})", *id); From 071980abec97c2d59c6fdaf2f58a2d393fd2cb34 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 16:32:47 +0200 Subject: [PATCH 110/253] limit flow scale --- src/context.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/context.cpp b/src/context.cpp index 91b0c2a..69b8544 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -16,9 +16,10 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, extent(extent) { // read environment variables const char* lsfgFlowScaleStr = getenv("LSFG_FLOW_SCALE"); - const float flowScale = lsfgFlowScaleStr + float flowScale = lsfgFlowScaleStr ? std::stof(lsfgFlowScaleStr) : 1.0F; + flowScale = std::max(0.3F, std::min(flowScale, 1.0F)); const char* lsfgHdrStr = getenv("LSFG_HDR"); const bool isHdr = lsfgHdrStr From 92ca8827dbdecfb561b856f3dbe764716ad8c4c6 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 16:33:42 +0200 Subject: [PATCH 111/253] initial round of code cleanup --- src/extract/extract.cpp | 4 ++-- src/layer.cpp | 2 +- src/mini/commandbuffer.cpp | 2 ++ src/utils/utils.cpp | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index 2ba0c67..598a414 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -53,7 +53,7 @@ namespace { return hash; } - int on_resource(void* data, const peparse::resource& res) { // NOLINT + int on_resource(void*, const peparse::resource& res) { // NOLINT if (res.type != peparse::RT_RCDATA || res.buf == nullptr || res.buf->bufLen <= 0) return 0; std::vector resource_data(res.buf->bufLen); @@ -66,7 +66,7 @@ namespace { } void Extract::extractShaders() { - if (shaderData.size() > 0) + if (!shaderData.empty()) return; // find path to dll (absolutely beautiful code) diff --git a/src/layer.cpp b/src/layer.cpp index 7bede45..722e2f1 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -154,7 +154,7 @@ namespace { return VK_ERROR_INITIALIZATION_FAILED; } - next_vkGetDeviceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetDeviceProcAddr;; + next_vkGetDeviceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetDeviceProcAddr; Log::debug("layer", "Next device proc addr: {:x}", reinterpret_cast(next_vkGetDeviceProcAddr)); diff --git a/src/mini/commandbuffer.cpp b/src/mini/commandbuffer.cpp index b8679b1..63ca204 100644 --- a/src/mini/commandbuffer.cpp +++ b/src/mini/commandbuffer.cpp @@ -18,6 +18,8 @@ CommandBuffer::CommandBuffer(VkDevice device, const CommandPool& pool) { if (res != VK_SUCCESS || commandBufferHandle == VK_NULL_HANDLE) throw LSFG::vulkan_error(res, "Unable to allocate command buffer"); res = Layer::ovkSetDeviceLoaderData(device, commandBufferHandle); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unable to set device loader data for command buffer"); // store command buffer in shared ptr this->state = std::make_shared(CommandBufferState::Empty); diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 00322af..8ff3d82 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -58,7 +58,7 @@ std::vector Utils::addExtensions(const char* const* extensions, siz for (const auto& e : requiredExtensions) { auto it = std::ranges::find_if(ext, [e](const char* extName) { - return std::strcmp(extName, e) == 0; + return std::string(extName) == std::string(e); }); if (it == ext.end()) { Log::debug("hooks-init", "Adding extension: {}", e); From f306c48e6dcc2cc94b5f0b9236040c83251dd11a Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 17:01:17 +0200 Subject: [PATCH 112/253] cleanup headers supersedes #32 fixes #42 --- .clang-tidy | 3 ++- include/context.hpp | 9 +++------ include/hooks.hpp | 6 ++---- include/layer.hpp | 7 +++---- include/mini/commandbuffer.hpp | 6 +----- include/mini/commandpool.hpp | 6 ++---- include/mini/image.hpp | 5 +---- include/mini/semaphore.hpp | 5 +---- include/utils/log.hpp | 15 +++++++-------- include/utils/utils.hpp | 7 +++---- lsfg-vk-v3.1/.clang-tidy | 1 + lsfg-vk-v3.1/include/shaders/generate.hpp | 2 ++ lsfg-vk-v3.1/src/pool/resourcepool.cpp | 2 +- src/context.cpp | 16 +++++++++++----- src/extract/extract.cpp | 4 ++-- src/extract/trans.cpp | 2 ++ src/hooks.cpp | 9 +++++++-- src/layer.cpp | 3 +-- src/mini/commandbuffer.cpp | 7 +++++++ src/mini/commandpool.cpp | 4 ++++ src/mini/image.cpp | 3 +++ src/mini/semaphore.cpp | 3 +++ src/utils/log.cpp | 9 ++++++++- src/utils/utils.cpp | 7 +++++-- 24 files changed, 82 insertions(+), 59 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index b8e891c..298590b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -8,13 +8,13 @@ Checks: - "readability-*" - "bugprone-*" - "misc-*" -- "-misc-include-cleaner" - "-readability-braces-around-statements" - "-readability-function-cognitive-complexity" - "-readability-identifier-length" - "-readability-implicit-bool-conversion" - "-readability-magic-numbers" - "-readability-math-missing-parentheses" +- "-readability-named-parameter" - "-bugprone-easily-swappable-parameters" # configure modernization - "modernize-*" @@ -24,3 +24,4 @@ Checks: - "-cppcoreguidelines-avoid-magic-numbers" - "-cppcoreguidelines-pro-type-reinterpret-cast" # allows reinterpret_cast - "-cppcoreguidelines-avoid-non-const-global-variables" +- "-cppcoreguidelines-pro-type-union-access" diff --git a/include/context.hpp b/include/context.hpp index fbd2481..3438e48 100644 --- a/include/context.hpp +++ b/include/context.hpp @@ -1,5 +1,4 @@ -#ifndef CONTEXT_HPP -#define CONTEXT_HPP +#pragma once #include "hooks.hpp" #include "mini/commandbuffer.hpp" @@ -7,11 +6,11 @@ #include "mini/image.hpp" #include "mini/semaphore.hpp" +#include + #include #include #include -#include - #include /// @@ -79,5 +78,3 @@ private: }; // data for a single render pass std::array passInfos; // allocate 8 because why not }; - -#endif // CONTEXT_HPP diff --git a/include/hooks.hpp b/include/hooks.hpp index 8e66dc5..487686e 100644 --- a/include/hooks.hpp +++ b/include/hooks.hpp @@ -1,8 +1,8 @@ -#ifndef HOOKS_HPP -#define HOOKS_HPP +#pragma once #include +#include #include #include #include @@ -21,5 +21,3 @@ namespace Hooks { extern std::unordered_map hooks; } - -#endif // HOOKS_HPP diff --git a/include/layer.hpp b/include/layer.hpp index ad86679..8cb3163 100644 --- a/include/layer.hpp +++ b/include/layer.hpp @@ -1,8 +1,9 @@ -#ifndef LAYER_HPP -#define LAYER_HPP +#pragma once #include +#include + namespace Layer { /// Call to the original vkCreateInstance function. VkResult ovkCreateInstance( @@ -216,5 +217,3 @@ namespace 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); - -#endif // LAYER_HPP diff --git a/include/mini/commandbuffer.hpp b/include/mini/commandbuffer.hpp index 6b0e4f2..2189596 100644 --- a/include/mini/commandbuffer.hpp +++ b/include/mini/commandbuffer.hpp @@ -1,8 +1,6 @@ -#ifndef COMMANDBUFFER_HPP -#define COMMANDBUFFER_HPP +#pragma once #include "mini/commandpool.hpp" -#include "mini/semaphore.hpp" #include @@ -91,5 +89,3 @@ namespace Mini { }; } - -#endif // COMMANDBUFFER_HPP diff --git a/include/mini/commandpool.hpp b/include/mini/commandpool.hpp index 6860ba0..08c0295 100644 --- a/include/mini/commandpool.hpp +++ b/include/mini/commandpool.hpp @@ -1,8 +1,8 @@ -#ifndef COMMANDPOOL_HPP -#define COMMANDPOOL_HPP +#pragma once #include +#include #include namespace Mini { @@ -40,5 +40,3 @@ namespace Mini { }; } - -#endif // COMMANDPOOL_HPP diff --git a/include/mini/image.hpp b/include/mini/image.hpp index a1de0cf..4c159cd 100644 --- a/include/mini/image.hpp +++ b/include/mini/image.hpp @@ -1,5 +1,4 @@ -#ifndef IMAGE_HPP -#define IMAGE_HPP +#pragma once #include @@ -59,5 +58,3 @@ namespace Mini { }; } - -#endif // IMAGE_HPP diff --git a/include/mini/semaphore.hpp b/include/mini/semaphore.hpp index 833ce35..03df4d9 100644 --- a/include/mini/semaphore.hpp +++ b/include/mini/semaphore.hpp @@ -1,5 +1,4 @@ -#ifndef SEMAPHORE_HPP -#define SEMAPHORE_HPP +#pragma once #include @@ -49,5 +48,3 @@ namespace Mini { }; } - -#endif // SEMAPHORE_HPP diff --git a/include/utils/log.hpp b/include/utils/log.hpp index 0c29623..6576aa5 100644 --- a/include/utils/log.hpp +++ b/include/utils/log.hpp @@ -1,11 +1,12 @@ -#ifndef LOG_HPP -#define LOG_HPP +#pragma once #include #include #include #include #include +#include +#include namespace Log { @@ -26,10 +27,10 @@ namespace Log { std::format_string fmt, Args&&... args) { Internal::setup(); - std::string prefix = std::format("lsfg-vk({}): ", module); - std::string message = std::format(fmt, std::forward(args)...); + const std::string prefix = std::format("lsfg-vk({}): ", module); + const std::string message = std::format(fmt, std::forward(args)...); - std::lock_guard lock(Internal::logMutex); + const std::lock_guard lock(Internal::logMutex); std::cerr << color << prefix << message << "\033[0m" << '\n'; if (Internal::logFile.is_open()) { Internal::logFile << prefix << message << '\n'; @@ -60,7 +61,7 @@ namespace Log { #ifdef LSFG_NO_DEBUG template -void debug(std::string_view, std::format_string, Args&&...) {} // NOLINT +void debug(std::string_view, std::format_string, Args&&...) {} #else template void debug(std::string_view module, std::format_string fmt, Args&&... args) { @@ -71,5 +72,3 @@ void debug(std::string_view, std::format_string, Args&&...) {} // NOLIN #endif } - -#endif // LOG_HPP diff --git a/include/utils/utils.hpp b/include/utils/utils.hpp index e40c85c..7094cbe 100644 --- a/include/utils/utils.hpp +++ b/include/utils/utils.hpp @@ -1,8 +1,9 @@ -#ifndef UTILS_HPP -#define UTILS_HPP +#pragma once #include +#include +#include #include #include @@ -59,5 +60,3 @@ namespace Utils { bool makeSrcPresentable, bool makeDstPresentable); } - -#endif // UTILS_HPP diff --git a/lsfg-vk-v3.1/.clang-tidy b/lsfg-vk-v3.1/.clang-tidy index 5ac5468..cf513e9 100644 --- a/lsfg-vk-v3.1/.clang-tidy +++ b/lsfg-vk-v3.1/.clang-tidy @@ -23,3 +23,4 @@ Checks: - "-cppcoreguidelines-avoid-magic-numbers" - "-cppcoreguidelines-pro-type-reinterpret-cast" # allows reinterpret_cast - "-cppcoreguidelines-avoid-non-const-global-variables" +- "-cppcoreguidelines-pro-type-union-access" diff --git a/lsfg-vk-v3.1/include/shaders/generate.hpp b/lsfg-vk-v3.1/include/shaders/generate.hpp index 34ccb56..1a6ecb7 100644 --- a/lsfg-vk-v3.1/include/shaders/generate.hpp +++ b/lsfg-vk-v3.1/include/shaders/generate.hpp @@ -9,6 +9,8 @@ #include "core/shadermodule.hpp" #include "utils/utils.hpp" +#include + #include #include #include diff --git a/lsfg-vk-v3.1/src/pool/resourcepool.cpp b/lsfg-vk-v3.1/src/pool/resourcepool.cpp index 804217b..8180945 100644 --- a/lsfg-vk-v3.1/src/pool/resourcepool.cpp +++ b/lsfg-vk-v3.1/src/pool/resourcepool.cpp @@ -29,7 +29,7 @@ Core::Buffer ResourcePool::getBuffer( uint64_t hash = 0; const union { float f; uint32_t i; } u{ .f = timestamp }; - hash |= u.i; // NOLINT + hash |= u.i; hash |= static_cast(firstIter) << 32; hash |= static_cast(firstIterS) << 33; diff --git a/src/context.cpp b/src/context.cpp index 69b8544..ebbc269 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -1,13 +1,19 @@ #include "context.hpp" #include "extract/extract.hpp" #include "extract/trans.hpp" -#include "layer.hpp" -#include "utils/log.hpp" #include "utils/utils.hpp" +#include "utils/log.hpp" +#include "hooks.hpp" +#include "layer.hpp" +#include #include +#include +#include #include +#include +#include #include LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, @@ -19,7 +25,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, float flowScale = lsfgFlowScaleStr ? std::stof(lsfgFlowScaleStr) : 1.0F; - flowScale = std::max(0.3F, std::min(flowScale, 1.0F)); + flowScale = std::max(0.25F, std::min(flowScale, 1.0F)); const char* lsfgHdrStr = getenv("LSFG_HDR"); const bool isHdr = lsfgHdrStr @@ -67,7 +73,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, // initialize lsfg Log::debug("context", "(entering LSFG initialization)"); - setenv("DISABLE_LSFG", "1", 1); + setenv("DISABLE_LSFG", "1", 1); // NOLINT Extract::extractShaders(); LSFG::initialize( Utils::getDeviceUUID(info.physicalDevice), @@ -78,7 +84,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, return spirv; } ); - unsetenv("DISABLE_LSFG"); + unsetenv("DISABLE_LSFG"); // NOLINT Log::debug("context", "(exiting LSFG initialization)"); // create lsfg context diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index 598a414..5706dd7 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -1,8 +1,8 @@ #include "extract/extract.hpp" -#include #include +#include #include #include #include @@ -53,7 +53,7 @@ namespace { return hash; } - int on_resource(void*, const peparse::resource& res) { // NOLINT + int on_resource(void*, const peparse::resource& res) { if (res.type != peparse::RT_RCDATA || res.buf == nullptr || res.buf->bufLen <= 0) return 0; std::vector resource_data(res.buf->bufLen); diff --git a/src/extract/trans.cpp b/src/extract/trans.cpp index badb34c..f9860f9 100644 --- a/src/extract/trans.cpp +++ b/src/extract/trans.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include diff --git a/src/hooks.cpp b/src/hooks.cpp index 81dceaa..826c817 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -5,10 +5,15 @@ #include "utils/utils.hpp" #include +#include +#include +#include +#include +#include #include #include -#include +#include using namespace Hooks; @@ -86,7 +91,7 @@ namespace { VkResult myvkCreateDevicePost( VkPhysicalDevice physicalDevice, VkDeviceCreateInfo* pCreateInfo, - const VkAllocationCallbacks*, // NOLINT + const VkAllocationCallbacks*, VkDevice* pDevice) { // store device info Log::debug("hooks", "Creating device info for device: {:x}", diff --git a/src/layer.cpp b/src/layer.cpp index 722e2f1..6a5e915 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -2,12 +2,11 @@ #include "hooks.hpp" #include "utils/log.hpp" -#include #include +#include #include #include -#include namespace { PFN_vkCreateInstance next_vkCreateInstance{}; diff --git a/src/mini/commandbuffer.cpp b/src/mini/commandbuffer.cpp index 63ca204..d7eb230 100644 --- a/src/mini/commandbuffer.cpp +++ b/src/mini/commandbuffer.cpp @@ -1,8 +1,15 @@ #include "mini/commandbuffer.hpp" +#include "mini/commandpool.hpp" #include "layer.hpp" +#include #include +#include +#include +#include +#include + using namespace Mini; CommandBuffer::CommandBuffer(VkDevice device, const CommandPool& pool) { diff --git a/src/mini/commandpool.cpp b/src/mini/commandpool.cpp index a624a59..bd63454 100644 --- a/src/mini/commandpool.cpp +++ b/src/mini/commandpool.cpp @@ -2,6 +2,10 @@ #include "layer.hpp" #include +#include + +#include +#include using namespace Mini; diff --git a/src/mini/image.cpp b/src/mini/image.cpp index 714c9cb..6b74864 100644 --- a/src/mini/image.cpp +++ b/src/mini/image.cpp @@ -1,8 +1,11 @@ #include "mini/image.hpp" #include "layer.hpp" +#include #include +#include +#include #include using namespace Mini; diff --git a/src/mini/semaphore.cpp b/src/mini/semaphore.cpp index b54c776..c240c87 100644 --- a/src/mini/semaphore.cpp +++ b/src/mini/semaphore.cpp @@ -2,6 +2,9 @@ #include "layer.hpp" #include +#include + +#include using namespace Mini; diff --git a/src/utils/log.cpp b/src/utils/log.cpp index 21405d0..225d9f4 100644 --- a/src/utils/log.cpp +++ b/src/utils/log.cpp @@ -1,8 +1,15 @@ #include "utils/log.hpp" -#include #include +#include +#include +#include +#include +#include +#include +#include + using namespace Log; bool Internal::isSetup{}; diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 8ff3d82..a36bae3 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -2,12 +2,15 @@ #include "utils/log.hpp" #include "layer.hpp" -#include #include +#include +#include +#include #include #include -#include +#include +#include using namespace Utils; From 7fe59a945985e863b933b17181e6c50c784734cf Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 18:32:47 +0200 Subject: [PATCH 113/253] implement simple benchmark fixes #44 --- lsfg-vk-v3.1/src/context.cpp | 12 ++-- lsfg-vk-v3.1/src/core/device.cpp | 4 +- lsfg-vk-v3.1/src/core/image.cpp | 2 +- lsfg-vk-v3.1/src/lsfg.cpp | 1 + lsfg-vk-v3.1/src/shaders/generate.cpp | 2 +- src/utils/benchmark.cpp | 98 +++++++++++++++++++++++++++ 6 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 src/utils/benchmark.cpp diff --git a/lsfg-vk-v3.1/src/context.cpp b/lsfg-vk-v3.1/src/context.cpp index fb8b62b..5bd9d51 100644 --- a/lsfg-vk-v3.1/src/context.cpp +++ b/lsfg-vk-v3.1/src/context.cpp @@ -71,7 +71,7 @@ void Context::present(Vulkan& vk, data.shouldWait = true; // 1. create mipmaps and process input image - data.inSemaphore = Core::Semaphore(vk.device, inSem); + 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); @@ -84,15 +84,17 @@ void Context::present(Vulkan& vk, this->beta.Dispatch(data.cmdBuffer1, this->frameIdx); data.cmdBuffer1.end(); + std::vector waits = { data.inSemaphore }; + if (inSem < 0) waits.clear(); data.cmdBuffer1.submit(vk.device.getComputeQueue(), std::nullopt, - { data.inSemaphore }, 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); - outSemaphore = Core::Semaphore(vk.device, outSem.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); @@ -108,9 +110,11 @@ void Context::present(Vulkan& vk, this->generate.Dispatch(buf2, this->frameIdx, pass); buf2.end(); + std::vector signals = { outSemaphore }; + if (inSem < 0) signals.clear(); buf2.submit(vk.device.getComputeQueue(), completionFence, { internalSemaphore }, std::nullopt, - { outSemaphore }, std::nullopt); + signals, std::nullopt); } this->frameIdx++; diff --git a/lsfg-vk-v3.1/src/core/device.cpp b/lsfg-vk-v3.1/src/core/device.cpp index b257517..81ec914 100644 --- a/lsfg-vk-v3.1/src/core/device.cpp +++ b/lsfg-vk-v3.1/src/core/device.cpp @@ -29,7 +29,7 @@ Device::Device(const Instance& instance, uint64_t deviceUUID) { if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Failed to get physical devices"); - // get device by uuid + // get device by uuid std::optional physicalDevice; for (const auto& device : devices) { VkPhysicalDeviceProperties properties; @@ -37,7 +37,7 @@ Device::Device(const Instance& instance, uint64_t deviceUUID) { const uint64_t uuid = static_cast(properties.vendorID) << 32 | properties.deviceID; - if (deviceUUID == uuid) { + if (deviceUUID == uuid || deviceUUID == 0x1463ABAC) { physicalDevice = device; break; } diff --git a/lsfg-vk-v3.1/src/core/image.cpp b/lsfg-vk-v3.1/src/core/image.cpp index c038373..9eb333d 100644 --- a/lsfg-vk-v3.1/src/core/image.cpp +++ b/lsfg-vk-v3.1/src/core/image.cpp @@ -181,7 +181,7 @@ Image::Image(const Core::Device& device, VkExtent2D extent, VkFormat format, }; const VkMemoryAllocateInfo allocInfo{ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, - .pNext = &importInfo, + .pNext = fd == -1 ? nullptr : &importInfo, .allocationSize = memReqs.size, .memoryTypeIndex = memType.value() }; diff --git a/lsfg-vk-v3.1/src/lsfg.cpp b/lsfg-vk-v3.1/src/lsfg.cpp index 4a80619..49fc018 100644 --- a/lsfg-vk-v3.1/src/lsfg.cpp +++ b/lsfg-vk-v3.1/src/lsfg.cpp @@ -40,6 +40,7 @@ void LSFG::initialize(uint64_t deviceUUID, .flowScale = flowScale, .isHdr = isHdr }); + contexts = std::unordered_map(); device->commandPool = Core::CommandPool(device->device); device->descriptorPool = Core::DescriptorPool(device->device); diff --git a/lsfg-vk-v3.1/src/shaders/generate.cpp b/lsfg-vk-v3.1/src/shaders/generate.cpp index 20a1098..95c2f74 100644 --- a/lsfg-vk-v3.1/src/shaders/generate.cpp +++ b/lsfg-vk-v3.1/src/shaders/generate.cpp @@ -35,7 +35,7 @@ Generate::Generate(Vulkan& vk, 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.at(i)); + VK_IMAGE_ASPECT_COLOR_BIT, fds.empty() ? -1 : fds.at(i)); // hook up shaders for (size_t i = 0; i < vk.generationCount; i++) { diff --git a/src/utils/benchmark.cpp b/src/utils/benchmark.cpp new file mode 100644 index 0000000..91db75e --- /dev/null +++ b/src/utils/benchmark.cpp @@ -0,0 +1,98 @@ +#include "extract/extract.hpp" +#include "extract/trans.hpp" +#include "utils/log.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +namespace { + void __attribute__((constructor)) init() { + // continue if preloaded + const char* preload = std::getenv("LD_PRELOAD"); + if (!preload || *preload == '\0') + return; + const std::string preload_str(preload); + if (preload_str.find("liblsfg-vk.so") == std::string::npos) + return; + // continue if benchmark requested + const char* benchmark = std::getenv("LSFG_BENCHMARK"); + if (!benchmark || *benchmark == '\0') + return; + const std::string benchmark_str(benchmark); + if (benchmark_str != "1") + return; + + // fetch benchmark parameters + const char* lsfgFlowScale = std::getenv("LSFG_FLOW_SCALE"); + const char* lsfgHdr = std::getenv("LSFG_HDR"); + const char* lsfgMultiplier = std::getenv("LSFG_MULTIPLIER"); + const char* lsfgExtentWidth = std::getenv("LSFG_EXTENT_WIDTH"); + const char* lsfgExtentHeight = std::getenv("LSFG_EXTENT_HEIGHT"); + + const float flowScale = lsfgFlowScale + ? std::stof(lsfgFlowScale) : 1.0F; + const bool isHdr = lsfgHdr + ? *lsfgHdr == '1' : false; + const uint64_t multiplier = lsfgMultiplier + ? std::stoull(std::string(lsfgMultiplier)) : 2; + const uint32_t width = lsfgExtentWidth + ? static_cast(std::stoul(lsfgExtentWidth)) : 1920; + const uint32_t height = lsfgExtentHeight + ? static_cast(std::stoul(lsfgExtentHeight)) : 1080; + + Log::info("bench", "Running {}x benchmark with {}x{} extent and flow scale of {} {} HDR", + multiplier, width, height, flowScale, isHdr ? "with" : "without"); + + // create the benchmark context + const char* lsfgDeviceUUID = std::getenv("LSFG_DEVICE_UUID"); + const uint64_t deviceUUID = lsfgDeviceUUID + ? std::stoull(std::string(lsfgDeviceUUID), nullptr, 16) : 0x1463ABAC; + + Extract::extractShaders(); + LSFG::initialize( + deviceUUID, // some magic number if not given + isHdr, 1.0F / flowScale, multiplier - 1, + [](const std::string& name) -> std::vector { + auto dxbc = Extract::getShader(name); + auto spirv = Extract::translateShader(dxbc); + return spirv; + } + ); + const int32_t ctx = LSFG::createContext(-1, -1, {}, + { .width = width, .height = height }, + isHdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM + ); + + Log::info("bench", "Benchmark context created, ready to run"); + + // run the benchmark (run 8*n + 1 so the fences are waited on) + const auto now = std::chrono::high_resolution_clock::now(); + const uint64_t iterations = (8 * 500) + 1; + for (uint64_t count = 0; count < iterations; count++) + LSFG::presentContext(ctx, -1, {}); + const auto then = std::chrono::high_resolution_clock::now(); + + // print results + const auto ms = std::chrono::duration_cast(then - now).count(); + + const auto perIteration = static_cast(ms) / static_cast(iterations); + + const uint64_t totalGen = (multiplier - 1) * iterations; + const auto genFps = static_cast(totalGen) / (static_cast(ms) / 1000.0F); + + const uint64_t totalFrames = iterations * multiplier; + const auto totalFps = static_cast(totalFrames) / (static_cast(ms) / 1000.0F); + + Log::info("bench", "Benchmark completed in {} ms", ms); + Log::info("bench", "Time per iteration: {:.2f} ms", perIteration); + Log::info("bench", "Generation FPS: {:.2f}", genFps); + Log::info("bench", "Final FPS: {:.2f}", totalFps); + Log::info("bench", "Benchmark finished, exiting"); + } +} From 5da49bed0ff52ab1676811e44104c3b1ca75d78a Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 18:40:12 +0200 Subject: [PATCH 114/253] enable LTO by default --- CMakeLists.txt | 5 +++++ lsfg-vk-v3.1/CMakeLists.txt | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ade9f38..3adae89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,11 @@ target_include_directories(lsfg-vk target_link_libraries(lsfg-vk PRIVATE lsfg-vk-v3.1 peparse dxvk vulkan) +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set_target_properties(lsfg-vk PROPERTIES + INTERPROCEDURAL_OPTIMIZATION ON) +endif() + # diagnostics if(CMAKE_BUILD_TYPE STREQUAL "Debug") set_target_properties(lsfg-vk PROPERTIES diff --git a/lsfg-vk-v3.1/CMakeLists.txt b/lsfg-vk-v3.1/CMakeLists.txt index d6cb670..2b1042c 100644 --- a/lsfg-vk-v3.1/CMakeLists.txt +++ b/lsfg-vk-v3.1/CMakeLists.txt @@ -29,6 +29,11 @@ target_link_libraries(lsfg-vk-v3.1 target_compile_options(lsfg-vk-v3.1 PRIVATE -fPIC) +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set_target_properties(lsfg-vk-v3.1 PROPERTIES + INTERPROCEDURAL_OPTIMIZATION ON) +endif() + # diagnostics if(CMAKE_BUILD_TYPE STREQUAL "Debug") set_target_properties(lsfg-vk-v3.1 PROPERTIES From 63f096837512f2ff018325d8dee35705dc2bc166 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 18:52:36 +0200 Subject: [PATCH 115/253] improve benchmark and fix init order --- CMakeLists.txt | 6 +++++- src/extract/extract.cpp | 17 ++++++++++------- src/utils/benchmark.cpp | 10 ++++++++-- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3adae89..ca7a3c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,15 @@ cmake_minimum_required(VERSION 3.29) +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) + +set(CMAKE_SKIP_RPATH ON) + if(CMAKE_BUILD_TYPE STREQUAL "Release") add_definitions("-DLSFG_NO_DEBUG") endif() # subprojects -set(CMAKE_SKIP_RPATH ON) include(cmake/FetchDXVK.cmake) include(cmake/FetchPeParse.cmake) diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index 5706dd7..a7add33 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -42,7 +42,10 @@ const std::unordered_map nameHashTable = {{ }}; namespace { - std::unordered_map> shaderData; + auto& shaders() { + static std::unordered_map> shaderData; + return shaderData; + } uint32_t fnv1a_hash(const std::vector& data) { uint32_t hash = 0x811C9DC5; @@ -60,13 +63,13 @@ namespace { std::copy_n(res.buf->buf, res.buf->bufLen, resource_data.data()); const uint32_t hash = fnv1a_hash(resource_data); - shaderData[hash] = resource_data; + shaders()[hash] = resource_data; return 0; } } void Extract::extractShaders() { - if (!shaderData.empty()) + if (!shaders().empty()) return; // find path to dll (absolutely beautiful code) @@ -99,15 +102,15 @@ void Extract::extractShaders() { } std::vector Extract::getShader(const std::string& name) { - if (shaderData.empty()) + if (shaders().empty()) throw std::runtime_error("Shaders are not loaded."); auto hit = nameHashTable.find(name); if (hit == nameHashTable.end()) - throw std::runtime_error("Shader not found: " + name); + throw std::runtime_error("Shader hash not found: " + name); - auto sit = shaderData.find(hit->second); - if (sit == shaderData.end()) + auto sit = shaders().find(hit->second); + if (sit == shaders().end()) throw std::runtime_error("Shader not found: " + name); return sit->second; diff --git a/src/utils/benchmark.cpp b/src/utils/benchmark.cpp index 91db75e..3f805e7 100644 --- a/src/utils/benchmark.cpp +++ b/src/utils/benchmark.cpp @@ -12,7 +12,7 @@ #include namespace { - void __attribute__((constructor)) init() { + void __attribute__((constructor)) benchmark_init() { // continue if preloaded const char* preload = std::getenv("LD_PRELOAD"); if (!preload || *preload == '\0') @@ -74,8 +74,14 @@ namespace { // run the benchmark (run 8*n + 1 so the fences are waited on) const auto now = std::chrono::high_resolution_clock::now(); const uint64_t iterations = (8 * 500) + 1; - for (uint64_t count = 0; count < iterations; count++) + for (uint64_t count = 0; count < iterations; count++) { LSFG::presentContext(ctx, -1, {}); + + if (count % 500 == 0) + Log::info("bench", "{:.2f}% done ({}/{})", + static_cast(count) / static_cast(iterations) * 100.0F, + count + 1, iterations); + } const auto then = std::chrono::high_resolution_clock::now(); // print results From 7fa2a605d054d7030b98c7ad2a267f4e4f5939b0 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 22:27:04 +0200 Subject: [PATCH 116/253] force enable vsync again --- src/hooks.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/hooks.cpp b/src/hooks.cpp index 826c817..7eff7a9 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -210,6 +210,20 @@ namespace { auto& deviceInfo = devices.at(swapchainToDeviceTable.at(*pPresentInfo->pSwapchains)); auto& swapchain = swapchains.at(*pPresentInfo->pSwapchains); + // patch vsync NOLINTBEGIN + const VkSwapchainPresentModeInfoEXT* presentModeInfo = + reinterpret_cast(pPresentInfo->pNext); + while (presentModeInfo) { + if (presentModeInfo->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT) { + for (size_t i = 0; i < presentModeInfo->swapchainCount; i++) + const_cast(presentModeInfo->pPresentModes)[i] = + VK_PRESENT_MODE_FIFO_KHR; + } + presentModeInfo = + reinterpret_cast(presentModeInfo->pNext); + } // NOLINTEND + + // present the next frame Log::debug("hooks2", "Presenting swapchain: {:x} on queue: {:x}", reinterpret_cast(*pPresentInfo->pSwapchains), reinterpret_cast(queue)); From 5ab8b4e91184dcaf78f1bb17296864bbd2162178 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 10 Jul 2025 23:22:12 +0200 Subject: [PATCH 117/253] create a build workflow fixes #45 --- .github/workflows/build.yml | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..32a610f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,42 @@ +name: Build lsfg-vk on Arch Linux +on: [push] + +jobs: + build-arch: + runs-on: ubuntu-latest + container: + image: archlinux:latest + steps: + - name: Prepare cache for pacman packages + uses: actions/cache@v4 + with: + path: /var/cache/pacman/pkg + key: archlinux-pacman-cache + - name: Install build dependencies + run: | + pacman -Syu --noconfirm \ + base-devel \ + git sdl3 \ + clang llvm \ + cmake meson ninja \ + glslang vulkan-headers vulkan-icd-loader + - name: Checkout repository + uses: actions/checkout@v4 + - name: Configure with CMake and Ninja + run: | + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=./build-release + - name: Build with Ninja + run: | + ninja -C build + - name: Install with CMake + run: | + cmake --install build + - name: Upload lsfg-vk artifact + uses: actions/upload-artifact@v4 + with: + name: lsfg-vk + path: | + build-release/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json + build-release/lib/liblsfg-vk.so From 8823dea1748d0f67174d2acf0915d5bd7861391f Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 11 Jul 2025 00:10:48 +0200 Subject: [PATCH 118/253] create extremely primitive install script fixes #20 --- README.md | 11 ++++++++--- install.sh | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100755 install.sh diff --git a/README.md b/README.md index cbf22c7..94f0c06 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,13 @@ This project brings [Lossless Scaling's Frame Generation](https://store.steampow >[!CAUTION] > **This is a work-in-progress**. While frame generation has worked in quite a few games, compatibility in many games as well as frame pacing issues still need to be fixed. -Please see the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki) for install instructions and join the [Discord](https://discord.gg/losslessscaling) for help (In order to see the linux channels, verify your Steam account.) +You can install lsfg-vk by running this in your command line: +```bash +curl -sSf https://pancake.gay/lsfg-vk.sh | sh +``` +Note: This script is extremely primitive and will not work on all distros. It has been written for Arch Linux and its derivations + +Please see the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki) for more information and join the [Discord](https://discord.gg/losslessscaling) for help (In order to see the linux channels, verify your Steam account.) >[!WARNING] -> Please do note open GitHub issues for anything other than feature requests. Due to the nature of this project, it is much easier to deal with issues through Discord, than GitHub. Use the #linux-reports channel for game compatibility. - +> Please do not open GitHub issues for anything other than feature requests. Due to the nature of this project, it is much easier to deal with issues through Discord, than GitHub. Use the #linux-reports channel for game compatibility. diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..ce50d05 --- /dev/null +++ b/install.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +: "${INSTALL_PATH:=$HOME/.local}" +BASE_URL='https://pancake.gay/lsfg-vk' + +# get local and remote versions +REMOTE_HASH=$(curl -fsSL "$BASE_URL/lsfg-vk.zip.sha") +LOCAL_HASH=$(test -f "$INSTALL_PATH/share/lsfg-vk.sha" && cat "$INSTALL_PATH/share/lsfg-vk.sha") + +if [ "$REMOTE_HASH" != "$LOCAL_HASH" ]; then + # prompt user for confirmation + echo -n "Do you wish to install the latest version of lsfg-vk? (y/n) " + read -r answer < /dev/tty + + if [ "$answer" != "y" ]; then + echo "Installation aborted." + exit 0 + fi + + # download lsfg-vk + curl -fsSL -o "/tmp/lsfg-vk.zip" "$BASE_URL/lsfg-vk.zip" + if [ $? -ne 0 ]; then + echo "Failed to download lsfg-vk. Please check your internet connection." + exit 1 + fi + + # install lsfg-vk + cd "$INSTALL_PATH" || exit 1 + unzip -oqq "/tmp/lsfg-vk.zip" + echo "$REMOTE_HASH" > share/lsfg-vk.sha + + echo "lsfg-vk has been installed." +else + echo "lsfg-vk is already up to date." +fi From 896a205e8e05f0f3a419311b5a4cab129260e933 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 11 Jul 2025 15:21:05 +0200 Subject: [PATCH 119/253] ensure swapchains exist before presenting fixes #57 --- src/hooks.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/hooks.cpp b/src/hooks.cpp index 7eff7a9..a7afc26 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -142,7 +142,12 @@ namespace { const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain) { - auto& deviceInfo = devices.at(device); + auto it = devices.find(device); + if (it == devices.end()) { + Log::warn("hooks", "Created swapchain without device info present"); + return Layer::ovkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + } + auto& deviceInfo = it->second; // update swapchain create info VkSwapchainCreateInfoKHR createInfo = *pCreateInfo; @@ -207,8 +212,27 @@ namespace { VkResult myvkQueuePresentKHR( VkQueue queue, const VkPresentInfoKHR* pPresentInfo) { - auto& deviceInfo = devices.at(swapchainToDeviceTable.at(*pPresentInfo->pSwapchains)); - auto& swapchain = swapchains.at(*pPresentInfo->pSwapchains); + auto it = swapchainToDeviceTable.find(*pPresentInfo->pSwapchains); + if (it == swapchainToDeviceTable.end()) { + Log::warn("hooks2", "Swapchain {:x} not found in swapchainToDeviceTable", + reinterpret_cast(*pPresentInfo->pSwapchains)); + return Layer::ovkQueuePresentKHR(queue, pPresentInfo); + } + auto it2 = devices.find(it->second); + if (it2 == devices.end()) { + Log::warn("hooks2", "Device {:x} not found in devices", + reinterpret_cast(it->second)); + return Layer::ovkQueuePresentKHR(queue, pPresentInfo); + } + auto it3 = swapchains.find(*pPresentInfo->pSwapchains); + if (it3 == swapchains.end()) { + Log::warn("hooks2", "Swapchain {:x} not found in swapchains", + reinterpret_cast(*pPresentInfo->pSwapchains)); + return Layer::ovkQueuePresentKHR(queue, pPresentInfo); + } + + auto& deviceInfo = it2->second; + auto& swapchain = it3->second; // patch vsync NOLINTBEGIN const VkSwapchainPresentModeInfoEXT* presentModeInfo = From d4e4ec8df619031673b160671d71d3e6a9c2380e Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 11 Jul 2025 18:39:23 +0200 Subject: [PATCH 120/253] allow suboptimal rendering with fewer than required swapchain images fixes #61 --- include/layer.hpp | 5 +++++ include/utils/utils.hpp | 9 +++++++++ src/hooks.cpp | 15 +++++++++++++-- src/layer.cpp | 12 ++++++++++++ src/utils/utils.cpp | 11 +++++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/include/layer.hpp b/include/layer.hpp index 8cb3163..ce12818 100644 --- a/include/layer.hpp +++ b/include/layer.hpp @@ -53,6 +53,11 @@ namespace Layer { 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( diff --git a/include/utils/utils.hpp b/include/utils/utils.hpp index 7094cbe..138eb00 100644 --- a/include/utils/utils.hpp +++ b/include/utils/utils.hpp @@ -31,6 +31,15 @@ namespace Utils { /// 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. /// diff --git a/src/hooks.cpp b/src/hooks.cpp index a7afc26..6f7c12b 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -151,13 +151,21 @@ namespace { // update swapchain create info VkSwapchainCreateInfoKHR createInfo = *pCreateInfo; - createInfo.minImageCount += 1 + deviceInfo.frameGen; // 1 deferred + N framegen, FIXME: check hardware max + const uint32_t maxImageCount = Utils::getMaxImageCount(deviceInfo.physicalDevice, pCreateInfo->surface); + const uint32_t imageCount = createInfo.minImageCount + 1 + static_cast(deviceInfo.frameGen); + Log::debug("hooks", "Creating swapchain with max image count: {}/{}", imageCount, maxImageCount); + if (imageCount > maxImageCount) { + Log::warn("hooks", "LSFG_MULTIPLIER is set very high. This might lead to performance degradation"); + createInfo.minImageCount = maxImageCount; // limit to max possible + } else { + createInfo.minImageCount = imageCount; // set to frameGen + 1 + } createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; // allow copy from/to images createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; // force vsync auto res = Layer::ovkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); if (res != VK_SUCCESS) { - Log::error("hooks", "Failed to create swapchain: {:x}", static_cast(res)); + Log::error("hooks", "Failed to create swapchain: {}", static_cast(res)); return res; } Log::info("hooks", "Swapchain created successfully: {:x}", @@ -235,6 +243,8 @@ namespace { auto& swapchain = it3->second; // patch vsync NOLINTBEGIN + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" const VkSwapchainPresentModeInfoEXT* presentModeInfo = reinterpret_cast(pPresentInfo->pNext); while (presentModeInfo) { @@ -246,6 +256,7 @@ namespace { presentModeInfo = reinterpret_cast(presentModeInfo->pNext); } // NOLINTEND +#pragma clang diagnostic pop // present the next frame Log::debug("hooks2", "Presenting swapchain: {:x} on queue: {:x}", diff --git a/src/layer.cpp b/src/layer.cpp index 6a5e915..bc8bb9d 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -23,6 +23,7 @@ namespace { PFN_vkGetPhysicalDeviceQueueFamilyProperties next_vkGetPhysicalDeviceQueueFamilyProperties{}; PFN_vkGetPhysicalDeviceMemoryProperties next_vkGetPhysicalDeviceMemoryProperties{}; PFN_vkGetPhysicalDeviceProperties next_vkGetPhysicalDeviceProperties{}; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR{}; PFN_vkCreateSwapchainKHR next_vkCreateSwapchainKHR{}; PFN_vkQueuePresentKHR next_vkQueuePresentKHR{}; @@ -122,6 +123,8 @@ namespace { "vkGetPhysicalDeviceMemoryProperties", &next_vkGetPhysicalDeviceMemoryProperties); success &= initInstanceFunc(*pInstance, "vkGetPhysicalDeviceProperties", &next_vkGetPhysicalDeviceProperties); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceSurfaceCapabilitiesKHR", &next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR); if (!success) { Log::error("layer", "Failed to get instance function pointers"); return VK_ERROR_INITIALIZATION_FAILED; @@ -374,6 +377,15 @@ void Layer::ovkGetPhysicalDeviceProperties( reinterpret_cast(physicalDevice)); next_vkGetPhysicalDeviceProperties(physicalDevice, pProperties); } +VkResult Layer::ovkGetPhysicalDeviceSurfaceCapabilitiesKHR( + VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface, + VkSurfaceCapabilitiesKHR* pSurfaceCapabilities) { + Log::debug("vulkan", "vkGetPhysicalDeviceSurfaceCapabilitiesKHR called for physical device {:x} and surface {:x}", + reinterpret_cast(physicalDevice), + reinterpret_cast(surface)); + return next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, pSurfaceCapabilities); +} VkResult Layer::ovkCreateSwapchainKHR( VkDevice device, diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index a36bae3..d09de7f 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -53,6 +53,17 @@ uint64_t Utils::getDeviceUUID(VkPhysicalDevice physicalDevice) { return static_cast(properties.vendorID) << 32 | properties.deviceID; } +uint32_t Utils::getMaxImageCount(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface) { + VkSurfaceCapabilitiesKHR capabilities{}; + auto res = Layer::ovkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, + surface, &capabilities); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to get surface capabilities"); + if (capabilities.maxImageCount == 0) + return 999; // :3 + return capabilities.maxImageCount; +} + std::vector Utils::addExtensions(const char* const* extensions, size_t count, const std::vector& requiredExtensions) { std::vector ext(count); From bc481e65a5b848115762e1ea00d0121cb6a5a4eb Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 11 Jul 2025 23:01:02 +0200 Subject: [PATCH 121/253] fixing steam deck game mode... somehow fixes #60 --- src/hooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks.cpp b/src/hooks.cpp index 6f7c12b..dff7d95 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -104,7 +104,7 @@ namespace { frameGen + 1); auto queue = Utils::findQueue(*pDevice, physicalDevice, pCreateInfo, - VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT); + VK_QUEUE_GRAPHICS_BIT); Log::debug("hooks", "Found queue at index {}: {:x}", queue.first, reinterpret_cast(queue.second)); From dac149c84a9300e20165c274f3f6427f164ebde9 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 11 Jul 2025 23:25:07 +0200 Subject: [PATCH 122/253] support searching for more paths fixes #56 --- src/extract/extract.cpp | 54 ++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index a7add33..c61f3da 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -66,35 +67,44 @@ namespace { shaders()[hash] = resource_data; return 0; } + + const std::vector PATHS{{ + ".local/share/Steam/steamapps/common", + ".steam/steam/steamapps/common", + ".steam/debian-installation/steamapps/common", + ".var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common", + "snap/steam/common/.local/share/Steam/steamapps/common" + }}; + + std::string getDllPath() { + // overriden path + const char* dllPath = getenv("LSFG_DLL_PATH"); + if (dllPath && *dllPath != '\0') + return{dllPath}; + // home based paths + const char* home = getenv("HOME"); + const std::string homeStr = home ? home : ""; + for (const auto& base : PATHS) { + const std::filesystem::path path = + std::filesystem::path(homeStr) / base / "Lossless Scaling" / "Lossless.dll"; + if (std::filesystem::exists(path)) + return path.string(); + } + // xdg home + const char* dataDir = getenv("XDG_DATA_HOME"); + if (dataDir && *dataDir != '\0') + return std::string(dataDir) + "/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; + // final fallback + return "Lossless.dll"; + } } void Extract::extractShaders() { if (!shaders().empty()) return; - // find path to dll (absolutely beautiful code) - char* dllPath = getenv("LSFG_DLL_PATH"); - std::string dllPathStr; - if (dllPath && *dllPath != '\0') { - dllPathStr = std::string(dllPath); - } else { - const char* dataDir = getenv("XDG_DATA_HOME"); - if (dataDir && *dataDir != '\0') { - dllPathStr = std::string(dataDir) + - "/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; - } else { - const char* homeDir = getenv("HOME"); - if (homeDir && *homeDir != '\0') { - dllPathStr = std::string(homeDir) + - "/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"; - } else { - dllPathStr = "Lossless.dll"; - } - } - } - // parse the dll - peparse::parsed_pe* dll = peparse::ParsePEFromFile(dllPathStr.c_str()); + peparse::parsed_pe* dll = peparse::ParsePEFromFile(getDllPath().c_str()); if (!dll) throw std::runtime_error("Unable to read Lossless.dll, is it installed?"); peparse::IterRsrc(dll, on_resource, nullptr); From 82bcf2bb72fcbc4dbff2967db19b76d3114d47f9 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 18:19:18 +0200 Subject: [PATCH 123/253] allow for multiple lsfg versions to coexist --- CMakeLists.txt | 3 +- lsfg-vk-common/CMakeLists.txt | 59 +++++++++++++++++++ lsfg-vk-common/include/common/exception.hpp | 34 +++++++++++ .../include/common}/utils.hpp | 0 .../include/core/buffer.hpp | 0 .../include/core/commandbuffer.hpp | 0 .../include/core/commandpool.hpp | 0 .../include/core/descriptorpool.hpp | 0 .../include/core/descriptorset.hpp | 0 .../include/core/device.hpp | 0 .../include/core/fence.hpp | 0 .../include/core/image.hpp | 0 .../include/core/instance.hpp | 0 .../include/core/pipeline.hpp | 0 .../include/core/sampler.hpp | 0 .../include/core/semaphore.hpp | 0 .../include/core/shadermodule.hpp | 0 .../include/pool/resourcepool.hpp | 0 .../include/pool/shaderpool.hpp | 0 lsfg-vk-common/src/common/exception.cpp | 16 +++++ .../src/common}/utils.cpp | 4 +- .../src/core/buffer.cpp | 2 +- .../src/core/commandbuffer.cpp | 2 +- .../src/core/commandpool.cpp | 2 +- .../src/core/descriptorpool.cpp | 2 +- .../src/core/descriptorset.cpp | 2 +- .../src/core/device.cpp | 2 +- .../src/core/fence.cpp | 2 +- .../src/core/image.cpp | 2 +- .../src/core/instance.cpp | 2 +- .../src/core/pipeline.cpp | 2 +- .../src/core/sampler.cpp | 2 +- .../src/core/semaphore.cpp | 2 +- .../src/core/shadermodule.cpp | 2 +- .../src/pool/resourcepool.cpp | 0 .../src/pool/shaderpool.cpp | 0 lsfg-vk-v3.1/CMakeLists.txt | 4 +- lsfg-vk-v3.1/README.md | 6 +- lsfg-vk-v3.1/include/context.hpp | 2 +- lsfg-vk-v3.1/include/shaders/alpha.hpp | 2 +- lsfg-vk-v3.1/include/shaders/beta.hpp | 2 +- lsfg-vk-v3.1/include/shaders/delta.hpp | 2 +- lsfg-vk-v3.1/include/shaders/gamma.hpp | 2 +- lsfg-vk-v3.1/include/shaders/generate.hpp | 2 +- lsfg-vk-v3.1/include/shaders/mipmaps.hpp | 2 +- lsfg-vk-v3.1/public/lsfg.hpp | 27 +-------- lsfg-vk-v3.1/src/context.cpp | 4 +- lsfg-vk-v3.1/src/lsfg.cpp | 33 +++++------ lsfg-vk-v3.1/src/shaders/alpha.cpp | 2 +- lsfg-vk-v3.1/src/shaders/beta.cpp | 2 +- lsfg-vk-v3.1/src/shaders/delta.cpp | 2 +- lsfg-vk-v3.1/src/shaders/gamma.cpp | 2 +- lsfg-vk-v3.1/src/shaders/generate.cpp | 2 +- lsfg-vk-v3.1/src/shaders/mipmaps.cpp | 2 +- src/context.cpp | 9 +-- src/hooks.cpp | 2 +- src/mini/commandbuffer.cpp | 2 +- src/mini/commandpool.cpp | 2 +- src/mini/image.cpp | 2 +- src/mini/semaphore.cpp | 2 +- src/utils/benchmark.cpp | 6 +- src/utils/utils.cpp | 2 +- 62 files changed, 174 insertions(+), 95 deletions(-) create mode 100644 lsfg-vk-common/CMakeLists.txt create mode 100644 lsfg-vk-common/include/common/exception.hpp rename {lsfg-vk-v3.1/include/utils => lsfg-vk-common/include/common}/utils.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/buffer.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/commandbuffer.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/commandpool.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/descriptorpool.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/descriptorset.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/device.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/fence.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/image.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/instance.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/pipeline.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/sampler.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/semaphore.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/core/shadermodule.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/pool/resourcepool.hpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/include/pool/shaderpool.hpp (100%) create mode 100644 lsfg-vk-common/src/common/exception.cpp rename {lsfg-vk-v3.1/src/utils => lsfg-vk-common/src/common}/utils.cpp (99%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/buffer.cpp (99%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/commandbuffer.cpp (99%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/commandpool.cpp (96%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/descriptorpool.cpp (97%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/descriptorset.cpp (99%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/device.cpp (99%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/fence.cpp (97%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/image.cpp (99%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/instance.cpp (97%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/pipeline.cpp (98%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/sampler.cpp (97%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/semaphore.cpp (99%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/core/shadermodule.cpp (98%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/pool/resourcepool.cpp (100%) rename {lsfg-vk-v3.1 => lsfg-vk-common}/src/pool/shaderpool.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca7a3c6..cd59fc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ endif() include(cmake/FetchDXVK.cmake) include(cmake/FetchPeParse.cmake) +add_subdirectory(lsfg-vk-common) add_subdirectory(lsfg-vk-v3.1) # main project @@ -38,7 +39,7 @@ set_target_properties(lsfg-vk PROPERTIES target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk - PRIVATE lsfg-vk-v3.1 peparse dxvk vulkan) + PRIVATE lsfg-vk-common lsfg-vk-v3.1 peparse dxvk vulkan) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(lsfg-vk PROPERTIES diff --git a/lsfg-vk-common/CMakeLists.txt b/lsfg-vk-common/CMakeLists.txt new file mode 100644 index 0000000..50b4525 --- /dev/null +++ b/lsfg-vk-common/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.29) + +# project +project(lsfg-vk-common + LANGUAGES CXX) + +file(GLOB SOURCES + "src/core/*.cpp" + "src/pool/*.cpp" + "src/common/*.cpp" + "src/*.cpp" +) + +add_library(lsfg-vk-common STATIC ${SOURCES}) + +# target +set_target_properties(lsfg-vk-common PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON) +target_include_directories(lsfg-vk-common + PUBLIC include) +target_link_libraries(lsfg-vk-common + PRIVATE vulkan) +target_compile_options(lsfg-vk-common PRIVATE + -fPIC) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set_target_properties(lsfg-vk-common PROPERTIES + INTERPROCEDURAL_OPTIMIZATION ON) +endif() + +# diagnostics +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set_target_properties(lsfg-vk-common PROPERTIES + EXPORT_COMPILE_COMMANDS ON) +endif() + +if(LSFGVK_EXCESS_DEBUG) + target_compile_options(lsfg-vk-common 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-common PROPERTIES + CXX_CLANG_TIDY clang-tidy) +endif() diff --git a/lsfg-vk-common/include/common/exception.hpp b/lsfg-vk-common/include/common/exception.hpp new file mode 100644 index 0000000..3808ca8 --- /dev/null +++ b/lsfg-vk-common/include/common/exception.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include + +namespace LSFG { + + /// Simple exception class for Vulkan errors. + class vulkan_error : public std::runtime_error { + public: + /// + /// Construct a vulkan_error with a message and a Vulkan result code. + /// + /// @param result The Vulkan result code associated with the error. + /// @param message The error message. + /// + explicit vulkan_error(VkResult result, const std::string& message); + + /// Get the Vulkan result code associated with this error. + [[nodiscard]] VkResult error() const { return this->result; } + + // Trivially copyable, moveable and destructible + vulkan_error(const vulkan_error&) = default; + vulkan_error(vulkan_error&&) = default; + vulkan_error& operator=(const vulkan_error&) = default; + vulkan_error& operator=(vulkan_error&&) = default; + ~vulkan_error() noexcept override; + private: + VkResult result; + }; + +} diff --git a/lsfg-vk-v3.1/include/utils/utils.hpp b/lsfg-vk-common/include/common/utils.hpp similarity index 100% rename from lsfg-vk-v3.1/include/utils/utils.hpp rename to lsfg-vk-common/include/common/utils.hpp diff --git a/lsfg-vk-v3.1/include/core/buffer.hpp b/lsfg-vk-common/include/core/buffer.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/buffer.hpp rename to lsfg-vk-common/include/core/buffer.hpp diff --git a/lsfg-vk-v3.1/include/core/commandbuffer.hpp b/lsfg-vk-common/include/core/commandbuffer.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/commandbuffer.hpp rename to lsfg-vk-common/include/core/commandbuffer.hpp diff --git a/lsfg-vk-v3.1/include/core/commandpool.hpp b/lsfg-vk-common/include/core/commandpool.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/commandpool.hpp rename to lsfg-vk-common/include/core/commandpool.hpp diff --git a/lsfg-vk-v3.1/include/core/descriptorpool.hpp b/lsfg-vk-common/include/core/descriptorpool.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/descriptorpool.hpp rename to lsfg-vk-common/include/core/descriptorpool.hpp diff --git a/lsfg-vk-v3.1/include/core/descriptorset.hpp b/lsfg-vk-common/include/core/descriptorset.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/descriptorset.hpp rename to lsfg-vk-common/include/core/descriptorset.hpp diff --git a/lsfg-vk-v3.1/include/core/device.hpp b/lsfg-vk-common/include/core/device.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/device.hpp rename to lsfg-vk-common/include/core/device.hpp diff --git a/lsfg-vk-v3.1/include/core/fence.hpp b/lsfg-vk-common/include/core/fence.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/fence.hpp rename to lsfg-vk-common/include/core/fence.hpp diff --git a/lsfg-vk-v3.1/include/core/image.hpp b/lsfg-vk-common/include/core/image.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/image.hpp rename to lsfg-vk-common/include/core/image.hpp diff --git a/lsfg-vk-v3.1/include/core/instance.hpp b/lsfg-vk-common/include/core/instance.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/instance.hpp rename to lsfg-vk-common/include/core/instance.hpp diff --git a/lsfg-vk-v3.1/include/core/pipeline.hpp b/lsfg-vk-common/include/core/pipeline.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/pipeline.hpp rename to lsfg-vk-common/include/core/pipeline.hpp diff --git a/lsfg-vk-v3.1/include/core/sampler.hpp b/lsfg-vk-common/include/core/sampler.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/sampler.hpp rename to lsfg-vk-common/include/core/sampler.hpp diff --git a/lsfg-vk-v3.1/include/core/semaphore.hpp b/lsfg-vk-common/include/core/semaphore.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/semaphore.hpp rename to lsfg-vk-common/include/core/semaphore.hpp diff --git a/lsfg-vk-v3.1/include/core/shadermodule.hpp b/lsfg-vk-common/include/core/shadermodule.hpp similarity index 100% rename from lsfg-vk-v3.1/include/core/shadermodule.hpp rename to lsfg-vk-common/include/core/shadermodule.hpp diff --git a/lsfg-vk-v3.1/include/pool/resourcepool.hpp b/lsfg-vk-common/include/pool/resourcepool.hpp similarity index 100% rename from lsfg-vk-v3.1/include/pool/resourcepool.hpp rename to lsfg-vk-common/include/pool/resourcepool.hpp diff --git a/lsfg-vk-v3.1/include/pool/shaderpool.hpp b/lsfg-vk-common/include/pool/shaderpool.hpp similarity index 100% rename from lsfg-vk-v3.1/include/pool/shaderpool.hpp rename to lsfg-vk-common/include/pool/shaderpool.hpp diff --git a/lsfg-vk-common/src/common/exception.cpp b/lsfg-vk-common/src/common/exception.cpp new file mode 100644 index 0000000..f675833 --- /dev/null +++ b/lsfg-vk-common/src/common/exception.cpp @@ -0,0 +1,16 @@ +#include "common/exception.hpp" + +#include + +#include +#include +#include +#include + +using namespace LSFG; + +vulkan_error::vulkan_error(VkResult result, const std::string& message) + : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), + result(result) {} + +vulkan_error::~vulkan_error() noexcept = default; diff --git a/lsfg-vk-v3.1/src/utils/utils.cpp b/lsfg-vk-common/src/common/utils.cpp similarity index 99% rename from lsfg-vk-v3.1/src/utils/utils.cpp rename to lsfg-vk-common/src/common/utils.cpp index 5aea8fd..5b083ee 100644 --- a/lsfg-vk-v3.1/src/utils/utils.cpp +++ b/lsfg-vk-common/src/common/utils.cpp @@ -1,10 +1,10 @@ -#include "utils/utils.hpp" +#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 "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/buffer.cpp b/lsfg-vk-common/src/core/buffer.cpp similarity index 99% rename from lsfg-vk-v3.1/src/core/buffer.cpp rename to lsfg-vk-common/src/core/buffer.cpp index c18a8ae..001c77b 100644 --- a/lsfg-vk-v3.1/src/core/buffer.cpp +++ b/lsfg-vk-common/src/core/buffer.cpp @@ -1,6 +1,6 @@ #include "core/buffer.hpp" #include "core/device.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/commandbuffer.cpp b/lsfg-vk-common/src/core/commandbuffer.cpp similarity index 99% rename from lsfg-vk-v3.1/src/core/commandbuffer.cpp rename to lsfg-vk-common/src/core/commandbuffer.cpp index 84999de..762db6a 100644 --- a/lsfg-vk-v3.1/src/core/commandbuffer.cpp +++ b/lsfg-vk-common/src/core/commandbuffer.cpp @@ -3,7 +3,7 @@ #include "core/commandpool.hpp" #include "core/fence.hpp" #include "core/semaphore.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/commandpool.cpp b/lsfg-vk-common/src/core/commandpool.cpp similarity index 96% rename from lsfg-vk-v3.1/src/core/commandpool.cpp rename to lsfg-vk-common/src/core/commandpool.cpp index 377de39..ffa89de 100644 --- a/lsfg-vk-v3.1/src/core/commandpool.cpp +++ b/lsfg-vk-common/src/core/commandpool.cpp @@ -1,6 +1,6 @@ #include "core/commandpool.hpp" #include "core/device.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/descriptorpool.cpp b/lsfg-vk-common/src/core/descriptorpool.cpp similarity index 97% rename from lsfg-vk-v3.1/src/core/descriptorpool.cpp rename to lsfg-vk-common/src/core/descriptorpool.cpp index ac08956..f9cd359 100644 --- a/lsfg-vk-v3.1/src/core/descriptorpool.cpp +++ b/lsfg-vk-common/src/core/descriptorpool.cpp @@ -1,6 +1,6 @@ #include "core/descriptorpool.hpp" #include "core/device.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/descriptorset.cpp b/lsfg-vk-common/src/core/descriptorset.cpp similarity index 99% rename from lsfg-vk-v3.1/src/core/descriptorset.cpp rename to lsfg-vk-common/src/core/descriptorset.cpp index b8f95a2..e72ba72 100644 --- a/lsfg-vk-v3.1/src/core/descriptorset.cpp +++ b/lsfg-vk-common/src/core/descriptorset.cpp @@ -7,7 +7,7 @@ #include "core/image.hpp" #include "core/sampler.hpp" #include "core/buffer.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/device.cpp b/lsfg-vk-common/src/core/device.cpp similarity index 99% rename from lsfg-vk-v3.1/src/core/device.cpp rename to lsfg-vk-common/src/core/device.cpp index 81ec914..72cfa4b 100644 --- a/lsfg-vk-v3.1/src/core/device.cpp +++ b/lsfg-vk-common/src/core/device.cpp @@ -1,6 +1,6 @@ #include "core/device.hpp" #include "core/instance.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/fence.cpp b/lsfg-vk-common/src/core/fence.cpp similarity index 97% rename from lsfg-vk-v3.1/src/core/fence.cpp rename to lsfg-vk-common/src/core/fence.cpp index 52a05a2..e402019 100644 --- a/lsfg-vk-v3.1/src/core/fence.cpp +++ b/lsfg-vk-common/src/core/fence.cpp @@ -1,6 +1,6 @@ #include "core/fence.hpp" #include "core/device.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/image.cpp b/lsfg-vk-common/src/core/image.cpp similarity index 99% rename from lsfg-vk-v3.1/src/core/image.cpp rename to lsfg-vk-common/src/core/image.cpp index 9eb333d..50b522e 100644 --- a/lsfg-vk-v3.1/src/core/image.cpp +++ b/lsfg-vk-common/src/core/image.cpp @@ -1,6 +1,6 @@ #include "core/image.hpp" #include "core/device.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/instance.cpp b/lsfg-vk-common/src/core/instance.cpp similarity index 97% rename from lsfg-vk-v3.1/src/core/instance.cpp rename to lsfg-vk-common/src/core/instance.cpp index 29203fc..1302b7a 100644 --- a/lsfg-vk-v3.1/src/core/instance.cpp +++ b/lsfg-vk-common/src/core/instance.cpp @@ -1,5 +1,5 @@ #include "core/instance.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include #include diff --git a/lsfg-vk-v3.1/src/core/pipeline.cpp b/lsfg-vk-common/src/core/pipeline.cpp similarity index 98% rename from lsfg-vk-v3.1/src/core/pipeline.cpp rename to lsfg-vk-common/src/core/pipeline.cpp index cff1bd6..e3dfd9d 100644 --- a/lsfg-vk-v3.1/src/core/pipeline.cpp +++ b/lsfg-vk-common/src/core/pipeline.cpp @@ -2,7 +2,7 @@ #include "core/device.hpp" #include "core/shadermodule.hpp" #include "core/commandbuffer.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/sampler.cpp b/lsfg-vk-common/src/core/sampler.cpp similarity index 97% rename from lsfg-vk-v3.1/src/core/sampler.cpp rename to lsfg-vk-common/src/core/sampler.cpp index 0b82a8f..f44fe2d 100644 --- a/lsfg-vk-v3.1/src/core/sampler.cpp +++ b/lsfg-vk-common/src/core/sampler.cpp @@ -1,6 +1,6 @@ #include "core/sampler.hpp" #include "core/device.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/semaphore.cpp b/lsfg-vk-common/src/core/semaphore.cpp similarity index 99% rename from lsfg-vk-v3.1/src/core/semaphore.cpp rename to lsfg-vk-common/src/core/semaphore.cpp index 0ef4de9..57bcd42 100644 --- a/lsfg-vk-v3.1/src/core/semaphore.cpp +++ b/lsfg-vk-common/src/core/semaphore.cpp @@ -1,6 +1,6 @@ #include "core/semaphore.hpp" #include "core/device.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/core/shadermodule.cpp b/lsfg-vk-common/src/core/shadermodule.cpp similarity index 98% rename from lsfg-vk-v3.1/src/core/shadermodule.cpp rename to lsfg-vk-common/src/core/shadermodule.cpp index 50b36b2..099b08b 100644 --- a/lsfg-vk-v3.1/src/core/shadermodule.cpp +++ b/lsfg-vk-common/src/core/shadermodule.cpp @@ -1,6 +1,6 @@ #include "core/shadermodule.hpp" #include "core/device.hpp" -#include "lsfg.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/pool/resourcepool.cpp b/lsfg-vk-common/src/pool/resourcepool.cpp similarity index 100% rename from lsfg-vk-v3.1/src/pool/resourcepool.cpp rename to lsfg-vk-common/src/pool/resourcepool.cpp diff --git a/lsfg-vk-v3.1/src/pool/shaderpool.cpp b/lsfg-vk-common/src/pool/shaderpool.cpp similarity index 100% rename from lsfg-vk-v3.1/src/pool/shaderpool.cpp rename to lsfg-vk-common/src/pool/shaderpool.cpp diff --git a/lsfg-vk-v3.1/CMakeLists.txt b/lsfg-vk-v3.1/CMakeLists.txt index 2b1042c..314c0ad 100644 --- a/lsfg-vk-v3.1/CMakeLists.txt +++ b/lsfg-vk-v3.1/CMakeLists.txt @@ -5,8 +5,6 @@ project(lsfg-vk-v3.1 DESCRIPTION "Lossless Scaling Frame Generation v3.1" LANGUAGES CXX) -option(LDFGVK_EXCESS_DEBUG "Enable excessive debug output" OFF) - file(GLOB SOURCES "src/core/*.cpp" "src/pool/*.cpp" @@ -25,7 +23,7 @@ target_include_directories(lsfg-vk-v3.1 PRIVATE include PUBLIC public) target_link_libraries(lsfg-vk-v3.1 - PRIVATE vulkan) + PUBLIC lsfg-vk-common vulkan) target_compile_options(lsfg-vk-v3.1 PRIVATE -fPIC) diff --git a/lsfg-vk-v3.1/README.md b/lsfg-vk-v3.1/README.md index 016bfa4..238e37b 100644 --- a/lsfg-vk-v3.1/README.md +++ b/lsfg-vk-v3.1/README.md @@ -7,8 +7,8 @@ The project is intentionally structured as a fully external project, such that i ### Interface -Interfacing with lsfg-vk-v3.1 is done via `lsfg.hpp` header. The internal Vulkan instance is created using `LSFG::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::finalize()` after which `LSFG::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. +Interfacing with lsfg-vk-v3.1 is done via `lsfg.hpp` header. The internal Vulkan instance is created using `LSFG_3_1::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_3_1::finalize()` after which `LSFG_3_1::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::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::deleteContext()`. +Once the format and extent of the requested images is determined, `LSFG_3_1::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_3_1::deleteContext()`. -Presenting the context can be done via `LSFG::presentContext()`. Before calling the function a second time, make sure the outgoing semaphores have been signaled. +Presenting the context can be done via `LSFG_3_1::presentContext()`. Before calling the function a second time, make sure the outgoing semaphores have been signaled. diff --git a/lsfg-vk-v3.1/include/context.hpp b/lsfg-vk-v3.1/include/context.hpp index 6283236..3819f02 100644 --- a/lsfg-vk-v3.1/include/context.hpp +++ b/lsfg-vk-v3.1/include/context.hpp @@ -10,7 +10,7 @@ #include "shaders/gamma.hpp" #include "shaders/generate.hpp" #include "shaders/mipmaps.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include diff --git a/lsfg-vk-v3.1/include/shaders/alpha.hpp b/lsfg-vk-v3.1/include/shaders/alpha.hpp index fa48ec2..c837264 100644 --- a/lsfg-vk-v3.1/include/shaders/alpha.hpp +++ b/lsfg-vk-v3.1/include/shaders/alpha.hpp @@ -6,7 +6,7 @@ #include "core/pipeline.hpp" #include "core/sampler.hpp" #include "core/shadermodule.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include #include diff --git a/lsfg-vk-v3.1/include/shaders/beta.hpp b/lsfg-vk-v3.1/include/shaders/beta.hpp index b8e2a2b..6921673 100644 --- a/lsfg-vk-v3.1/include/shaders/beta.hpp +++ b/lsfg-vk-v3.1/include/shaders/beta.hpp @@ -7,7 +7,7 @@ #include "core/pipeline.hpp" #include "core/sampler.hpp" #include "core/shadermodule.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include #include diff --git a/lsfg-vk-v3.1/include/shaders/delta.hpp b/lsfg-vk-v3.1/include/shaders/delta.hpp index 5e81aec..52a64d8 100644 --- a/lsfg-vk-v3.1/include/shaders/delta.hpp +++ b/lsfg-vk-v3.1/include/shaders/delta.hpp @@ -7,7 +7,7 @@ #include "core/pipeline.hpp" #include "core/sampler.hpp" #include "core/shadermodule.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include #include diff --git a/lsfg-vk-v3.1/include/shaders/gamma.hpp b/lsfg-vk-v3.1/include/shaders/gamma.hpp index 0f09ab3..6298bb3 100644 --- a/lsfg-vk-v3.1/include/shaders/gamma.hpp +++ b/lsfg-vk-v3.1/include/shaders/gamma.hpp @@ -7,7 +7,7 @@ #include "core/pipeline.hpp" #include "core/sampler.hpp" #include "core/shadermodule.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include #include diff --git a/lsfg-vk-v3.1/include/shaders/generate.hpp b/lsfg-vk-v3.1/include/shaders/generate.hpp index 1a6ecb7..cf8d5f4 100644 --- a/lsfg-vk-v3.1/include/shaders/generate.hpp +++ b/lsfg-vk-v3.1/include/shaders/generate.hpp @@ -7,7 +7,7 @@ #include "core/pipeline.hpp" #include "core/sampler.hpp" #include "core/shadermodule.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include diff --git a/lsfg-vk-v3.1/include/shaders/mipmaps.hpp b/lsfg-vk-v3.1/include/shaders/mipmaps.hpp index 741c93c..cdffed7 100644 --- a/lsfg-vk-v3.1/include/shaders/mipmaps.hpp +++ b/lsfg-vk-v3.1/include/shaders/mipmaps.hpp @@ -7,7 +7,7 @@ #include "core/pipeline.hpp" #include "core/sampler.hpp" #include "core/shadermodule.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include #include diff --git a/lsfg-vk-v3.1/public/lsfg.hpp b/lsfg-vk-v3.1/public/lsfg.hpp index 3ebfee8..ae8b86e 100644 --- a/lsfg-vk-v3.1/public/lsfg.hpp +++ b/lsfg-vk-v3.1/public/lsfg.hpp @@ -3,12 +3,11 @@ #include #include -#include #include #include #include -namespace LSFG { +namespace LSFG_3_1 { /// /// Initialize the LSFG library. @@ -64,28 +63,4 @@ namespace LSFG { /// void finalize(); - /// 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; - }; - } diff --git a/lsfg-vk-v3.1/src/context.cpp b/lsfg-vk-v3.1/src/context.cpp index 5bd9d51..0e45687 100644 --- a/lsfg-vk-v3.1/src/context.cpp +++ b/lsfg-vk-v3.1/src/context.cpp @@ -1,6 +1,6 @@ #include "context.hpp" -#include "lsfg.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" +#include "common/exception.hpp" #include diff --git a/lsfg-vk-v3.1/src/lsfg.cpp b/lsfg-vk-v3.1/src/lsfg.cpp index 49fc018..7873e8a 100644 --- a/lsfg-vk-v3.1/src/lsfg.cpp +++ b/lsfg-vk-v3.1/src/lsfg.cpp @@ -4,22 +4,22 @@ #include "core/descriptorpool.hpp" #include "core/instance.hpp" #include "pool/shaderpool.hpp" -#include "utils/utils.hpp" +#include "common/exception.hpp" +#include "common/utils.hpp" #include #include +#include #include #include -#include -#include #include #include -#include #include #include using namespace LSFG; +using namespace LSFG_3_1; namespace { std::optional instance; @@ -27,7 +27,7 @@ namespace { std::unordered_map contexts; } -void LSFG::initialize(uint64_t deviceUUID, +void LSFG_3_1::initialize(uint64_t deviceUUID, bool isHdr, float flowScale, uint64_t generationCount, const std::function(const std::string&)>& loader) { if (instance.has_value() || device.has_value()) @@ -51,41 +51,41 @@ void LSFG::initialize(uint64_t deviceUUID, std::srand(static_cast(std::time(nullptr))); } -int32_t LSFG::createContext( +int32_t LSFG_3_1::createContext( int in0, int in1, const std::vector& outN, VkExtent2D extent, VkFormat format) { if (!instance.has_value() || !device.has_value()) - throw vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + 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::presentContext(int32_t id, int inSem, const std::vector& outSem) { +void LSFG_3_1::presentContext(int32_t id, int inSem, const std::vector& outSem) { if (!instance.has_value() || !device.has_value()) - throw vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); auto it = contexts.find(id); if (it == contexts.end()) - throw vulkan_error(VK_ERROR_UNKNOWN, "Context not found"); + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Context not found"); it->second.present(*device, inSem, outSem); } -void LSFG::deleteContext(int32_t id) { +void LSFG_3_1::deleteContext(int32_t id) { if (!instance.has_value() || !device.has_value()) - throw vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); auto it = contexts.find(id); if (it == contexts.end()) - throw vulkan_error(VK_ERROR_DEVICE_LOST, "No such context"); + throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context"); vkDeviceWaitIdle(device->device.handle()); contexts.erase(it); } -void LSFG::finalize() { +void LSFG_3_1::finalize() { if (!instance.has_value() || !device.has_value()) return; @@ -94,8 +94,3 @@ void LSFG::finalize() { device.reset(); instance.reset(); } - -vulkan_error::vulkan_error(VkResult result, const std::string& message) - : std::runtime_error(std::format("{} (error {})", message, static_cast(result))), result(result) {} - -vulkan_error::~vulkan_error() noexcept = default; diff --git a/lsfg-vk-v3.1/src/shaders/alpha.cpp b/lsfg-vk-v3.1/src/shaders/alpha.cpp index 7eb3019..79c555c 100644 --- a/lsfg-vk-v3.1/src/shaders/alpha.cpp +++ b/lsfg-vk-v3.1/src/shaders/alpha.cpp @@ -1,5 +1,5 @@ #include "shaders/alpha.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/beta.cpp b/lsfg-vk-v3.1/src/shaders/beta.cpp index afa185a..64b3fd3 100644 --- a/lsfg-vk-v3.1/src/shaders/beta.cpp +++ b/lsfg-vk-v3.1/src/shaders/beta.cpp @@ -1,5 +1,5 @@ #include "shaders/beta.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/delta.cpp b/lsfg-vk-v3.1/src/shaders/delta.cpp index 548ab39..8c05f20 100644 --- a/lsfg-vk-v3.1/src/shaders/delta.cpp +++ b/lsfg-vk-v3.1/src/shaders/delta.cpp @@ -1,5 +1,5 @@ #include "shaders/delta.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/gamma.cpp b/lsfg-vk-v3.1/src/shaders/gamma.cpp index 647e31a..e703d4e 100644 --- a/lsfg-vk-v3.1/src/shaders/gamma.cpp +++ b/lsfg-vk-v3.1/src/shaders/gamma.cpp @@ -1,5 +1,5 @@ #include "shaders/gamma.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/generate.cpp b/lsfg-vk-v3.1/src/shaders/generate.cpp index 95c2f74..dde317c 100644 --- a/lsfg-vk-v3.1/src/shaders/generate.cpp +++ b/lsfg-vk-v3.1/src/shaders/generate.cpp @@ -1,5 +1,5 @@ #include "shaders/generate.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/mipmaps.cpp b/lsfg-vk-v3.1/src/shaders/mipmaps.cpp index 8bd4a40..eab1e91 100644 --- a/lsfg-vk-v3.1/src/shaders/mipmaps.cpp +++ b/lsfg-vk-v3.1/src/shaders/mipmaps.cpp @@ -1,5 +1,5 @@ #include "shaders/mipmaps.hpp" -#include "utils/utils.hpp" +#include "common/utils.hpp" #include "core/image.hpp" #include "core/commandbuffer.hpp" diff --git a/src/context.cpp b/src/context.cpp index ebbc269..ae1bcdf 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -5,6 +5,7 @@ #include "utils/log.hpp" #include "hooks.hpp" #include "layer.hpp" +#include "common/exception.hpp" #include #include @@ -75,7 +76,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, Log::debug("context", "(entering LSFG initialization)"); setenv("DISABLE_LSFG", "1", 1); // NOLINT Extract::extractShaders(); - LSFG::initialize( + LSFG_3_1::initialize( Utils::getDeviceUUID(info.physicalDevice), isHdr, 1.0F / flowScale, info.frameGen, [](const std::string& name) { @@ -90,12 +91,12 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, // create lsfg context Log::debug("context", "(entering LSFG context creation)"); this->lsfgCtxId = std::shared_ptr( - new int32_t(LSFG::createContext(frame_0_fd, frame_1_fd, out_n_fds, + new int32_t(LSFG_3_1::createContext(frame_0_fd, frame_1_fd, out_n_fds, extent, format)), [](const int32_t* id) { Log::info("context", "(entering LSFG context deletion with id: {})", *id); - LSFG::deleteContext(*id); + LSFG_3_1::deleteContext(*id); Log::info("context", "(exiting LSFG context deletion with id: {})", *id); } @@ -156,7 +157,7 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk Log::debug("context2", "(entering LSFG present with id: {})", *this->lsfgCtxId); - LSFG::presentContext(*this->lsfgCtxId, + LSFG_3_1::presentContext(*this->lsfgCtxId, preCopySemaphoreFd, renderSemaphoreFds); Log::debug("context2", diff --git a/src/hooks.cpp b/src/hooks.cpp index dff7d95..c589b79 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -3,8 +3,8 @@ #include "layer.hpp" #include "utils/log.hpp" #include "utils/utils.hpp" +#include "common/exception.hpp" -#include #include #include diff --git a/src/mini/commandbuffer.cpp b/src/mini/commandbuffer.cpp index d7eb230..3b0e49a 100644 --- a/src/mini/commandbuffer.cpp +++ b/src/mini/commandbuffer.cpp @@ -1,9 +1,9 @@ #include "mini/commandbuffer.hpp" #include "mini/commandpool.hpp" +#include "common/exception.hpp" #include "layer.hpp" #include -#include #include #include diff --git a/src/mini/commandpool.cpp b/src/mini/commandpool.cpp index bd63454..e43c7db 100644 --- a/src/mini/commandpool.cpp +++ b/src/mini/commandpool.cpp @@ -1,7 +1,7 @@ #include "mini/commandpool.hpp" +#include "common/exception.hpp" #include "layer.hpp" -#include #include #include diff --git a/src/mini/image.cpp b/src/mini/image.cpp index 6b74864..978c67c 100644 --- a/src/mini/image.cpp +++ b/src/mini/image.cpp @@ -1,8 +1,8 @@ #include "mini/image.hpp" +#include "common/exception.hpp" #include "layer.hpp" #include -#include #include #include diff --git a/src/mini/semaphore.cpp b/src/mini/semaphore.cpp index c240c87..cd03031 100644 --- a/src/mini/semaphore.cpp +++ b/src/mini/semaphore.cpp @@ -1,7 +1,7 @@ #include "mini/semaphore.hpp" +#include "common/exception.hpp" #include "layer.hpp" -#include #include #include diff --git a/src/utils/benchmark.cpp b/src/utils/benchmark.cpp index 3f805e7..cb1afbf 100644 --- a/src/utils/benchmark.cpp +++ b/src/utils/benchmark.cpp @@ -55,7 +55,7 @@ namespace { ? std::stoull(std::string(lsfgDeviceUUID), nullptr, 16) : 0x1463ABAC; Extract::extractShaders(); - LSFG::initialize( + LSFG_3_1::initialize( deviceUUID, // some magic number if not given isHdr, 1.0F / flowScale, multiplier - 1, [](const std::string& name) -> std::vector { @@ -64,7 +64,7 @@ namespace { return spirv; } ); - const int32_t ctx = LSFG::createContext(-1, -1, {}, + const int32_t ctx = LSFG_3_1::createContext(-1, -1, {}, { .width = width, .height = height }, isHdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM ); @@ -75,7 +75,7 @@ namespace { const auto now = std::chrono::high_resolution_clock::now(); const uint64_t iterations = (8 * 500) + 1; for (uint64_t count = 0; count < iterations; count++) { - LSFG::presentContext(ctx, -1, {}); + LSFG_3_1::presentContext(ctx, -1, {}); if (count % 500 == 0) Log::info("bench", "{:.2f}% done ({}/{})", diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index d09de7f..8340464 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -1,8 +1,8 @@ #include "utils/utils.hpp" #include "utils/log.hpp" +#include "common/exception.hpp" #include "layer.hpp" -#include #include #include From d8a98997472a22235cc0f16444ff866f0c83fe33 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 18:37:44 +0200 Subject: [PATCH 124/253] performance mode base --- CMakeLists.txt | 6 +- include/context.hpp | 1 + .../public/{lsfg.hpp => lsfg_3_1.hpp} | 0 lsfg-vk-v3.1/src/lsfg.cpp | 2 +- lsfg-vk-v3.1p/.clang-tidy | 26 ++ lsfg-vk-v3.1p/.gitattributes | 3 + lsfg-vk-v3.1p/.gitignore | 9 + lsfg-vk-v3.1p/CMakeLists.txt | 62 ++++ lsfg-vk-v3.1p/LICENSE.md | 21 ++ lsfg-vk-v3.1p/README.md | 14 + lsfg-vk-v3.1p/include/context.hpp | 83 +++++ lsfg-vk-v3.1p/include/shaders/alpha.hpp | 60 ++++ lsfg-vk-v3.1p/include/shaders/beta.hpp | 61 ++++ lsfg-vk-v3.1p/include/shaders/delta.hpp | 79 ++++ lsfg-vk-v3.1p/include/shaders/gamma.hpp | 71 ++++ lsfg-vk-v3.1p/include/shaders/generate.hpp | 70 ++++ lsfg-vk-v3.1p/include/shaders/mipmaps.hpp | 59 +++ lsfg-vk-v3.1p/public/lsfg_3_1p.hpp | 66 ++++ lsfg-vk-v3.1p/src/context.cpp | 121 +++++++ lsfg-vk-v3.1p/src/lsfg.cpp | 96 +++++ lsfg-vk-v3.1p/src/shaders/alpha.cpp | 139 +++++++ lsfg-vk-v3.1p/src/shaders/beta.cpp | 161 +++++++++ lsfg-vk-v3.1p/src/shaders/delta.cpp | 340 ++++++++++++++++++ lsfg-vk-v3.1p/src/shaders/gamma.cpp | 192 ++++++++++ lsfg-vk-v3.1p/src/shaders/generate.cpp | 82 +++++ lsfg-vk-v3.1p/src/shaders/mipmaps.cpp | 65 ++++ src/context.cpp | 38 +- src/utils/benchmark.cpp | 21 +- 28 files changed, 1933 insertions(+), 15 deletions(-) rename lsfg-vk-v3.1/public/{lsfg.hpp => lsfg_3_1.hpp} (100%) create mode 100644 lsfg-vk-v3.1p/.clang-tidy create mode 100644 lsfg-vk-v3.1p/.gitattributes create mode 100644 lsfg-vk-v3.1p/.gitignore create mode 100644 lsfg-vk-v3.1p/CMakeLists.txt create mode 100644 lsfg-vk-v3.1p/LICENSE.md create mode 100644 lsfg-vk-v3.1p/README.md create mode 100644 lsfg-vk-v3.1p/include/context.hpp create mode 100644 lsfg-vk-v3.1p/include/shaders/alpha.hpp create mode 100644 lsfg-vk-v3.1p/include/shaders/beta.hpp create mode 100644 lsfg-vk-v3.1p/include/shaders/delta.hpp create mode 100644 lsfg-vk-v3.1p/include/shaders/gamma.hpp create mode 100644 lsfg-vk-v3.1p/include/shaders/generate.hpp create mode 100644 lsfg-vk-v3.1p/include/shaders/mipmaps.hpp create mode 100644 lsfg-vk-v3.1p/public/lsfg_3_1p.hpp create mode 100644 lsfg-vk-v3.1p/src/context.cpp create mode 100644 lsfg-vk-v3.1p/src/lsfg.cpp create mode 100644 lsfg-vk-v3.1p/src/shaders/alpha.cpp create mode 100644 lsfg-vk-v3.1p/src/shaders/beta.cpp create mode 100644 lsfg-vk-v3.1p/src/shaders/delta.cpp create mode 100644 lsfg-vk-v3.1p/src/shaders/gamma.cpp create mode 100644 lsfg-vk-v3.1p/src/shaders/generate.cpp create mode 100644 lsfg-vk-v3.1p/src/shaders/mipmaps.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cd59fc5..ef8c74a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ include(cmake/FetchPeParse.cmake) add_subdirectory(lsfg-vk-common) add_subdirectory(lsfg-vk-v3.1) +add_subdirectory(lsfg-vk-v3.1p) # main project project(lsfg-vk @@ -38,8 +39,9 @@ set_target_properties(lsfg-vk PROPERTIES CXX_STANDARD_REQUIRED ON) target_include_directories(lsfg-vk PRIVATE include) -target_link_libraries(lsfg-vk - PRIVATE lsfg-vk-common lsfg-vk-v3.1 peparse dxvk vulkan) +target_link_libraries(lsfg-vk PRIVATE + lsfg-vk-common lsfg-vk-v3.1 lsfg-vk-v3.1p + peparse dxvk vulkan) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(lsfg-vk PROPERTIES diff --git a/include/context.hpp b/include/context.hpp index 3438e48..37269b2 100644 --- a/include/context.hpp +++ b/include/context.hpp @@ -63,6 +63,7 @@ private: Mini::CommandPool cmdPool; uint64_t frameIdx{0}; + bool isPerfMode{false}; struct RenderPassInfo { Mini::CommandBuffer preCopyBuf; // copy from swapchain image to frame_0/frame_1 diff --git a/lsfg-vk-v3.1/public/lsfg.hpp b/lsfg-vk-v3.1/public/lsfg_3_1.hpp similarity index 100% rename from lsfg-vk-v3.1/public/lsfg.hpp rename to lsfg-vk-v3.1/public/lsfg_3_1.hpp diff --git a/lsfg-vk-v3.1/src/lsfg.cpp b/lsfg-vk-v3.1/src/lsfg.cpp index 7873e8a..75e0992 100644 --- a/lsfg-vk-v3.1/src/lsfg.cpp +++ b/lsfg-vk-v3.1/src/lsfg.cpp @@ -1,4 +1,4 @@ -#include "lsfg.hpp" +#include "lsfg_3_1.hpp" #include "context.hpp" #include "core/commandpool.hpp" #include "core/descriptorpool.hpp" diff --git a/lsfg-vk-v3.1p/.clang-tidy b/lsfg-vk-v3.1p/.clang-tidy new file mode 100644 index 0000000..cf513e9 --- /dev/null +++ b/lsfg-vk-v3.1p/.clang-tidy @@ -0,0 +1,26 @@ +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" +- "-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" diff --git a/lsfg-vk-v3.1p/.gitattributes b/lsfg-vk-v3.1p/.gitattributes new file mode 100644 index 0000000..8d476d4 --- /dev/null +++ b/lsfg-vk-v3.1p/.gitattributes @@ -0,0 +1,3 @@ +*.cpp diff=cpp eol=lf +*.hpp diff=cpp eol=lf +*.md diff=markdown eol=lf diff --git a/lsfg-vk-v3.1p/.gitignore b/lsfg-vk-v3.1p/.gitignore new file mode 100644 index 0000000..43ab8ae --- /dev/null +++ b/lsfg-vk-v3.1p/.gitignore @@ -0,0 +1,9 @@ +# cmake files +/build + +# ide/lsp files +/.zed +/.vscode +/.clangd +/.cache +/.ccls diff --git a/lsfg-vk-v3.1p/CMakeLists.txt b/lsfg-vk-v3.1p/CMakeLists.txt new file mode 100644 index 0000000..6e08b62 --- /dev/null +++ b/lsfg-vk-v3.1p/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.29) + +# project +project(lsfg-vk-v3.1p + DESCRIPTION "Lossless Scaling Frame Generation v3.1 (Performance Mode)" + LANGUAGES CXX) + +file(GLOB SOURCES + "src/core/*.cpp" + "src/pool/*.cpp" + "src/shaders/*.cpp" + "src/utils/*.cpp" + "src/*.cpp" +) + +add_library(lsfg-vk-v3.1p STATIC ${SOURCES}) + +# target +set_target_properties(lsfg-vk-v3.1p PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON) +target_include_directories(lsfg-vk-v3.1p + PRIVATE include + PUBLIC public) +target_link_libraries(lsfg-vk-v3.1p + PUBLIC lsfg-vk-common vulkan) +target_compile_options(lsfg-vk-v3.1p PRIVATE + -fPIC) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set_target_properties(lsfg-vk-v3.1p PROPERTIES + INTERPROCEDURAL_OPTIMIZATION ON) +endif() + +# diagnostics +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set_target_properties(lsfg-vk-v3.1p PROPERTIES + EXPORT_COMPILE_COMMANDS ON) +endif() + +if(LSFGVK_EXCESS_DEBUG) + target_compile_options(lsfg-vk-v3.1p 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-v3.1p PROPERTIES + CXX_CLANG_TIDY clang-tidy) +endif() diff --git a/lsfg-vk-v3.1p/LICENSE.md b/lsfg-vk-v3.1p/LICENSE.md new file mode 100644 index 0000000..b5c8a3e --- /dev/null +++ b/lsfg-vk-v3.1p/LICENSE.md @@ -0,0 +1,21 @@ +## MIT License + +Copyright (c) 2025 lsfg-vk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lsfg-vk-v3.1p/README.md b/lsfg-vk-v3.1p/README.md new file mode 100644 index 0000000..f99ba40 --- /dev/null +++ b/lsfg-vk-v3.1p/README.md @@ -0,0 +1,14 @@ +## lsfg-vk-v3.1p +Version 3.1 (Performance Mode) of Lossless Scaling Frame Generation + +This is a subproject of lsfg-vk and contains the external 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-v3.1p is done via `lsfg.hpp` header. The internal Vulkan instance is created using `LSFG_3_1P::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_3_1P::finalize()` after which `LSFG_3_1P::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_3_1P::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_3_1P::deleteContext()`. + +Presenting the context can be done via `LSFG_3_1P::presentContext()`. Before calling the function a second time, make sure the outgoing semaphores have been signaled. diff --git a/lsfg-vk-v3.1p/include/context.hpp b/lsfg-vk-v3.1p/include/context.hpp new file mode 100644 index 0000000..3819f02 --- /dev/null +++ b/lsfg-vk-v3.1p/include/context.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "core/image.hpp" +#include "core/semaphore.hpp" +#include "core/fence.hpp" +#include "core/commandbuffer.hpp" +#include "shaders/alpha.hpp" +#include "shaders/beta.hpp" +#include "shaders/delta.hpp" +#include "shaders/gamma.hpp" +#include "shaders/generate.hpp" +#include "shaders/mipmaps.hpp" +#include "common/utils.hpp" + +#include + +#include +#include +#include + +namespace LSFG { + + class Context { + public: + /// + /// Create a context + /// + /// @param vk The Vulkan instance to use. + /// @param in0 File descriptor for the first input image. + /// @param in1 File descriptor for the second input image. + /// @param outN File descriptors for the output images. + /// @param extent The size of the images. + /// @param format The format of the images. + /// + /// @throws LSFG::vulkan_error if the context fails to initialize. + /// + Context(Vulkan& vk, + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format); + + /// + /// Present on the context. + /// + /// @param inSem Semaphore to wait on before starting the generation. + /// @param outSem Semaphores to signal after each generation is done. + /// + /// @throws LSFG::vulkan_error if the context fails to present. + /// + void present(Vulkan& vk, + int inSem, const std::vector& outSem); + + // Trivially copyable, moveable and destructible + Context(const Context&) = default; + Context& operator=(const Context&) = default; + Context(Context&&) = default; + Context& operator=(Context&&) = default; + ~Context() = default; + private: + Core::Image inImg_0, inImg_1; // inImg_0 is next when fc % 2 == 0 + uint64_t frameIdx{0}; + + struct RenderData { + Core::Semaphore inSemaphore; // signaled when input is ready + std::vector internalSemaphores; // signaled when first step is done + std::vector outSemaphores; // signaled when each pass is done + std::vector completionFences; // fence for completion of each pass + + Core::CommandBuffer cmdBuffer1; + std::vector cmdBuffers2; // command buffers for second step + + bool shouldWait{false}; + }; + std::array data; + + Shaders::Mipmaps mipmaps; + std::array alpha; + Shaders::Beta beta; + std::array gamma; + std::array delta; + Shaders::Generate generate; + }; + +} diff --git a/lsfg-vk-v3.1p/include/shaders/alpha.hpp b/lsfg-vk-v3.1p/include/shaders/alpha.hpp new file mode 100644 index 0000000..c837264 --- /dev/null +++ b/lsfg-vk-v3.1p/include/shaders/alpha.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include + +namespace LSFG::Shaders { + + /// + /// Alpha shader. + /// + class Alpha { + public: + Alpha() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg One mipmap level + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Alpha(Vulkan& vk, Core::Image inImg); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Alpha(const Alpha&) noexcept = default; + Alpha& operator=(const Alpha&) noexcept = default; + Alpha(Alpha&&) noexcept = default; + Alpha& operator=(Alpha&&) noexcept = default; + ~Alpha() = default; + private: + std::array shaderModules; + std::array pipelines; + Core::Sampler sampler; + std::array descriptorSets; + std::array lastDescriptorSet; + + Core::Image inImg; + std::array tempImgs1; + std::array tempImgs2; + std::array tempImgs3; + std::array, 3> outImgs; + }; + +} diff --git a/lsfg-vk-v3.1p/include/shaders/beta.hpp b/lsfg-vk-v3.1p/include/shaders/beta.hpp new file mode 100644 index 0000000..6921673 --- /dev/null +++ b/lsfg-vk-v3.1p/include/shaders/beta.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include + +namespace LSFG::Shaders { + + /// + /// Beta shader. + /// + class Beta { + public: + Beta() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs Three sets of four RGBA images, corresponding to a frame count % 3. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Beta(Vulkan& vk, std::array, 3> inImgs); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Beta(const Beta&) noexcept = default; + Beta& operator=(const Beta&) noexcept = default; + Beta(Beta&&) noexcept = default; + Beta& operator=(Beta&&) noexcept = default; + ~Beta() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + + std::array, 3> inImgs; + std::array tempImgs1; + std::array tempImgs2; + std::array outImgs; + }; + +} diff --git a/lsfg-vk-v3.1p/include/shaders/delta.hpp b/lsfg-vk-v3.1p/include/shaders/delta.hpp new file mode 100644 index 0000000..52a64d8 --- /dev/null +++ b/lsfg-vk-v3.1p/include/shaders/delta.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include +#include +#include + +namespace LSFG::Shaders { + + /// + /// Delta shader. + /// + class Delta { + public: + Delta() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs1 Three sets of four RGBA images, corresponding to a frame count % 3. + /// @param inImg2 Second Input image + /// @param optImg1 Optional image for non-first passes. + /// @param optImg2 Second optional image for non-first passes. + /// @param optImg3 Third optional image for non-first passes. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Delta(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg1, + std::optional optImg2, + std::optional optImg3); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Get the first output image + [[nodiscard]] const auto& getOutImage1() const { return this->outImg1; } + /// Get the second output image + [[nodiscard]] const auto& getOutImage2() const { return this->outImg2; } + + /// Trivially copyable, moveable and destructible + Delta(const Delta&) noexcept = default; + Delta& operator=(const Delta&) noexcept = default; + Delta(Delta&&) noexcept = default; + Delta& operator=(Delta&&) noexcept = default; + ~Delta() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + struct DeltaPass { + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + std::array sixthDescriptorSet; + }; + std::vector passes; + + std::array, 3> inImgs1; + Core::Image inImg2; + std::optional optImg1, optImg2, optImg3; + std::array tempImgs1; + std::array tempImgs2; + Core::Image outImg1, outImg2; + }; + +} diff --git a/lsfg-vk-v3.1p/include/shaders/gamma.hpp b/lsfg-vk-v3.1p/include/shaders/gamma.hpp new file mode 100644 index 0000000..6298bb3 --- /dev/null +++ b/lsfg-vk-v3.1p/include/shaders/gamma.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include +#include +#include + +namespace LSFG::Shaders { + + /// + /// Gamma shader. + /// + class Gamma { + public: + Gamma() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImgs1 Three sets of four RGBA images, corresponding to a frame count % 3. + /// @param inImg2 Second Input image + /// @param optImg Optional image for non-first passes. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Gamma(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, std::optional optImg); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Get the output image + [[nodiscard]] const auto& getOutImage() const { return this->outImg; } + + /// Trivially copyable, moveable and destructible + Gamma(const Gamma&) noexcept = default; + Gamma& operator=(const Gamma&) noexcept = default; + Gamma(Gamma&&) noexcept = default; + Gamma& operator=(Gamma&&) noexcept = default; + ~Gamma() = default; + private: + std::array shaderModules; + std::array pipelines; + std::array samplers; + struct GammaPass { + Core::Buffer buffer; + std::array firstDescriptorSet; + std::array descriptorSets; + }; + std::vector passes; + + std::array, 3> inImgs1; + Core::Image inImg2; + std::optional optImg; + std::array tempImgs1; + std::array tempImgs2; + Core::Image outImg; + }; + +} diff --git a/lsfg-vk-v3.1p/include/shaders/generate.hpp b/lsfg-vk-v3.1p/include/shaders/generate.hpp new file mode 100644 index 0000000..cf8d5f4 --- /dev/null +++ b/lsfg-vk-v3.1p/include/shaders/generate.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include + +#include +#include +#include + +namespace LSFG::Shaders { + + /// + /// Generate shader. + /// + class Generate { + public: + Generate() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg1 Input image 1. + /// @param inImg2 Input image 2. + /// @param inImg3 Input image 3. + /// @param inImg4 Input image 4. + /// @param inImg5 Input image 5. + /// @param fds File descriptors for the output images. + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Generate(Vulkan& vk, + Core::Image inImg1, Core::Image inImg2, + Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, + const std::vector& fds, VkFormat format); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + + /// Trivially copyable, moveable and destructible + Generate(const Generate&) noexcept = default; + Generate& operator=(const Generate&) noexcept = default; + Generate(Generate&&) noexcept = default; + Generate& operator=(Generate&&) noexcept = default; + ~Generate() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + std::array samplers; + struct GeneratePass { + Core::Buffer buffer; + std::array descriptorSet; + }; + std::vector passes; + + Core::Image inImg1, inImg2; + Core::Image inImg3, inImg4, inImg5; + std::vector outImgs; + }; + +} diff --git a/lsfg-vk-v3.1p/include/shaders/mipmaps.hpp b/lsfg-vk-v3.1p/include/shaders/mipmaps.hpp new file mode 100644 index 0000000..cdffed7 --- /dev/null +++ b/lsfg-vk-v3.1p/include/shaders/mipmaps.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "core/buffer.hpp" +#include "core/commandbuffer.hpp" +#include "core/descriptorset.hpp" +#include "core/image.hpp" +#include "core/pipeline.hpp" +#include "core/sampler.hpp" +#include "core/shadermodule.hpp" +#include "common/utils.hpp" + +#include +#include + +namespace LSFG::Shaders { + + /// + /// Mipmaps shader. + /// + class Mipmaps { + public: + Mipmaps() = default; + + /// + /// Initialize the shaderchain. + /// + /// @param inImg_0 The next frame (when fc % 2 == 0) + /// @param inImg_1 The next frame (when fc % 2 == 1) + /// + /// @throws LSFG::vulkan_error if resource creation fails. + /// + Mipmaps(Vulkan& vk, Core::Image inImg_0, Core::Image inImg_1); + + /// + /// Dispatch the shaderchain. + /// + void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount); + + /// Get the output images. + [[nodiscard]] const auto& getOutImages() const { return this->outImgs; } + + /// Trivially copyable, moveable and destructible + Mipmaps(const Mipmaps&) noexcept = default; + Mipmaps& operator=(const Mipmaps&) noexcept = default; + Mipmaps(Mipmaps&&) noexcept = default; + Mipmaps& operator=(Mipmaps&&) noexcept = default; + ~Mipmaps() = default; + private: + Core::ShaderModule shaderModule; + Core::Pipeline pipeline; + Core::Buffer buffer; + Core::Sampler sampler; + std::array descriptorSets; + + Core::Image inImg_0, inImg_1; + std::array outImgs; + }; + +} diff --git a/lsfg-vk-v3.1p/public/lsfg_3_1p.hpp b/lsfg-vk-v3.1p/public/lsfg_3_1p.hpp new file mode 100644 index 0000000..27417ce --- /dev/null +++ b/lsfg-vk-v3.1p/public/lsfg_3_1p.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace LSFG_3_1P { + + /// + /// Initialize the LSFG library. + /// + /// @param deviceUUID The UUID of the Vulkan device to use. + /// @param isHdr Whether the images are in HDR format. + /// @param flowScale Internal flow scale factor. + /// @param generationCount Number of frames to generate. + /// @param loader Function to load shader source code by name. + /// + /// @throws LSFG::vulkan_error if Vulkan objects fail to initialize. + /// + void initialize(uint64_t deviceUUID, + bool isHdr, float flowScale, uint64_t generationCount, + const std::function(const std::string&)>& loader); + + /// + /// Create a new LSFG context on a swapchain. + /// + /// @param in0 File descriptor for the first input image. + /// @param in1 File descriptor for the second input image. + /// @param outN File descriptor for each output image. This defines the LSFG level. + /// @param extent The size of the images + /// @param format The format of the images. + /// @return A unique identifier for the created context. + /// + /// @throws LSFG::vulkan_error if the context cannot be created. + /// + int32_t createContext( + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format); + + /// + /// Present a context. + /// + /// @param id Unique identifier of the context to present. + /// @param inSem Semaphore to wait on before starting the generation. + /// @param outSem Semaphores to signal once each output image is ready. + /// + /// @throws LSFG::vulkan_error if the context cannot be presented. + /// + void presentContext(int32_t id, int inSem, const std::vector& outSem); + + /// + /// Delete an LSFG context. + /// + /// @param id Unique identifier of the context to delete. + /// + void deleteContext(int32_t id); + + /// + /// Deinitialize the LSFG library. + /// + void finalize(); + +} diff --git a/lsfg-vk-v3.1p/src/context.cpp b/lsfg-vk-v3.1p/src/context.cpp new file mode 100644 index 0000000..0e45687 --- /dev/null +++ b/lsfg-vk-v3.1p/src/context.cpp @@ -0,0 +1,121 @@ +#include "context.hpp" +#include "common/utils.hpp" +#include "common/exception.hpp" + +#include + +#include +#include +#include +#include +#include + +using namespace LSFG; + +Context::Context(Vulkan& vk, + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format) { + // import input images + this->inImg_0 = Core::Image(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, in0); + this->inImg_1 = Core::Image(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, in1); + + // prepare render data + for (size_t i = 0; i < 8; i++) { + auto& data = this->data.at(i); + data.internalSemaphores.resize(vk.generationCount); + data.outSemaphores.resize(vk.generationCount); + data.completionFences.resize(vk.generationCount); + data.cmdBuffers2.resize(vk.generationCount); + } + + // create shader chains + this->mipmaps = Shaders::Mipmaps(vk, this->inImg_0, this->inImg_1); + for (size_t i = 0; i < 7; i++) + this->alpha.at(i) = Shaders::Alpha(vk, this->mipmaps.getOutImages().at(i)); + this->beta = Shaders::Beta(vk, this->alpha.at(0).getOutImages()); + for (size_t i = 0; i < 7; i++) { + this->gamma.at(i) = Shaders::Gamma(vk, + this->alpha.at(6 - i).getOutImages(), + this->beta.getOutImages().at(std::min(6 - i, 5)), + (i == 0) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage())); + if (i < 4) continue; + + this->delta.at(i - 4) = Shaders::Delta(vk, + this->alpha.at(6 - i).getOutImages(), + this->beta.getOutImages().at(6 - i), + (i == 4) ? std::nullopt : std::make_optional(this->gamma.at(i - 1).getOutImage()), + (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage1()), + (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage2())); + } + this->generate = Shaders::Generate(vk, + this->inImg_0, this->inImg_1, + this->gamma.at(6).getOutImage(), + this->delta.at(2).getOutImage1(), + this->delta.at(2).getOutImage2(), + outN, format); +} + +void Context::present(Vulkan& vk, + int inSem, const std::vector& outSem) { + auto& data = this->data.at(this->frameIdx % 8); + + // 3. wait for completion of previous frame in this slot + if (data.shouldWait) + for (auto& fence : data.completionFences) + if (!fence.wait(vk.device, UINT64_MAX)) + throw LSFG::vulkan_error(VK_TIMEOUT, "Fence wait timed out"); + data.shouldWait = true; + + // 1. create mipmaps and process input image + if (inSem >= 0) data.inSemaphore = Core::Semaphore(vk.device, inSem); + for (size_t i = 0; i < vk.generationCount; i++) + data.internalSemaphores.at(i) = Core::Semaphore(vk.device); + + data.cmdBuffer1 = Core::CommandBuffer(vk.device, vk.commandPool); + data.cmdBuffer1.begin(); + + this->mipmaps.Dispatch(data.cmdBuffer1, this->frameIdx); + for (size_t i = 0; i < 7; i++) + this->alpha.at(6 - i).Dispatch(data.cmdBuffer1, this->frameIdx); + this->beta.Dispatch(data.cmdBuffer1, this->frameIdx); + + data.cmdBuffer1.end(); + std::vector waits = { data.inSemaphore }; + if (inSem < 0) waits.clear(); + data.cmdBuffer1.submit(vk.device.getComputeQueue(), std::nullopt, + waits, std::nullopt, + data.internalSemaphores, std::nullopt); + + // 2. generate intermediary frames + for (size_t pass = 0; pass < vk.generationCount; pass++) { + auto& internalSemaphore = data.internalSemaphores.at(pass); + auto& outSemaphore = data.outSemaphores.at(pass); + if (inSem >= 0) outSemaphore = Core::Semaphore(vk.device, outSem.empty() ? -1 : outSem.at(pass)); + auto& completionFence = data.completionFences.at(pass); + completionFence = Core::Fence(vk.device); + + auto& buf2 = data.cmdBuffers2.at(pass); + buf2 = Core::CommandBuffer(vk.device, vk.commandPool); + buf2.begin(); + + for (size_t i = 0; i < 7; i++) { + this->gamma.at(i).Dispatch(buf2, this->frameIdx, pass); + if (i >= 4) + this->delta.at(i - 4).Dispatch(buf2, this->frameIdx, pass); + } + this->generate.Dispatch(buf2, this->frameIdx, pass); + + buf2.end(); + std::vector signals = { outSemaphore }; + if (inSem < 0) signals.clear(); + buf2.submit(vk.device.getComputeQueue(), completionFence, + { internalSemaphore }, std::nullopt, + signals, std::nullopt); + } + + this->frameIdx++; +} diff --git a/lsfg-vk-v3.1p/src/lsfg.cpp b/lsfg-vk-v3.1p/src/lsfg.cpp new file mode 100644 index 0000000..276385a --- /dev/null +++ b/lsfg-vk-v3.1p/src/lsfg.cpp @@ -0,0 +1,96 @@ +#include "lsfg_3_1p.hpp" +#include "context.hpp" +#include "core/commandpool.hpp" +#include "core/descriptorpool.hpp" +#include "core/instance.hpp" +#include "pool/shaderpool.hpp" +#include "common/exception.hpp" +#include "common/utils.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace LSFG; +using namespace LSFG_3_1P; + +namespace { + std::optional instance; + std::optional device; + std::unordered_map contexts; +} + +void LSFG_3_1P::initialize(uint64_t deviceUUID, + bool isHdr, float flowScale, uint64_t generationCount, + const std::function(const std::string&)>& loader) { + if (instance.has_value() || device.has_value()) + return; + + instance.emplace(); + device.emplace(Vulkan { + .device{*instance, deviceUUID}, + .generationCount = generationCount, + .flowScale = flowScale, + .isHdr = isHdr + }); + contexts = std::unordered_map(); + + device->commandPool = Core::CommandPool(device->device); + device->descriptorPool = Core::DescriptorPool(device->device); + + device->resources = Pool::ResourcePool(device->isHdr, device->flowScale); + device->shaders = Pool::ShaderPool(loader); + + std::srand(static_cast(std::time(nullptr))); +} + +int32_t LSFG_3_1P::createContext( + int in0, int in1, const std::vector& outN, + VkExtent2D extent, VkFormat format) { + if (!instance.has_value() || !device.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + const int32_t id = std::rand(); + contexts.emplace(id, Context(*device, in0, in1, outN, extent, format)); + return id; +} + +void LSFG_3_1P::presentContext(int32_t id, int inSem, const std::vector& outSem) { + if (!instance.has_value() || !device.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + auto it = contexts.find(id); + if (it == contexts.end()) + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Context not found"); + + it->second.present(*device, inSem, outSem); +} + +void LSFG_3_1P::deleteContext(int32_t id) { + if (!instance.has_value() || !device.has_value()) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "LSFG not initialized"); + + auto it = contexts.find(id); + if (it == contexts.end()) + throw LSFG::vulkan_error(VK_ERROR_DEVICE_LOST, "No such context"); + + vkDeviceWaitIdle(device->device.handle()); + contexts.erase(it); +} + +void LSFG_3_1P::finalize() { + if (!instance.has_value() || !device.has_value()) + return; + + vkDeviceWaitIdle(device->device.handle()); + contexts.clear(); + device.reset(); + instance.reset(); +} diff --git a/lsfg-vk-v3.1p/src/shaders/alpha.cpp b/lsfg-vk-v3.1p/src/shaders/alpha.cpp new file mode 100644 index 0000000..79c555c --- /dev/null +++ b/lsfg-vk-v3.1p/src/shaders/alpha.cpp @@ -0,0 +1,139 @@ +#include "shaders/alpha.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include +#include +#include + +using namespace LSFG::Shaders; + +Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "alpha[0]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "alpha[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "alpha[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "alpha[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "alpha[0]"), + vk.shaders.getPipeline(vk.device, "alpha[1]"), + vk.shaders.getPipeline(vk.device, "alpha[2]"), + vk.shaders.getPipeline(vk.device, "alpha[3]") + }}; + this->sampler = vk.resources.getSampler(vk.device); + for (size_t i = 0; i < 3; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i)); + for (size_t i = 0; i < 3; i++) + this->lastDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(3)); + + // create internal images/outputs + const VkExtent2D extent = this->inImg.getExtent(); + const VkExtent2D halfExtent = { + .width = (extent.width + 1) >> 1, + .height = (extent.height + 1) >> 1 + }; + for (size_t i = 0; i < 2; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, halfExtent); + this->tempImgs2.at(i) = Core::Image(vk.device, halfExtent); + } + + const VkExtent2D quarterExtent = { + .width = (halfExtent.width + 1) >> 1, + .height = (halfExtent.height + 1) >> 1 + }; + for (size_t i = 0; i < 4; i++) { + this->tempImgs3.at(i) = Core::Image(vk.device, quarterExtent); + for (size_t j = 0; j < 3; j++) + this->outImgs.at(j).at(i) = Core::Image(vk.device, quarterExtent); + } + + // hook up shaders + this->descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + this->descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs3) + .build(); + for (size_t i = 0; i < 3; i++) + this->lastDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i)) + .build(); +} + +void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto halfExtent = this->tempImgs1.at(0).getExtent(); + uint32_t threadsX = (halfExtent.width + 7) >> 3; + uint32_t threadsY = (halfExtent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImg) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(0).bind(buf); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third pass + const auto quarterExtent = this->tempImgs3.at(0).getExtent(); + threadsX = (quarterExtent.width + 7) >> 3; + threadsY = (quarterExtent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs3) + .build(); + + this->pipelines.at(2).bind(buf); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs3) + .addR2W(this->outImgs.at(frameCount % 3)) + .build(); + + this->pipelines.at(3).bind(buf); + this->lastDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1p/src/shaders/beta.cpp b/lsfg-vk-v3.1p/src/shaders/beta.cpp new file mode 100644 index 0000000..64b3fd3 --- /dev/null +++ b/lsfg-vk-v3.1p/src/shaders/beta.cpp @@ -0,0 +1,161 @@ +#include "shaders/beta.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include +#include +#include +#include + +using namespace LSFG::Shaders; + +Beta::Beta(Vulkan& vk, std::array, 3> inImgs) + : inImgs(std::move(inImgs)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "beta[0]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 12, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "beta[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "beta[0]"), + vk.shaders.getPipeline(vk.device, "beta[1]"), + vk.shaders.getPipeline(vk.device, "beta[2]"), + vk.shaders.getPipeline(vk.device, "beta[3]"), + vk.shaders.getPipeline(vk.device, "beta[4]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + for (size_t i = 0; i < 3; i++) + this->firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(0)); + for (size_t i = 0; i < 4; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModules.at(i + 1)); + this->buffer = vk.resources.getBuffer(vk.device, 0.5F); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs.at(0).at(0).getExtent(); + for (size_t i = 0; i < 2; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + } + + for (size_t i = 0; i < 6; i++) + this->outImgs.at(i) = Core::Image(vk.device, + { extent.width >> i, extent.height >> i }, + VK_FORMAT_R8_UNORM); + + // hook up shaders + for (size_t i = 0; i < 3; i++) { + this->firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 1) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + } + this->descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + this->descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + this->descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .build(); +} + +void Beta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto extent = this->tempImgs1.at(0).getExtent(); + uint32_t threadsX = (extent.width + 7) >> 3; + uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs.at(0)) + .addW2R(this->inImgs.at(1)) + .addW2R(this->inImgs.at(2)) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(0).bind(buf); + this->firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + this->descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + this->descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth pass + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + this->descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth pass + threadsX = (extent.width + 31) >> 5; + threadsY = (extent.height + 31) >> 5; + + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->outImgs) + .build(); + + this->pipelines.at(4).bind(buf); + this->descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1p/src/shaders/delta.cpp b/lsfg-vk-v3.1p/src/shaders/delta.cpp new file mode 100644 index 0000000..8c05f20 --- /dev/null +++ b/lsfg-vk-v3.1p/src/shaders/delta.cpp @@ -0,0 +1,340 @@ +#include "shaders/delta.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include +#include +#include +#include +#include + +using namespace LSFG::Shaders; + +Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg1, + std::optional optImg2, + std::optional optImg3) + : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), + optImg1(std::move(optImg1)), optImg2(std::move(optImg2)), + optImg3(std::move(optImg3)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "delta[0]", + { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 9, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[5]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 10, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[6]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[7]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[8]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "delta[9]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "delta[0]"), + vk.shaders.getPipeline(vk.device, "delta[1]"), + vk.shaders.getPipeline(vk.device, "delta[2]"), + vk.shaders.getPipeline(vk.device, "delta[3]"), + vk.shaders.getPipeline(vk.device, "delta[4]"), + vk.shaders.getPipeline(vk.device, "delta[5]"), + vk.shaders.getPipeline(vk.device, "delta[6]"), + vk.shaders.getPipeline(vk.device, "delta[7]"), + vk.shaders.getPipeline(vk.device, "delta[8]"), + vk.shaders.getPipeline(vk.device, "delta[9]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + this->samplers.at(2) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); + for (size_t i = 0; i < 4; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + } + + this->outImg1 = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + this->outImg2 = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + + // hook up shaders + for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(pass_idx + 1) / static_cast(vk.generationCount + 1), + false, !this->optImg1.has_value()); + for (size_t i = 0; i < 3; i++) { + pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(0)); + pass.firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(2)) + .build(); + } + pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(1)); + pass.descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(2)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(2)); + pass.descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(3)); + pass.descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(4)); + pass.descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg1) + .build(); + for (size_t i = 0; i < 3; i++) { + pass.sixthDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(5)); + pass.sixthDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(1)) + .build(); + } + pass.descriptorSets.at(4) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(6)); + pass.descriptorSets.at(4).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .build(); + pass.descriptorSets.at(5) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(7)); + pass.descriptorSets.at(5).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2.at(1)) + .build(); + pass.descriptorSets.at(6) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(8)); + pass.descriptorSets.at(6).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .build(); + pass.descriptorSets.at(7) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(9)); + pass.descriptorSets.at(7).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg3) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg2) + .build(); + } +} + +void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first shader + const auto extent = this->tempImgs1.at(0).getExtent(); + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg1) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .addR2W(this->tempImgs1.at(2)) + .build(); + + this->pipelines.at(0).bind(buf); + pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addW2R(this->tempImgs1.at(2)) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addW2R(this->optImg1) + .addW2R(this->inImg2) + .addR2W(this->outImg1) + .build(); + + this->pipelines.at(4).bind(buf); + pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); + + // sixth shader + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg1) + .addW2R(this->optImg2) + .addR2W(this->tempImgs2.at(0)) + .addR2W(this->tempImgs2.at(1)) + .build(); + + this->pipelines.at(5).bind(buf); + pass.sixthDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(5)); + buf.dispatch(threadsX, threadsY, 1); + + // seventh shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2.at(0)) + .addW2R(this->tempImgs2.at(1)) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .build(); + + this->pipelines.at(6).bind(buf); + pass.descriptorSets.at(4).bind(buf, this->pipelines.at(6)); + buf.dispatch(threadsX, threadsY, 1); + + // eighth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addR2W(this->tempImgs2.at(0)) + .addR2W(this->tempImgs2.at(1)) + .build(); + this->pipelines.at(7).bind(buf); + pass.descriptorSets.at(5).bind(buf, this->pipelines.at(7)); + buf.dispatch(threadsX, threadsY, 1); + + // ninth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2.at(0)) + .addW2R(this->tempImgs2.at(1)) + .addW2R(this->optImg3) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .build(); + + this->pipelines.at(8).bind(buf); + pass.descriptorSets.at(6).bind(buf, this->pipelines.at(8)); + buf.dispatch(threadsX, threadsY, 1); + + // tenth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addW2R(this->optImg3) + .addR2W(this->outImg2) + .build(); + + this->pipelines.at(9).bind(buf); + pass.descriptorSets.at(7).bind(buf, this->pipelines.at(9)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1p/src/shaders/gamma.cpp b/lsfg-vk-v3.1p/src/shaders/gamma.cpp new file mode 100644 index 0000000..e703d4e --- /dev/null +++ b/lsfg-vk-v3.1p/src/shaders/gamma.cpp @@ -0,0 +1,192 @@ +#include "shaders/gamma.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include +#include +#include +#include +#include + +using namespace LSFG::Shaders; + +Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, + Core::Image inImg2, + std::optional optImg) + : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), + optImg(std::move(optImg)) { + // create resources + this->shaderModules = {{ + vk.shaders.getShader(vk.device, "gamma[0]", + { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 9, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[1]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[2]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[3]", + { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + vk.shaders.getShader(vk.device, "gamma[4]", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) + }}; + this->pipelines = {{ + vk.shaders.getPipeline(vk.device, "gamma[0]"), + vk.shaders.getPipeline(vk.device, "gamma[1]"), + vk.shaders.getPipeline(vk.device, "gamma[2]"), + vk.shaders.getPipeline(vk.device, "gamma[3]"), + vk.shaders.getPipeline(vk.device, "gamma[4]") + }}; + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + this->samplers.at(2) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + // create internal images/outputs + const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); + for (size_t i = 0; i < 4; i++) { + this->tempImgs1.at(i) = Core::Image(vk.device, extent); + this->tempImgs2.at(i) = Core::Image(vk.device, extent); + } + + this->outImg = Core::Image(vk.device, + { extent.width, extent.height }, + VK_FORMAT_R16G16B16A16_SFLOAT); + + // hook up shaders + for (size_t pass_idx = 0; pass_idx < vk.generationCount; pass_idx++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(pass_idx + 1) / static_cast(vk.generationCount + 1), + !this->optImg.has_value()); + for (size_t i = 0; i < 3; i++) { + pass.firstDescriptorSet.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(0)); + pass.firstDescriptorSet.at(i).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at((i + 2) % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImgs1.at(i % 3)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1.at(2)) + .build(); + } + pass.descriptorSets.at(0) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(1)); + pass.descriptorSets.at(0).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(1)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1.at(2)) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(2)); + pass.descriptorSets.at(1).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) + .build(); + pass.descriptorSets.at(2) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(3)); + pass.descriptorSets.at(2).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .build(); + pass.descriptorSets.at(3) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModules.at(4)); + pass.descriptorSets.at(3).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(0)) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers.at(2)) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->tempImgs2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->optImg) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg2) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImg) + .build(); + } +} + +void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first shader + const auto extent = this->tempImgs1.at(0).getExtent(); + const uint32_t threadsX = (extent.width + 7) >> 3; + const uint32_t threadsY = (extent.height + 7) >> 3; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImgs1.at((frameCount + 2) % 3)) + .addW2R(this->inImgs1.at(frameCount % 3)) + .addW2R(this->optImg) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) + .addR2W(this->tempImgs1.at(2)) + .build(); + + this->pipelines.at(0).bind(buf); + pass.firstDescriptorSet.at(frameCount % 3).bind(buf, this->pipelines.at(0)); + buf.dispatch(threadsX, threadsY, 1); + + // second shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) + .addW2R(this->tempImgs1.at(2)) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(1).bind(buf); + pass.descriptorSets.at(0).bind(buf, this->pipelines.at(1)); + buf.dispatch(threadsX, threadsY, 1); + + // third shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addR2W(this->tempImgs1) + .build(); + + this->pipelines.at(2).bind(buf); + pass.descriptorSets.at(1).bind(buf, this->pipelines.at(2)); + buf.dispatch(threadsX, threadsY, 1); + + // fourth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs1) + .addR2W(this->tempImgs2) + .build(); + + this->pipelines.at(3).bind(buf); + pass.descriptorSets.at(2).bind(buf, this->pipelines.at(3)); + buf.dispatch(threadsX, threadsY, 1); + + // fifth shader + Utils::BarrierBuilder(buf) + .addW2R(this->tempImgs2) + .addW2R(this->optImg) + .addW2R(this->inImg2) + .addR2W(this->outImg) + .build(); + + this->pipelines.at(4).bind(buf); + pass.descriptorSets.at(3).bind(buf, this->pipelines.at(4)); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1p/src/shaders/generate.cpp b/lsfg-vk-v3.1p/src/shaders/generate.cpp new file mode 100644 index 0000000..dde317c --- /dev/null +++ b/lsfg-vk-v3.1p/src/shaders/generate.cpp @@ -0,0 +1,82 @@ +#include "shaders/generate.hpp" +#include "common/utils.hpp" +#include "core/commandbuffer.hpp" +#include "core/image.hpp" + +#include + +#include +#include +#include +#include + +using namespace LSFG::Shaders; + +Generate::Generate(Vulkan& vk, + Core::Image inImg1, Core::Image inImg2, + Core::Image inImg3, Core::Image inImg4, Core::Image inImg5, + const std::vector& fds, VkFormat format) + : inImg1(std::move(inImg1)), inImg2(std::move(inImg2)), + inImg3(std::move(inImg3)), inImg4(std::move(inImg4)), + inImg5(std::move(inImg5)) { + // create resources + this->shaderModule = vk.shaders.getShader(vk.device, "generate", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); + this->pipeline = vk.shaders.getPipeline(vk.device, "generate"); + this->samplers.at(0) = vk.resources.getSampler(vk.device); + this->samplers.at(1) = vk.resources.getSampler(vk.device, + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS); + + // create internal images/outputs + const VkExtent2D extent = this->inImg1.getExtent(); + for (size_t i = 0; i < vk.generationCount; i++) + this->outImgs.emplace_back(vk.device, extent, format, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, fds.empty() ? -1 : fds.at(i)); + + // hook up shaders + for (size_t i = 0; i < vk.generationCount; i++) { + auto& pass = this->passes.emplace_back(); + pass.buffer = vk.resources.getBuffer(vk.device, + static_cast(i + 1) / static_cast(vk.generationCount + 1)); + for (size_t j = 0; j < 2; j++) { + pass.descriptorSet.at(j) = Core::DescriptorSet(vk.device, vk.descriptorPool, + this->shaderModule); + pass.descriptorSet.at(j).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, pass.buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->samplers) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg2 : this->inImg1) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, j == 0 ? this->inImg1 : this->inImg2) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg3) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg4) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, this->inImg5) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs.at(i)) + .build(); + } + } +} + +void Generate::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx) { + auto& pass = this->passes.at(pass_idx); + + // first pass + const auto extent = this->inImg1.getExtent(); + const uint32_t threadsX = (extent.width + 15) >> 4; + const uint32_t threadsY = (extent.height + 15) >> 4; + + Utils::BarrierBuilder(buf) + .addW2R(this->inImg1) + .addW2R(this->inImg2) + .addW2R(this->inImg3) + .addW2R(this->inImg4) + .addW2R(this->inImg5) + .addR2W(this->outImgs.at(pass_idx)) + .build(); + + this->pipeline.bind(buf); + pass.descriptorSet.at(frameCount % 2).bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp b/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp new file mode 100644 index 0000000..eab1e91 --- /dev/null +++ b/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp @@ -0,0 +1,65 @@ +#include "shaders/mipmaps.hpp" +#include "common/utils.hpp" +#include "core/image.hpp" +#include "core/commandbuffer.hpp" + +#include + +#include +#include +#include + +using namespace LSFG::Shaders; + +Mipmaps::Mipmaps(Vulkan& vk, + Core::Image inImg_0, Core::Image inImg_1) + : inImg_0(std::move(inImg_0)), inImg_1(std::move(inImg_1)) { + // create resources + this->shaderModule = vk.shaders.getShader(vk.device, "mipmaps", + { { 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }); + this->pipeline = vk.shaders.getPipeline(vk.device, "mipmaps"); + this->buffer = vk.resources.getBuffer(vk.device); + this->sampler = vk.resources.getSampler(vk.device); + for (size_t i = 0; i < 2; i++) + this->descriptorSets.at(i) = Core::DescriptorSet(vk.device, vk.descriptorPool, this->shaderModule); + + // create outputs + const VkExtent2D flowExtent{ + .width = static_cast( + static_cast(this->inImg_0.getExtent().width) / vk.flowScale), + .height = static_cast( + static_cast(this->inImg_0.getExtent().height) / vk.flowScale) + }; + for (size_t i = 0; i < 7; i++) + this->outImgs.at(i) = Core::Image(vk.device, + { flowExtent.width >> i, flowExtent.height >> i }, + VK_FORMAT_R8_UNORM); + + // hook up shaders + for (size_t fc = 0; fc < 2; fc++) + this->descriptorSets.at(fc).update(vk.device) + .add(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, this->buffer) + .add(VK_DESCRIPTOR_TYPE_SAMPLER, this->sampler) + .add(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, (fc % 2 == 0) ? this->inImg_0 : this->inImg_1) + .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->outImgs) + .build(); +} + +void Mipmaps::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { + // first pass + const auto flowExtent = this->outImgs.at(0).getExtent(); + const uint32_t threadsX = (flowExtent.width + 63) >> 6; + const uint32_t threadsY = (flowExtent.height + 63) >> 6; + + Utils::BarrierBuilder(buf) + .addW2R((frameCount % 2 == 0) ? this->inImg_0 : this->inImg_1) + .addR2W(this->outImgs) + .build(); + + this->pipeline.bind(buf); + this->descriptorSets.at(frameCount % 2).bind(buf, this->pipeline); + buf.dispatch(threadsX, threadsY, 1); +} diff --git a/src/context.cpp b/src/context.cpp index ae1bcdf..e9f5ec8 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -8,7 +8,8 @@ #include "common/exception.hpp" #include -#include +#include +#include #include #include @@ -33,6 +34,11 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, ? *lsfgHdrStr == '1' : false; + const char* lsfgPerfModeStr = getenv("LSFG_PERF_MODE"); + const bool perfMode = lsfgPerfModeStr + ? *lsfgPerfModeStr == '1' + : false; + // we could take the format from the swapchain, // but honestly this is safer. const VkFormat format = isHdr @@ -72,11 +78,21 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, i, out_n_fds.at(i)); } + auto* lsfgInitialize = LSFG_3_1::initialize; + auto* lsfgCreateContext = LSFG_3_1::createContext; + auto* lsfgDeleteContext = LSFG_3_1::deleteContext; + if (perfMode) { + Log::debug("context", "Using performance mode"); + this->isPerfMode = true; + lsfgInitialize = LSFG_3_1P::initialize; + lsfgCreateContext = LSFG_3_1P::createContext; + lsfgDeleteContext = LSFG_3_1P::deleteContext; + } // initialize lsfg Log::debug("context", "(entering LSFG initialization)"); setenv("DISABLE_LSFG", "1", 1); // NOLINT Extract::extractShaders(); - LSFG_3_1::initialize( + lsfgInitialize( Utils::getDeviceUUID(info.physicalDevice), isHdr, 1.0F / flowScale, info.frameGen, [](const std::string& name) { @@ -91,12 +107,12 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, // create lsfg context Log::debug("context", "(entering LSFG context creation)"); this->lsfgCtxId = std::shared_ptr( - new int32_t(LSFG_3_1::createContext(frame_0_fd, frame_1_fd, out_n_fds, + new int32_t(lsfgCreateContext(frame_0_fd, frame_1_fd, out_n_fds, extent, format)), - [](const int32_t* id) { + [lsfgDeleteContext = lsfgDeleteContext](const int32_t* id) { Log::info("context", "(entering LSFG context deletion with id: {})", *id); - LSFG_3_1::deleteContext(*id); + lsfgDeleteContext(*id); Log::info("context", "(exiting LSFG context deletion with id: {})", *id); } @@ -157,9 +173,15 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk Log::debug("context2", "(entering LSFG present with id: {})", *this->lsfgCtxId); - LSFG_3_1::presentContext(*this->lsfgCtxId, - preCopySemaphoreFd, - renderSemaphoreFds); + if (this->isPerfMode) { + LSFG_3_1P::presentContext(*this->lsfgCtxId, + preCopySemaphoreFd, + renderSemaphoreFds); + } else { + LSFG_3_1::presentContext(*this->lsfgCtxId, + preCopySemaphoreFd, + renderSemaphoreFds); + } Log::debug("context2", "(exiting LSFG present with id: {})", *this->lsfgCtxId); diff --git a/src/utils/benchmark.cpp b/src/utils/benchmark.cpp index cb1afbf..47b94fc 100644 --- a/src/utils/benchmark.cpp +++ b/src/utils/benchmark.cpp @@ -3,7 +3,8 @@ #include "utils/log.hpp" #include -#include +#include +#include #include #include @@ -34,6 +35,7 @@ namespace { const char* lsfgMultiplier = std::getenv("LSFG_MULTIPLIER"); const char* lsfgExtentWidth = std::getenv("LSFG_EXTENT_WIDTH"); const char* lsfgExtentHeight = std::getenv("LSFG_EXTENT_HEIGHT"); + const char* lsfgPerfMode = std::getenv("LSFG_PERF_MODE"); const float flowScale = lsfgFlowScale ? std::stof(lsfgFlowScale) : 1.0F; @@ -45,6 +47,17 @@ namespace { ? static_cast(std::stoul(lsfgExtentWidth)) : 1920; const uint32_t height = lsfgExtentHeight ? static_cast(std::stoul(lsfgExtentHeight)) : 1080; + const bool perfMode = lsfgPerfMode + ? *lsfgPerfMode == '1' : false; + + auto* lsfgInitialize = LSFG_3_1::initialize; + auto* lsfgCreateContext = LSFG_3_1::createContext; + auto* lsfgPresentContext = LSFG_3_1::presentContext; + if (perfMode) { + lsfgInitialize = LSFG_3_1P::initialize; + lsfgCreateContext = LSFG_3_1P::createContext; + lsfgPresentContext = LSFG_3_1P::presentContext; + } Log::info("bench", "Running {}x benchmark with {}x{} extent and flow scale of {} {} HDR", multiplier, width, height, flowScale, isHdr ? "with" : "without"); @@ -55,7 +68,7 @@ namespace { ? std::stoull(std::string(lsfgDeviceUUID), nullptr, 16) : 0x1463ABAC; Extract::extractShaders(); - LSFG_3_1::initialize( + lsfgInitialize( deviceUUID, // some magic number if not given isHdr, 1.0F / flowScale, multiplier - 1, [](const std::string& name) -> std::vector { @@ -64,7 +77,7 @@ namespace { return spirv; } ); - const int32_t ctx = LSFG_3_1::createContext(-1, -1, {}, + const int32_t ctx = lsfgCreateContext(-1, -1, {}, { .width = width, .height = height }, isHdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM ); @@ -75,7 +88,7 @@ namespace { const auto now = std::chrono::high_resolution_clock::now(); const uint64_t iterations = (8 * 500) + 1; for (uint64_t count = 0; count < iterations; count++) { - LSFG_3_1::presentContext(ctx, -1, {}); + lsfgPresentContext(ctx, -1, {}); if (count % 500 == 0) Log::info("bench", "{:.2f}% done ({}/{})", From 0b4caa71269bf25150cb5352130619c1864f1ef7 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 18:55:04 +0200 Subject: [PATCH 125/253] update shader hashes --- src/extract/extract.cpp | 48 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index c61f3da..21fdb40 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -15,30 +15,30 @@ using namespace Extract; const std::unordered_map nameHashTable = {{ { "mipmaps", 0xe365474d }, - { "alpha[0]", 0xf37c8aa8 }, - { "alpha[1]", 0xeb7a52d4 }, - { "alpha[2]", 0x8901788e }, - { "alpha[3]", 0xa06a5e36 }, - { "beta[0]", 0x63c16b89 }, - { "beta[1]", 0xe3967ed5 }, - { "beta[2]", 0x570085ee }, - { "beta[3]", 0x4f4530db }, - { "beta[4]", 0x39727389 }, - { "gamma[0]", 0x94c4edf6 }, - { "gamma[1]", 0xf4e32702 }, - { "gamma[2]", 0xa3dc56fc }, - { "gamma[3]", 0x8b5ed8f6 }, - { "gamma[4]", 0x1cbf3c4d }, - { "delta[0]", 0x94c4edf6 }, - { "delta[1]", 0xacfd805b }, - { "delta[2]", 0x891dc48b }, - { "delta[3]", 0x98536d9d }, - { "delta[4]", 0x8e3f2155 }, - { "delta[5]", 0x8f0e70a1 }, - { "delta[6]", 0xd5eca8f1 }, - { "delta[7]", 0xa9e54e37 }, - { "delta[8]", 0x1dee8b84 }, - { "delta[9]", 0x1576c3f5 }, + { "alpha[0]", 0x0bf6c705 }, + { "alpha[1]", 0x4badbc24 }, + { "alpha[2]", 0x08a0f71c }, + { "alpha[3]", 0x5cc4d794 }, + { "beta[0]", 0x598efc9c }, + { "beta[1]", 0x17e25e8d }, + { "beta[2]", 0x6b8971f7 }, + { "beta[3]", 0x80a351c9 }, + { "beta[4]", 0xda0fcd5a }, + { "gamma[0]", 0x9dae0419 }, + { "gamma[1]", 0x96285646 }, + { "gamma[2]", 0xca44bb67 }, + { "gamma[3]", 0xa942fb59 }, + { "gamma[4]", 0x31996b8d }, + { "delta[0]", 0x9dae0419 }, + { "delta[1]", 0x67912aaa }, + { "delta[2]", 0xec138d48 }, + { "delta[3]", 0xdcb8247f }, + { "delta[4]", 0xd37c9bc8 }, + { "delta[5]", 0x62973dce }, + { "delta[6]", 0xe2ed5f66 }, + { "delta[7]", 0xc964a42a }, + { "delta[8]", 0x74536ad9 }, + { "delta[9]", 0xf472482a }, { "generate", 0x5c040bd5 } }}; From 2d66e1cade3fcae10022103b7337e7dad8d158cc Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 18:55:31 +0200 Subject: [PATCH 126/253] lsfg-vk-v3.1p: alpha shaders --- lsfg-vk-v3.1p/include/shaders/alpha.hpp | 8 ++--- lsfg-vk-v3.1p/src/shaders/alpha.cpp | 40 ++++++++++++------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/lsfg-vk-v3.1p/include/shaders/alpha.hpp b/lsfg-vk-v3.1p/include/shaders/alpha.hpp index c837264..5aad2dc 100644 --- a/lsfg-vk-v3.1p/include/shaders/alpha.hpp +++ b/lsfg-vk-v3.1p/include/shaders/alpha.hpp @@ -51,10 +51,10 @@ namespace LSFG::Shaders { std::array lastDescriptorSet; Core::Image inImg; - std::array tempImgs1; - std::array tempImgs2; - std::array tempImgs3; - std::array, 3> outImgs; + Core::Image tempImg1; + Core::Image tempImg2; + std::array tempImgs3; + std::array, 3> outImgs; }; } diff --git a/lsfg-vk-v3.1p/src/shaders/alpha.cpp b/lsfg-vk-v3.1p/src/shaders/alpha.cpp index 79c555c..c8ae075 100644 --- a/lsfg-vk-v3.1p/src/shaders/alpha.cpp +++ b/lsfg-vk-v3.1p/src/shaders/alpha.cpp @@ -17,19 +17,19 @@ Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) { vk.shaders.getShader(vk.device, "alpha[0]", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, - { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), + { 1, 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 } }), + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, 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 } }), + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, 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 } }) + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; this->pipelines = {{ vk.shaders.getPipeline(vk.device, "alpha[0]"), @@ -49,16 +49,14 @@ Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) { .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); - } + 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 < 4; i++) { + 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); @@ -68,16 +66,16 @@ Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) { 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) + .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->tempImgs1) - .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) + .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->tempImgs2) + .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++) @@ -90,13 +88,13 @@ Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) { void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { // first pass - const auto halfExtent = this->tempImgs1.at(0).getExtent(); + 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->tempImgs1) + .addR2W(this->tempImg1) .build(); this->pipelines.at(0).bind(buf); @@ -105,8 +103,8 @@ void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { // second pass Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs1) - .addR2W(this->tempImgs2) + .addW2R(this->tempImg1) + .addR2W(this->tempImg2) .build(); this->pipelines.at(1).bind(buf); @@ -119,7 +117,7 @@ void Alpha::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount) { threadsY = (quarterExtent.height + 7) >> 3; Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs2) + .addW2R(this->tempImg2) .addR2W(this->tempImgs3) .build(); From 3442c9c72216dacc3448d1cbd6ad3bb2a4e2cd4f Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 18:59:09 +0200 Subject: [PATCH 127/253] lsfg-vk-v3.1p: beta shaders --- lsfg-vk-v3.1p/include/shaders/beta.hpp | 6 +++--- lsfg-vk-v3.1p/src/shaders/beta.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lsfg-vk-v3.1p/include/shaders/beta.hpp b/lsfg-vk-v3.1p/include/shaders/beta.hpp index 6921673..11f6ccc 100644 --- a/lsfg-vk-v3.1p/include/shaders/beta.hpp +++ b/lsfg-vk-v3.1p/include/shaders/beta.hpp @@ -24,11 +24,11 @@ namespace LSFG::Shaders { /// /// Initialize the shaderchain. /// - /// @param inImgs Three sets of four RGBA images, corresponding to a frame count % 3. + /// @param inImgs Three sets of two RGBA images, corresponding to a frame count % 3. /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Beta(Vulkan& vk, std::array, 3> inImgs); + Beta(Vulkan& vk, std::array, 3> inImgs); /// /// Dispatch the shaderchain. @@ -52,7 +52,7 @@ namespace LSFG::Shaders { std::array firstDescriptorSet; std::array descriptorSets; - std::array, 3> inImgs; + std::array, 3> inImgs; std::array tempImgs1; std::array tempImgs2; std::array outImgs; diff --git a/lsfg-vk-v3.1p/src/shaders/beta.cpp b/lsfg-vk-v3.1p/src/shaders/beta.cpp index 64b3fd3..27f554c 100644 --- a/lsfg-vk-v3.1p/src/shaders/beta.cpp +++ b/lsfg-vk-v3.1p/src/shaders/beta.cpp @@ -12,13 +12,13 @@ using namespace LSFG::Shaders; -Beta::Beta(Vulkan& vk, std::array, 3> inImgs) +Beta::Beta(Vulkan& vk, std::array, 3> inImgs) : inImgs(std::move(inImgs)) { // create resources this->shaderModules = {{ vk.shaders.getShader(vk.device, "beta[0]", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 12, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }), vk.shaders.getShader(vk.device, "beta[1]", { { 1, VK_DESCRIPTOR_TYPE_SAMPLER }, From 191fc3301e9ecccf455dee2672725c01c90d4b6c Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 19:01:45 +0200 Subject: [PATCH 128/253] lsfg-vk-v3.1p: gamma shaders --- lsfg-vk-v3.1p/include/shaders/gamma.hpp | 10 +++++----- lsfg-vk-v3.1p/src/shaders/gamma.cpp | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lsfg-vk-v3.1p/include/shaders/gamma.hpp b/lsfg-vk-v3.1p/include/shaders/gamma.hpp index 6298bb3..fa8e8e4 100644 --- a/lsfg-vk-v3.1p/include/shaders/gamma.hpp +++ b/lsfg-vk-v3.1p/include/shaders/gamma.hpp @@ -26,13 +26,13 @@ namespace LSFG::Shaders { /// /// Initialize the shaderchain. /// - /// @param inImgs1 Three sets of four RGBA images, corresponding to a frame count % 3. + /// @param inImgs1 Three sets of two RGBA images, corresponding to a frame count % 3. /// @param inImg2 Second Input image /// @param optImg Optional image for non-first passes. /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Gamma(Vulkan& vk, std::array, 3> inImgs1, + Gamma(Vulkan& vk, std::array, 3> inImgs1, Core::Image inImg2, std::optional optImg); /// @@ -60,11 +60,11 @@ namespace LSFG::Shaders { }; std::vector passes; - std::array, 3> inImgs1; + std::array, 3> inImgs1; Core::Image inImg2; std::optional optImg; - std::array tempImgs1; - std::array tempImgs2; + std::array tempImgs1; + std::array tempImgs2; Core::Image outImg; }; diff --git a/lsfg-vk-v3.1p/src/shaders/gamma.cpp b/lsfg-vk-v3.1p/src/shaders/gamma.cpp index e703d4e..325eb81 100644 --- a/lsfg-vk-v3.1p/src/shaders/gamma.cpp +++ b/lsfg-vk-v3.1p/src/shaders/gamma.cpp @@ -13,7 +13,7 @@ using namespace LSFG::Shaders; -Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, +Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, Core::Image inImg2, std::optional optImg) : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), @@ -23,24 +23,24 @@ Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, vk.shaders.getShader(vk.device, "gamma[0]", { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 9, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 5, 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 } }), + { 2, 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 } }), + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, 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 } }), + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, 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 }, + { 4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; this->pipelines = {{ @@ -58,7 +58,7 @@ Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, // create internal images/outputs const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); - for (size_t i = 0; i < 4; i++) { + 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); } From ec4be495eadc46fa3eb0509fcdfd391fd0c7dd60 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 19:08:47 +0200 Subject: [PATCH 129/253] lsfg-vk-v3.1p: delta shaders --- lsfg-vk-v3.1p/include/shaders/delta.hpp | 10 ++-- lsfg-vk-v3.1p/src/shaders/delta.cpp | 80 ++++++++++--------------- 2 files changed, 36 insertions(+), 54 deletions(-) diff --git a/lsfg-vk-v3.1p/include/shaders/delta.hpp b/lsfg-vk-v3.1p/include/shaders/delta.hpp index 52a64d8..c04375c 100644 --- a/lsfg-vk-v3.1p/include/shaders/delta.hpp +++ b/lsfg-vk-v3.1p/include/shaders/delta.hpp @@ -26,7 +26,7 @@ namespace LSFG::Shaders { /// /// Initialize the shaderchain. /// - /// @param inImgs1 Three sets of four RGBA images, corresponding to a frame count % 3. + /// @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. @@ -34,7 +34,7 @@ namespace LSFG::Shaders { /// /// @throws LSFG::vulkan_error if resource creation fails. /// - Delta(Vulkan& vk, std::array, 3> inImgs1, + Delta(Vulkan& vk, std::array, 3> inImgs1, Core::Image inImg2, std::optional optImg1, std::optional optImg2, @@ -68,11 +68,11 @@ namespace LSFG::Shaders { }; std::vector passes; - std::array, 3> inImgs1; + std::array, 3> inImgs1; Core::Image inImg2; std::optional optImg1, optImg2, optImg3; - std::array tempImgs1; - std::array tempImgs2; + std::array tempImgs1; + std::array tempImgs2; Core::Image outImg1, outImg2; }; diff --git a/lsfg-vk-v3.1p/src/shaders/delta.cpp b/lsfg-vk-v3.1p/src/shaders/delta.cpp index 8c05f20..ce87e39 100644 --- a/lsfg-vk-v3.1p/src/shaders/delta.cpp +++ b/lsfg-vk-v3.1p/src/shaders/delta.cpp @@ -13,7 +13,7 @@ using namespace LSFG::Shaders; -Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, +Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, Core::Image inImg2, std::optional optImg1, std::optional optImg2, @@ -26,46 +26,46 @@ Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, vk.shaders.getShader(vk.device, "delta[0]", { { 1 , VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }, { 2, VK_DESCRIPTOR_TYPE_SAMPLER }, - { 9, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 5, 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 } }), + { 2, 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 } }), + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, 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 } }), + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 2, 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 }, + { 4, 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 } }), + { 6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, 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 } }), + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, 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 } }), + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, 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 } }), + { 1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, + { 1, 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 }, + { 2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE }, { 1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE } }) }}; this->pipelines = {{ @@ -88,10 +88,10 @@ Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, // create internal images/outputs const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); - for (size_t i = 0; i < 4; i++) { + 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 }, @@ -116,18 +116,14 @@ Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, .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)) + .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.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_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) .build(); pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool, @@ -135,13 +131,15 @@ Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, 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) + .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) + .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, @@ -167,7 +165,6 @@ Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, .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, @@ -175,27 +172,21 @@ Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, 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)); @@ -204,7 +195,6 @@ Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, .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(); @@ -223,9 +213,7 @@ void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 .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)) + .addR2W(this->tempImgs1) .build(); this->pipelines.at(0).bind(buf); @@ -234,9 +222,7 @@ void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 // second shader Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs1.at(0)) - .addW2R(this->tempImgs1.at(1)) - .addW2R(this->tempImgs1.at(2)) + .addW2R(this->tempImgs1) .addR2W(this->tempImgs2) .build(); @@ -282,8 +268,7 @@ void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 .addW2R(this->inImgs1.at(frameCount % 3)) .addW2R(this->optImg1) .addW2R(this->optImg2) - .addR2W(this->tempImgs2.at(0)) - .addR2W(this->tempImgs2.at(1)) + .addR2W(this->tempImgs2) .build(); this->pipelines.at(5).bind(buf); @@ -292,8 +277,7 @@ void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 // seventh shader Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs2.at(0)) - .addW2R(this->tempImgs2.at(1)) + .addW2R(this->tempImgs2) .addR2W(this->tempImgs1.at(0)) .addR2W(this->tempImgs1.at(1)) .build(); @@ -306,8 +290,7 @@ void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 Utils::BarrierBuilder(buf) .addW2R(this->tempImgs1.at(0)) .addW2R(this->tempImgs1.at(1)) - .addR2W(this->tempImgs2.at(0)) - .addR2W(this->tempImgs2.at(1)) + .addR2W(this->tempImgs2) .build(); this->pipelines.at(7).bind(buf); pass.descriptorSets.at(5).bind(buf, this->pipelines.at(7)); @@ -315,8 +298,7 @@ void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 // ninth shader Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs2.at(0)) - .addW2R(this->tempImgs2.at(1)) + .addW2R(this->tempImgs2) .addW2R(this->optImg3) .addR2W(this->tempImgs1.at(0)) .addR2W(this->tempImgs1.at(1)) From 2fde1bbf1041732fc18e1556feeb2a0154ca7bf0 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 19:41:30 +0200 Subject: [PATCH 130/253] lsfg-vk-v3.1p: optimizing delta --- lsfg-vk-v3.1p/include/shaders/delta.hpp | 9 ++++----- lsfg-vk-v3.1p/src/context.cpp | 5 ++--- lsfg-vk-v3.1p/src/shaders/delta.cpp | 18 +++++++++--------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lsfg-vk-v3.1p/include/shaders/delta.hpp b/lsfg-vk-v3.1p/include/shaders/delta.hpp index c04375c..72d9262 100644 --- a/lsfg-vk-v3.1p/include/shaders/delta.hpp +++ b/lsfg-vk-v3.1p/include/shaders/delta.hpp @@ -30,20 +30,19 @@ namespace LSFG::Shaders { /// @param inImg2 Second Input image /// @param optImg1 Optional image for non-first passes. /// @param optImg2 Second optional image for non-first passes. - /// @param optImg3 Third optional image for non-first passes. /// /// @throws LSFG::vulkan_error if resource creation fails. /// Delta(Vulkan& vk, std::array, 3> inImgs1, Core::Image inImg2, std::optional optImg1, - std::optional optImg2, - std::optional optImg3); + std::optional optImg2); /// /// Dispatch the shaderchain. /// - void Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64_t pass_idx); + 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; } @@ -70,7 +69,7 @@ namespace LSFG::Shaders { std::array, 3> inImgs1; Core::Image inImg2; - std::optional optImg1, optImg2, optImg3; + std::optional optImg1, optImg2; std::array tempImgs1; std::array tempImgs2; Core::Image outImg1, outImg2; diff --git a/lsfg-vk-v3.1p/src/context.cpp b/lsfg-vk-v3.1p/src/context.cpp index 0e45687..327c4a7 100644 --- a/lsfg-vk-v3.1p/src/context.cpp +++ b/lsfg-vk-v3.1p/src/context.cpp @@ -48,8 +48,7 @@ Context::Context(Vulkan& 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())); + (i == 4) ? std::nullopt : std::make_optional(this->delta.at(i - 5).getOutImage1())); } this->generate = Shaders::Generate(vk, this->inImg_0, this->inImg_1, @@ -105,7 +104,7 @@ void Context::present(Vulkan& vk, 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->delta.at(i - 4).Dispatch(buf2, this->frameIdx, pass, i == 6); } this->generate.Dispatch(buf2, this->frameIdx, pass); diff --git a/lsfg-vk-v3.1p/src/shaders/delta.cpp b/lsfg-vk-v3.1p/src/shaders/delta.cpp index ce87e39..2c77c00 100644 --- a/lsfg-vk-v3.1p/src/shaders/delta.cpp +++ b/lsfg-vk-v3.1p/src/shaders/delta.cpp @@ -16,11 +16,9 @@ using namespace LSFG::Shaders; Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, Core::Image inImg2, std::optional optImg1, - std::optional optImg2, - std::optional optImg3) + std::optional optImg2) : inImgs1(std::move(inImgs1)), inImg2(std::move(inImg2)), - optImg1(std::move(optImg1)), optImg2(std::move(optImg2)), - optImg3(std::move(optImg3)) { + optImg1(std::move(optImg1)), optImg2(std::move(optImg2)) { // create resources this->shaderModules = {{ vk.shaders.getShader(vk.device, "delta[0]", @@ -115,7 +113,7 @@ Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, .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) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs1) .build(); } @@ -195,13 +193,14 @@ Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, .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->optImg3) + .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) { +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 @@ -275,6 +274,9 @@ void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 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) @@ -299,7 +301,6 @@ void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 // ninth shader Utils::BarrierBuilder(buf) .addW2R(this->tempImgs2) - .addW2R(this->optImg3) .addR2W(this->tempImgs1.at(0)) .addR2W(this->tempImgs1.at(1)) .build(); @@ -312,7 +313,6 @@ void Delta::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 Utils::BarrierBuilder(buf) .addW2R(this->tempImgs1.at(0)) .addW2R(this->tempImgs1.at(1)) - .addW2R(this->optImg3) .addR2W(this->outImg2) .build(); From 1576edcf1a4e0e9e45fe729b6145a506e8d9060c Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 19:51:55 +0200 Subject: [PATCH 131/253] avoid symbol clashing --- lsfg-vk-v3.1/include/context.hpp | 4 +++- lsfg-vk-v3.1/include/shaders/alpha.hpp | 4 +++- lsfg-vk-v3.1/include/shaders/beta.hpp | 4 +++- lsfg-vk-v3.1/include/shaders/delta.hpp | 4 +++- lsfg-vk-v3.1/include/shaders/gamma.hpp | 4 +++- lsfg-vk-v3.1/include/shaders/generate.hpp | 4 +++- lsfg-vk-v3.1/include/shaders/mipmaps.hpp | 4 +++- lsfg-vk-v3.1/src/context.cpp | 2 +- lsfg-vk-v3.1/src/shaders/alpha.cpp | 2 +- lsfg-vk-v3.1/src/shaders/beta.cpp | 2 +- lsfg-vk-v3.1/src/shaders/delta.cpp | 2 +- lsfg-vk-v3.1/src/shaders/gamma.cpp | 2 +- lsfg-vk-v3.1/src/shaders/generate.cpp | 2 +- lsfg-vk-v3.1/src/shaders/mipmaps.cpp | 2 +- lsfg-vk-v3.1p/include/context.hpp | 4 +++- lsfg-vk-v3.1p/include/shaders/alpha.hpp | 4 +++- lsfg-vk-v3.1p/include/shaders/beta.hpp | 4 +++- lsfg-vk-v3.1p/include/shaders/delta.hpp | 4 +++- lsfg-vk-v3.1p/include/shaders/gamma.hpp | 4 +++- lsfg-vk-v3.1p/include/shaders/generate.hpp | 4 +++- lsfg-vk-v3.1p/include/shaders/mipmaps.hpp | 4 +++- lsfg-vk-v3.1p/src/context.cpp | 1 + lsfg-vk-v3.1p/src/shaders/alpha.cpp | 2 +- lsfg-vk-v3.1p/src/shaders/beta.cpp | 2 +- lsfg-vk-v3.1p/src/shaders/delta.cpp | 2 +- lsfg-vk-v3.1p/src/shaders/gamma.cpp | 2 +- lsfg-vk-v3.1p/src/shaders/generate.cpp | 2 +- lsfg-vk-v3.1p/src/shaders/mipmaps.cpp | 2 +- src/context.cpp | 1 + 29 files changed, 57 insertions(+), 27 deletions(-) diff --git a/lsfg-vk-v3.1/include/context.hpp b/lsfg-vk-v3.1/include/context.hpp index 3819f02..16e3ffa 100644 --- a/lsfg-vk-v3.1/include/context.hpp +++ b/lsfg-vk-v3.1/include/context.hpp @@ -18,7 +18,9 @@ #include #include -namespace LSFG { +namespace LSFG_3_1 { + + using namespace LSFG; class Context { public: diff --git a/lsfg-vk-v3.1/include/shaders/alpha.hpp b/lsfg-vk-v3.1/include/shaders/alpha.hpp index c837264..11e516b 100644 --- a/lsfg-vk-v3.1/include/shaders/alpha.hpp +++ b/lsfg-vk-v3.1/include/shaders/alpha.hpp @@ -11,7 +11,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1::Shaders { + + using namespace LSFG; /// /// Alpha shader. diff --git a/lsfg-vk-v3.1/include/shaders/beta.hpp b/lsfg-vk-v3.1/include/shaders/beta.hpp index 6921673..58fd974 100644 --- a/lsfg-vk-v3.1/include/shaders/beta.hpp +++ b/lsfg-vk-v3.1/include/shaders/beta.hpp @@ -12,7 +12,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1::Shaders { + + using namespace LSFG; /// /// Beta shader. diff --git a/lsfg-vk-v3.1/include/shaders/delta.hpp b/lsfg-vk-v3.1/include/shaders/delta.hpp index 52a64d8..e671223 100644 --- a/lsfg-vk-v3.1/include/shaders/delta.hpp +++ b/lsfg-vk-v3.1/include/shaders/delta.hpp @@ -14,7 +14,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1::Shaders { + + using namespace LSFG; /// /// Delta shader. diff --git a/lsfg-vk-v3.1/include/shaders/gamma.hpp b/lsfg-vk-v3.1/include/shaders/gamma.hpp index 6298bb3..f3b4f73 100644 --- a/lsfg-vk-v3.1/include/shaders/gamma.hpp +++ b/lsfg-vk-v3.1/include/shaders/gamma.hpp @@ -14,7 +14,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1::Shaders { + + using namespace LSFG; /// /// Gamma shader. diff --git a/lsfg-vk-v3.1/include/shaders/generate.hpp b/lsfg-vk-v3.1/include/shaders/generate.hpp index cf8d5f4..5d63ef2 100644 --- a/lsfg-vk-v3.1/include/shaders/generate.hpp +++ b/lsfg-vk-v3.1/include/shaders/generate.hpp @@ -15,7 +15,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1::Shaders { + + using namespace LSFG; /// /// Generate shader. diff --git a/lsfg-vk-v3.1/include/shaders/mipmaps.hpp b/lsfg-vk-v3.1/include/shaders/mipmaps.hpp index cdffed7..e3595b4 100644 --- a/lsfg-vk-v3.1/include/shaders/mipmaps.hpp +++ b/lsfg-vk-v3.1/include/shaders/mipmaps.hpp @@ -12,7 +12,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1::Shaders { + + using namespace LSFG; /// /// Mipmaps shader. diff --git a/lsfg-vk-v3.1/src/context.cpp b/lsfg-vk-v3.1/src/context.cpp index 0e45687..362d337 100644 --- a/lsfg-vk-v3.1/src/context.cpp +++ b/lsfg-vk-v3.1/src/context.cpp @@ -10,7 +10,7 @@ #include #include -using namespace LSFG; +using namespace LSFG_3_1; Context::Context(Vulkan& vk, int in0, int in1, const std::vector& outN, diff --git a/lsfg-vk-v3.1/src/shaders/alpha.cpp b/lsfg-vk-v3.1/src/shaders/alpha.cpp index 79c555c..58b03ec 100644 --- a/lsfg-vk-v3.1/src/shaders/alpha.cpp +++ b/lsfg-vk-v3.1/src/shaders/alpha.cpp @@ -9,7 +9,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1::Shaders; Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) { // create resources diff --git a/lsfg-vk-v3.1/src/shaders/beta.cpp b/lsfg-vk-v3.1/src/shaders/beta.cpp index 64b3fd3..96c49b5 100644 --- a/lsfg-vk-v3.1/src/shaders/beta.cpp +++ b/lsfg-vk-v3.1/src/shaders/beta.cpp @@ -10,7 +10,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1::Shaders; Beta::Beta(Vulkan& vk, std::array, 3> inImgs) : inImgs(std::move(inImgs)) { diff --git a/lsfg-vk-v3.1/src/shaders/delta.cpp b/lsfg-vk-v3.1/src/shaders/delta.cpp index 8c05f20..1183e51 100644 --- a/lsfg-vk-v3.1/src/shaders/delta.cpp +++ b/lsfg-vk-v3.1/src/shaders/delta.cpp @@ -11,7 +11,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1::Shaders; Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, Core::Image inImg2, diff --git a/lsfg-vk-v3.1/src/shaders/gamma.cpp b/lsfg-vk-v3.1/src/shaders/gamma.cpp index e703d4e..baf0c0f 100644 --- a/lsfg-vk-v3.1/src/shaders/gamma.cpp +++ b/lsfg-vk-v3.1/src/shaders/gamma.cpp @@ -11,7 +11,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1::Shaders; Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, Core::Image inImg2, diff --git a/lsfg-vk-v3.1/src/shaders/generate.cpp b/lsfg-vk-v3.1/src/shaders/generate.cpp index dde317c..6d2569b 100644 --- a/lsfg-vk-v3.1/src/shaders/generate.cpp +++ b/lsfg-vk-v3.1/src/shaders/generate.cpp @@ -10,7 +10,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1::Shaders; Generate::Generate(Vulkan& vk, Core::Image inImg1, Core::Image inImg2, diff --git a/lsfg-vk-v3.1/src/shaders/mipmaps.cpp b/lsfg-vk-v3.1/src/shaders/mipmaps.cpp index eab1e91..0bc5065 100644 --- a/lsfg-vk-v3.1/src/shaders/mipmaps.cpp +++ b/lsfg-vk-v3.1/src/shaders/mipmaps.cpp @@ -9,7 +9,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1::Shaders; Mipmaps::Mipmaps(Vulkan& vk, Core::Image inImg_0, Core::Image inImg_1) diff --git a/lsfg-vk-v3.1p/include/context.hpp b/lsfg-vk-v3.1p/include/context.hpp index 3819f02..ce965a0 100644 --- a/lsfg-vk-v3.1p/include/context.hpp +++ b/lsfg-vk-v3.1p/include/context.hpp @@ -18,7 +18,9 @@ #include #include -namespace LSFG { +namespace LSFG_3_1P { + + using namespace LSFG; class Context { public: diff --git a/lsfg-vk-v3.1p/include/shaders/alpha.hpp b/lsfg-vk-v3.1p/include/shaders/alpha.hpp index 5aad2dc..c816285 100644 --- a/lsfg-vk-v3.1p/include/shaders/alpha.hpp +++ b/lsfg-vk-v3.1p/include/shaders/alpha.hpp @@ -11,7 +11,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; /// /// Alpha shader. diff --git a/lsfg-vk-v3.1p/include/shaders/beta.hpp b/lsfg-vk-v3.1p/include/shaders/beta.hpp index 11f6ccc..549aa9e 100644 --- a/lsfg-vk-v3.1p/include/shaders/beta.hpp +++ b/lsfg-vk-v3.1p/include/shaders/beta.hpp @@ -12,7 +12,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; /// /// Beta shader. diff --git a/lsfg-vk-v3.1p/include/shaders/delta.hpp b/lsfg-vk-v3.1p/include/shaders/delta.hpp index 72d9262..fdbf3d1 100644 --- a/lsfg-vk-v3.1p/include/shaders/delta.hpp +++ b/lsfg-vk-v3.1p/include/shaders/delta.hpp @@ -14,7 +14,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; /// /// Delta shader. diff --git a/lsfg-vk-v3.1p/include/shaders/gamma.hpp b/lsfg-vk-v3.1p/include/shaders/gamma.hpp index fa8e8e4..b4e24ab 100644 --- a/lsfg-vk-v3.1p/include/shaders/gamma.hpp +++ b/lsfg-vk-v3.1p/include/shaders/gamma.hpp @@ -14,7 +14,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; /// /// Gamma shader. diff --git a/lsfg-vk-v3.1p/include/shaders/generate.hpp b/lsfg-vk-v3.1p/include/shaders/generate.hpp index cf8d5f4..42f1e7b 100644 --- a/lsfg-vk-v3.1p/include/shaders/generate.hpp +++ b/lsfg-vk-v3.1p/include/shaders/generate.hpp @@ -15,7 +15,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; /// /// Generate shader. diff --git a/lsfg-vk-v3.1p/include/shaders/mipmaps.hpp b/lsfg-vk-v3.1p/include/shaders/mipmaps.hpp index cdffed7..ce0dca1 100644 --- a/lsfg-vk-v3.1p/include/shaders/mipmaps.hpp +++ b/lsfg-vk-v3.1p/include/shaders/mipmaps.hpp @@ -12,7 +12,9 @@ #include #include -namespace LSFG::Shaders { +namespace LSFG_3_1P::Shaders { + + using namespace LSFG; /// /// Mipmaps shader. diff --git a/lsfg-vk-v3.1p/src/context.cpp b/lsfg-vk-v3.1p/src/context.cpp index 327c4a7..5a18353 100644 --- a/lsfg-vk-v3.1p/src/context.cpp +++ b/lsfg-vk-v3.1p/src/context.cpp @@ -11,6 +11,7 @@ #include using namespace LSFG; +using namespace LSFG_3_1P; Context::Context(Vulkan& vk, int in0, int in1, const std::vector& outN, diff --git a/lsfg-vk-v3.1p/src/shaders/alpha.cpp b/lsfg-vk-v3.1p/src/shaders/alpha.cpp index c8ae075..f572f4c 100644 --- a/lsfg-vk-v3.1p/src/shaders/alpha.cpp +++ b/lsfg-vk-v3.1p/src/shaders/alpha.cpp @@ -9,7 +9,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1P::Shaders; Alpha::Alpha(Vulkan& vk, Core::Image inImg) : inImg(std::move(inImg)) { // create resources diff --git a/lsfg-vk-v3.1p/src/shaders/beta.cpp b/lsfg-vk-v3.1p/src/shaders/beta.cpp index 27f554c..1a24825 100644 --- a/lsfg-vk-v3.1p/src/shaders/beta.cpp +++ b/lsfg-vk-v3.1p/src/shaders/beta.cpp @@ -10,7 +10,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1P::Shaders; Beta::Beta(Vulkan& vk, std::array, 3> inImgs) : inImgs(std::move(inImgs)) { diff --git a/lsfg-vk-v3.1p/src/shaders/delta.cpp b/lsfg-vk-v3.1p/src/shaders/delta.cpp index 2c77c00..c479945 100644 --- a/lsfg-vk-v3.1p/src/shaders/delta.cpp +++ b/lsfg-vk-v3.1p/src/shaders/delta.cpp @@ -11,7 +11,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1P::Shaders; Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, Core::Image inImg2, diff --git a/lsfg-vk-v3.1p/src/shaders/gamma.cpp b/lsfg-vk-v3.1p/src/shaders/gamma.cpp index 325eb81..ccbe932 100644 --- a/lsfg-vk-v3.1p/src/shaders/gamma.cpp +++ b/lsfg-vk-v3.1p/src/shaders/gamma.cpp @@ -11,7 +11,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1P::Shaders; Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, Core::Image inImg2, diff --git a/lsfg-vk-v3.1p/src/shaders/generate.cpp b/lsfg-vk-v3.1p/src/shaders/generate.cpp index dde317c..7772217 100644 --- a/lsfg-vk-v3.1p/src/shaders/generate.cpp +++ b/lsfg-vk-v3.1p/src/shaders/generate.cpp @@ -10,7 +10,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1P::Shaders; Generate::Generate(Vulkan& vk, Core::Image inImg1, Core::Image inImg2, diff --git a/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp b/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp index eab1e91..971055f 100644 --- a/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp +++ b/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp @@ -9,7 +9,7 @@ #include #include -using namespace LSFG::Shaders; +using namespace LSFG_3_1P::Shaders; Mipmaps::Mipmaps(Vulkan& vk, Core::Image inImg_0, Core::Image inImg_1) diff --git a/src/context.cpp b/src/context.cpp index e9f5ec8..712ef23 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -78,6 +78,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, i, out_n_fds.at(i)); } + this->isPerfMode = false; auto* lsfgInitialize = LSFG_3_1::initialize; auto* lsfgCreateContext = LSFG_3_1::createContext; auto* lsfgDeleteContext = LSFG_3_1::deleteContext; From 260998cbf5986f9b5e2bc3573307b75b49329065 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 20:15:22 +0200 Subject: [PATCH 132/253] don't override the previous shaders.. fixes #41 --- lsfg-vk-v3.1p/include/shaders/gamma.hpp | 2 +- lsfg-vk-v3.1p/src/shaders/alpha.cpp | 16 +++--- lsfg-vk-v3.1p/src/shaders/beta.cpp | 20 +++---- lsfg-vk-v3.1p/src/shaders/delta.cpp | 40 ++++++------- lsfg-vk-v3.1p/src/shaders/gamma.cpp | 52 ++++++++--------- lsfg-vk-v3.1p/src/shaders/generate.cpp | 4 +- lsfg-vk-v3.1p/src/shaders/mipmaps.cpp | 4 +- src/extract/extract.cpp | 74 +++++++++++++++++-------- 8 files changed, 117 insertions(+), 95 deletions(-) diff --git a/lsfg-vk-v3.1p/include/shaders/gamma.hpp b/lsfg-vk-v3.1p/include/shaders/gamma.hpp index b4e24ab..b1c3842 100644 --- a/lsfg-vk-v3.1p/include/shaders/gamma.hpp +++ b/lsfg-vk-v3.1p/include/shaders/gamma.hpp @@ -65,7 +65,7 @@ namespace LSFG_3_1P::Shaders { std::array, 3> inImgs1; Core::Image inImg2; std::optional optImg; - std::array tempImgs1; + std::array tempImgs1; std::array tempImgs2; Core::Image outImg; }; diff --git a/lsfg-vk-v3.1p/src/shaders/alpha.cpp b/lsfg-vk-v3.1p/src/shaders/alpha.cpp index f572f4c..2209adb 100644 --- a/lsfg-vk-v3.1p/src/shaders/alpha.cpp +++ b/lsfg-vk-v3.1p/src/shaders/alpha.cpp @@ -14,28 +14,28 @@ 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, "alpha[0]", + 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, "alpha[1]", + 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, "alpha[2]", + 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, "alpha[3]", + 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, "alpha[0]"), - vk.shaders.getPipeline(vk.device, "alpha[1]"), - vk.shaders.getPipeline(vk.device, "alpha[2]"), - vk.shaders.getPipeline(vk.device, "alpha[3]") + 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++) diff --git a/lsfg-vk-v3.1p/src/shaders/beta.cpp b/lsfg-vk-v3.1p/src/shaders/beta.cpp index 1a24825..c82976a 100644 --- a/lsfg-vk-v3.1p/src/shaders/beta.cpp +++ b/lsfg-vk-v3.1p/src/shaders/beta.cpp @@ -16,34 +16,34 @@ Beta::Beta(Vulkan& vk, std::array, 3> inImgs) : inImgs(std::move(inImgs)) { // create resources this->shaderModules = {{ - vk.shaders.getShader(vk.device, "beta[0]", + 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, "beta[1]", + 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, "beta[2]", + 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, "beta[3]", + 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, "beta[4]", + 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, "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]") + 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, diff --git a/lsfg-vk-v3.1p/src/shaders/delta.cpp b/lsfg-vk-v3.1p/src/shaders/delta.cpp index c479945..4d222dc 100644 --- a/lsfg-vk-v3.1p/src/shaders/delta.cpp +++ b/lsfg-vk-v3.1p/src/shaders/delta.cpp @@ -21,62 +21,62 @@ Delta::Delta(Vulkan& vk, std::array, 3> inImgs1, optImg1(std::move(optImg1)), optImg2(std::move(optImg2)) { // create resources this->shaderModules = {{ - vk.shaders.getShader(vk.device, "delta[0]", + 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, "delta[1]", + 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, "delta[2]", + 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, "delta[3]", + 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, "delta[4]", + 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, "delta[5]", + 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, "delta[6]", + 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, "delta[7]", + 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, "delta[8]", + 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, "delta[9]", + 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, "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]") + 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, diff --git a/lsfg-vk-v3.1p/src/shaders/gamma.cpp b/lsfg-vk-v3.1p/src/shaders/gamma.cpp index ccbe932..350f7a6 100644 --- a/lsfg-vk-v3.1p/src/shaders/gamma.cpp +++ b/lsfg-vk-v3.1p/src/shaders/gamma.cpp @@ -20,35 +20,35 @@ Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, optImg(std::move(optImg)) { // create resources this->shaderModules = {{ - vk.shaders.getShader(vk.device, "gamma[0]", + 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, "gamma[1]", + 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, "gamma[2]", + 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, "gamma[3]", + 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, "gamma[4]", + 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, "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]") + 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, @@ -58,10 +58,10 @@ Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, // create internal images/outputs const VkExtent2D extent = this->inImgs1.at(0).at(0).getExtent(); - for (size_t i = 0; i < 2; i++) { + 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 }, @@ -83,18 +83,14 @@ Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, .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)) + .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.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_SAMPLED_IMAGE, this->tempImgs1) .add(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, this->tempImgs2) .build(); pass.descriptorSets.at(1) = Core::DescriptorSet(vk.device, vk.descriptorPool, @@ -102,13 +98,15 @@ Gamma::Gamma(Vulkan& vk, std::array, 3> inImgs1, 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) + .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) + .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, @@ -137,9 +135,7 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 .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)) + .addR2W(this->tempImgs1) .build(); this->pipelines.at(0).bind(buf); @@ -148,9 +144,7 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 // second shader Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs1.at(0)) - .addW2R(this->tempImgs1.at(1)) - .addW2R(this->tempImgs1.at(2)) + .addW2R(this->tempImgs1) .addR2W(this->tempImgs2) .build(); @@ -161,7 +155,8 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 // third shader Utils::BarrierBuilder(buf) .addW2R(this->tempImgs2) - .addR2W(this->tempImgs1) + .addR2W(this->tempImgs1.at(0)) + .addR2W(this->tempImgs1.at(1)) .build(); this->pipelines.at(2).bind(buf); @@ -170,7 +165,8 @@ void Gamma::Dispatch(const Core::CommandBuffer& buf, uint64_t frameCount, uint64 // fourth shader Utils::BarrierBuilder(buf) - .addW2R(this->tempImgs1) + .addW2R(this->tempImgs1.at(0)) + .addW2R(this->tempImgs1.at(1)) .addR2W(this->tempImgs2) .build(); diff --git a/lsfg-vk-v3.1p/src/shaders/generate.cpp b/lsfg-vk-v3.1p/src/shaders/generate.cpp index 7772217..5d3fe78 100644 --- a/lsfg-vk-v3.1p/src/shaders/generate.cpp +++ b/lsfg-vk-v3.1p/src/shaders/generate.cpp @@ -20,12 +20,12 @@ Generate::Generate(Vulkan& vk, inImg3(std::move(inImg3)), inImg4(std::move(inImg4)), inImg5(std::move(inImg5)) { // create resources - this->shaderModule = vk.shaders.getShader(vk.device, "generate", + 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, "generate"); + 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); diff --git a/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp b/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp index 971055f..e47af8a 100644 --- a/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp +++ b/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp @@ -15,12 +15,12 @@ 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", + 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, "mipmaps"); + 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++) diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index 21fdb40..6652448 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -14,31 +14,57 @@ using namespace Extract; const std::unordered_map nameHashTable = {{ + { "p_mipmaps", 0xe365474d }, + { "p_alpha[0]", 0x0bf6c705 }, + { "p_alpha[1]", 0x4badbc24 }, + { "p_alpha[2]", 0x08a0f71c }, + { "p_alpha[3]", 0x5cc4d794 }, + { "p_beta[0]", 0x598efc9c }, + { "p_beta[1]", 0x17e25e8d }, + { "p_beta[2]", 0x6b8971f7 }, + { "p_beta[3]", 0x80a351c9 }, + { "p_beta[4]", 0xda0fcd5a }, + { "p_gamma[0]", 0x9dae0419 }, + { "p_gamma[1]", 0x96285646 }, + { "p_gamma[2]", 0xca44bb67 }, + { "p_gamma[3]", 0xa942fb59 }, + { "p_gamma[4]", 0x31996b8d }, + { "p_delta[0]", 0x9dae0419 }, + { "p_delta[1]", 0x67912aaa }, + { "p_delta[2]", 0xec138d48 }, + { "p_delta[3]", 0xdcb8247f }, + { "p_delta[4]", 0xd37c9bc8 }, + { "p_delta[5]", 0x62973dce }, + { "p_delta[6]", 0xe2ed5f66 }, + { "p_delta[7]", 0xc964a42a }, + { "p_delta[8]", 0x74536ad9 }, + { "p_delta[9]", 0xf472482a }, + { "p_generate", 0x5c040bd5 }, { "mipmaps", 0xe365474d }, - { "alpha[0]", 0x0bf6c705 }, - { "alpha[1]", 0x4badbc24 }, - { "alpha[2]", 0x08a0f71c }, - { "alpha[3]", 0x5cc4d794 }, - { "beta[0]", 0x598efc9c }, - { "beta[1]", 0x17e25e8d }, - { "beta[2]", 0x6b8971f7 }, - { "beta[3]", 0x80a351c9 }, - { "beta[4]", 0xda0fcd5a }, - { "gamma[0]", 0x9dae0419 }, - { "gamma[1]", 0x96285646 }, - { "gamma[2]", 0xca44bb67 }, - { "gamma[3]", 0xa942fb59 }, - { "gamma[4]", 0x31996b8d }, - { "delta[0]", 0x9dae0419 }, - { "delta[1]", 0x67912aaa }, - { "delta[2]", 0xec138d48 }, - { "delta[3]", 0xdcb8247f }, - { "delta[4]", 0xd37c9bc8 }, - { "delta[5]", 0x62973dce }, - { "delta[6]", 0xe2ed5f66 }, - { "delta[7]", 0xc964a42a }, - { "delta[8]", 0x74536ad9 }, - { "delta[9]", 0xf472482a }, + { "alpha[0]", 0xf37c8aa8 }, + { "alpha[1]", 0xeb7a52d4 }, + { "alpha[2]", 0x8901788e }, + { "alpha[3]", 0xa06a5e36 }, + { "beta[0]", 0x63c16b89 }, + { "beta[1]", 0xe3967ed5 }, + { "beta[2]", 0x570085ee }, + { "beta[3]", 0x4f4530db }, + { "beta[4]", 0x39727389 }, + { "gamma[0]", 0x94c4edf6 }, + { "gamma[1]", 0xf4e32702 }, + { "gamma[2]", 0xa3dc56fc }, + { "gamma[3]", 0x8b5ed8f6 }, + { "gamma[4]", 0x1cbf3c4d }, + { "delta[0]", 0x94c4edf6 }, + { "delta[1]", 0xacfd805b }, + { "delta[2]", 0x891dc48b }, + { "delta[3]", 0x98536d9d }, + { "delta[4]", 0x8e3f2155 }, + { "delta[5]", 0x8f0e70a1 }, + { "delta[6]", 0xd5eca8f1 }, + { "delta[7]", 0xa9e54e37 }, + { "delta[8]", 0x1dee8b84 }, + { "delta[9]", 0x1576c3f5 }, { "generate", 0x5c040bd5 } }}; From 3ec418fd0f802f245bc31511d33c94a25d045ae8 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 12 Jul 2025 22:02:48 +0200 Subject: [PATCH 133/253] rename build artifact allows for easier api query later --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 32a610f..5ab69d7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: - name: Upload lsfg-vk artifact uses: actions/upload-artifact@v4 with: - name: lsfg-vk + name: lsfg-vk_archlinux path: | build-release/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json build-release/lib/liblsfg-vk.so From 6a6465e12ea1d44e0d39edef5215208c1ce6b87e Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 13 Jul 2025 17:15:44 +0200 Subject: [PATCH 134/253] get rid of full dxvk --- CMakeLists.txt | 4 +-- cmake/FetchDXBC.cmake | 33 ++++++++++++++++++++++ cmake/FetchDXVK.cmake | 60 --------------------------------------- src/extract/trans.cpp | 65 ++++++++++++++++++++++++++++++++++--------- 4 files changed, 87 insertions(+), 75 deletions(-) create mode 100644 cmake/FetchDXBC.cmake delete mode 100644 cmake/FetchDXVK.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index ef8c74a..f057af5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ endif() # subprojects -include(cmake/FetchDXVK.cmake) +include(cmake/FetchDXBC.cmake) include(cmake/FetchPeParse.cmake) add_subdirectory(lsfg-vk-common) @@ -41,7 +41,7 @@ target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk PRIVATE lsfg-vk-common lsfg-vk-v3.1 lsfg-vk-v3.1p - peparse dxvk vulkan) + peparse dxvk_dxbc vulkan) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(lsfg-vk PROPERTIES diff --git a/cmake/FetchDXBC.cmake b/cmake/FetchDXBC.cmake new file mode 100644 index 0000000..3311a26 --- /dev/null +++ b/cmake/FetchDXBC.cmake @@ -0,0 +1,33 @@ +include(ExternalProject) + +ExternalProject_Add(dxbc_git + GIT_REPOSITORY "https://github.com/PancakeTAS/dxbc" + GIT_TAG "d77124f" + UPDATE_DISCONNECTED true + USES_TERMINAL_CONFIGURE true + USES_TERMINAL_BUILD true + BUILD_IN_SOURCE true + CONFIGURE_COMMAND + cmake -S . -B build -G Ninja + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_C_COMPILER=clang + -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_C_FLAGS=-fPIC + -DCMAKE_CXX_FLAGS=-fPIC + BUILD_COMMAND + ninja -C build + INSTALL_COMMAND "" +) + +ExternalProject_Get_Property(dxbc_git SOURCE_DIR) + +add_library(dxvk_dxbc INTERFACE) +add_dependencies(dxvk_dxbc dxbc_git) + +target_link_directories(dxvk_dxbc + INTERFACE ${SOURCE_DIR}/build) +target_include_directories(dxvk_dxbc SYSTEM + INTERFACE ${SOURCE_DIR}/include/dxbc + INTERFACE ${SOURCE_DIR}/include/spirv ${SOURCE_DIR}/include/util ${SOURCE_DIR}/include/dxvk) +target_link_libraries(dxvk_dxbc + INTERFACE dxbc) diff --git a/cmake/FetchDXVK.cmake b/cmake/FetchDXVK.cmake deleted file mode 100644 index f84de82..0000000 --- a/cmake/FetchDXVK.cmake +++ /dev/null @@ -1,60 +0,0 @@ -include(ExternalProject) - -if(CMAKE_BUILD_TYPE STREQUAL "Release") - set(BUILD_TYPE "release") - set(STRIP_FLAG "--strip") -else() - set(BUILD_TYPE "debug") - set(STRIP_FLAG "") -endif() - -ExternalProject_Add(dxvk_git - GIT_REPOSITORY "https://github.com/doitsujin/dxvk" - GIT_TAG "v2.6.2" - UPDATE_DISCONNECTED true - USES_TERMINAL_CONFIGURE true - USES_TERMINAL_BUILD true - BUILD_IN_SOURCE true - CONFIGURE_COMMAND - sed -i s/private://g - src/dxvk/dxvk_shader.h && - CC=clang CXX=clang++ CFLAGS=-w CXXFLAGS=-w - meson setup - --buildtype ${BUILD_TYPE} - --prefix /build-native - ${STRIP_FLAG} - -Dbuild_id=false - -Denable_d3d8=false - -Denable_d3d9=false - -Denable_d3d10=false - --force-fallback-for=libdisplay-info - --wipe - build - BUILD_COMMAND - ninja -C build install && - mv build/src/dxvk/libdxvk.a build/src/dxvk/libldxvk.a && - cd build/src/dxgi/libdxvk_dxgi.so.0.20602.p && - bash -c "ar rcs -o ../libdxgi.a *.o" && - cd ../../d3d11/libdxvk_d3d11.so.0.20602.p && - bash -c "ar rcs -o ../libd3d11.a *.o .*.o" - INSTALL_COMMAND "" -) - -ExternalProject_Get_Property(dxvk_git SOURCE_DIR) - -add_library(dxvk INTERFACE) -add_dependencies(dxvk dxvk_git) - -target_link_directories(dxvk - INTERFACE ${SOURCE_DIR}/build/src/dxvk - INTERFACE ${SOURCE_DIR}/build/src/dxbc - INTERFACE ${SOURCE_DIR}/build/src/dxgi - INTERFACE ${SOURCE_DIR}/build/src/d3d11 - INTERFACE ${SOURCE_DIR}/build/src/spirv - INTERFACE ${SOURCE_DIR}/build/src/util) -target_include_directories(dxvk SYSTEM - INTERFACE ${SOURCE_DIR}/build-native/include/dxvk - INTERFACE ${SOURCE_DIR}/src - INTERFACE ${SOURCE_DIR}/include/spirv/include) -target_link_libraries(dxvk INTERFACE - -Wl,--start-group dxgi d3d11 util ldxvk dxbc spirv -Wl,--end-group) diff --git a/src/extract/trans.cpp b/src/extract/trans.cpp index f9860f9..d458fea 100644 --- a/src/extract/trans.cpp +++ b/src/extract/trans.cpp @@ -1,8 +1,10 @@ #include "extract/trans.hpp" -#include -#include -#include +#include + +#include +#include +#include #include #include @@ -11,23 +13,60 @@ using namespace Extract; +struct BindingOffsets { + uint32_t bindingIndex{}; + uint32_t bindingOffset{}; + uint32_t setIndex{}; + uint32_t setOffset{}; +}; + std::vector Extract::translateShader(std::vector bytecode) { // compile the shader dxvk::DxbcReader reader(reinterpret_cast(bytecode.data()), bytecode.size()); dxvk::DxbcModule module(reader); const dxvk::DxbcModuleInfo info{}; - auto shader = module.compile(info, "CS"); + auto code = module.compile(info, "CS"); - // extract spir-v from d3d11 shader - auto code = shader->getRawCode(); + // find all bindings + std::vector bindingOffsets; + std::vector varIds; + for (auto ins : code) { + if (ins.opCode() == spv::OpDecorate) { + if (ins.arg(2) == spv::DecorationBinding) { + const uint32_t varId = ins.arg(1); + bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1))); + bindingOffsets[varId].bindingIndex = ins.arg(3); + bindingOffsets[varId].bindingOffset = ins.offset() + 3; + varIds.push_back(varId); + } - // patch binding offsets -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" - for (size_t i = 0; i < shader->m_bindingOffsets.size(); i++) - code.data()[shader->m_bindingOffsets.at(i).bindingOffset] // NOLINT - = static_cast(i); -#pragma clang diagnostic pop + if (ins.arg(2) == spv::DecorationDescriptorSet) { + const uint32_t varId = ins.arg(1); + bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1))); + bindingOffsets[varId].setIndex = ins.arg(3); + bindingOffsets[varId].setOffset = ins.offset() + 3; + } + } + + if (ins.opCode() == spv::OpFunction) + break; + } + + std::vector validBindings; + for (const auto varId : varIds) { + auto info = bindingOffsets[varId]; + + if (info.bindingOffset) + validBindings.push_back(info); + } + + // patch binding offset + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + for (size_t i = 0; i < validBindings.size(); i++) + code.data()[validBindings.at(i).bindingOffset] // NOLINT + = static_cast(i); + #pragma clang diagnostic pop // return the new bytecode std::vector spirvBytecode(code.size()); From b5678a9ce576e20252676725fc0b4e0b3615e1eb Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 13 Jul 2025 17:19:24 +0200 Subject: [PATCH 135/253] update workflow --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ab69d7..cd222ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,10 +16,10 @@ jobs: run: | pacman -Syu --noconfirm \ base-devel \ - git sdl3 \ + git \ clang llvm \ - cmake meson ninja \ - glslang vulkan-headers vulkan-icd-loader + cmake ninja \ + spirv-headers vulkan-headers vulkan-icd-loader - name: Checkout repository uses: actions/checkout@v4 - name: Configure with CMake and Ninja From 26ba56d9d06f89739552010090483459f749640d Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 13 Jul 2025 17:33:11 +0200 Subject: [PATCH 136/253] get rid of cmake fetch --- .github/workflows/build.yml | 2 ++ .gitmodules | 6 ++++++ CMakeLists.txt | 10 ++++++---- cmake/FetchDXBC.cmake | 33 --------------------------------- cmake/FetchPeParse.cmake | 32 -------------------------------- dxbc | 1 + lsfg-vk-common/CMakeLists.txt | 2 -- lsfg-vk-v3.1/CMakeLists.txt | 2 -- lsfg-vk-v3.1p/CMakeLists.txt | 2 -- pe-parse | 1 + 10 files changed, 16 insertions(+), 75 deletions(-) create mode 100644 .gitmodules delete mode 100644 cmake/FetchDXBC.cmake delete mode 100644 cmake/FetchPeParse.cmake create mode 160000 dxbc create mode 160000 pe-parse diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd222ce..0d467ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,8 @@ jobs: spirv-headers vulkan-headers vulkan-icd-loader - name: Checkout repository uses: actions/checkout@v4 + with: + submodules: true - name: Configure with CMake and Ninja run: | cmake -B build -G Ninja \ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..57f075d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "dxbc"] + path = dxbc + url = https://github.com/PancakeTAS/dxbc.git +[submodule "pe-parse"] + path = pe-parse + url = https://github.com/trailofbits/pe-parse diff --git a/CMakeLists.txt b/CMakeLists.txt index f057af5..de1f21b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,10 +10,12 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release") endif() # subprojects +add_compile_options(-fPIC + -Wno-deprecated-declarations + -Wno-unused-template) -include(cmake/FetchDXBC.cmake) -include(cmake/FetchPeParse.cmake) - +add_subdirectory(dxbc) +add_subdirectory(pe-parse/pe-parser-library) add_subdirectory(lsfg-vk-common) add_subdirectory(lsfg-vk-v3.1) add_subdirectory(lsfg-vk-v3.1p) @@ -41,7 +43,7 @@ target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk PRIVATE lsfg-vk-common lsfg-vk-v3.1 lsfg-vk-v3.1p - peparse dxvk_dxbc vulkan) + pe-parse dxbc vulkan) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(lsfg-vk PROPERTIES diff --git a/cmake/FetchDXBC.cmake b/cmake/FetchDXBC.cmake deleted file mode 100644 index 3311a26..0000000 --- a/cmake/FetchDXBC.cmake +++ /dev/null @@ -1,33 +0,0 @@ -include(ExternalProject) - -ExternalProject_Add(dxbc_git - GIT_REPOSITORY "https://github.com/PancakeTAS/dxbc" - GIT_TAG "d77124f" - UPDATE_DISCONNECTED true - USES_TERMINAL_CONFIGURE true - USES_TERMINAL_BUILD true - BUILD_IN_SOURCE true - CONFIGURE_COMMAND - cmake -S . -B build -G Ninja - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DCMAKE_C_COMPILER=clang - -DCMAKE_CXX_COMPILER=clang++ - -DCMAKE_C_FLAGS=-fPIC - -DCMAKE_CXX_FLAGS=-fPIC - BUILD_COMMAND - ninja -C build - INSTALL_COMMAND "" -) - -ExternalProject_Get_Property(dxbc_git SOURCE_DIR) - -add_library(dxvk_dxbc INTERFACE) -add_dependencies(dxvk_dxbc dxbc_git) - -target_link_directories(dxvk_dxbc - INTERFACE ${SOURCE_DIR}/build) -target_include_directories(dxvk_dxbc SYSTEM - INTERFACE ${SOURCE_DIR}/include/dxbc - INTERFACE ${SOURCE_DIR}/include/spirv ${SOURCE_DIR}/include/util ${SOURCE_DIR}/include/dxvk) -target_link_libraries(dxvk_dxbc - INTERFACE dxbc) diff --git a/cmake/FetchPeParse.cmake b/cmake/FetchPeParse.cmake deleted file mode 100644 index df7879b..0000000 --- a/cmake/FetchPeParse.cmake +++ /dev/null @@ -1,32 +0,0 @@ -include(ExternalProject) - -ExternalProject_Add(peparse_git - GIT_REPOSITORY "https://github.com/trailofbits/pe-parse" - GIT_TAG "v2.1.1" - UPDATE_DISCONNECTED true - USES_TERMINAL_CONFIGURE true - USES_TERMINAL_BUILD true - BUILD_IN_SOURCE true - CONFIGURE_COMMAND - cmake -S pe-parser-library -B build -G Ninja - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DCMAKE_C_COMPILER=clang - -DCMAKE_CXX_COMPILER=clang++ - -DCMAKE_C_FLAGS=-fPIC - -DCMAKE_CXX_FLAGS=-fPIC - BUILD_COMMAND - ninja -C build - INSTALL_COMMAND "" -) - -ExternalProject_Get_Property(peparse_git SOURCE_DIR) - -add_library(peparse INTERFACE) -add_dependencies(peparse peparse_git) - -target_link_directories(peparse - INTERFACE ${SOURCE_DIR}/build) -target_include_directories(peparse SYSTEM - INTERFACE ${SOURCE_DIR}/pe-parser-library/include) -target_link_libraries(peparse - INTERFACE pe-parse) diff --git a/dxbc b/dxbc new file mode 160000 index 0000000..04ca5e9 --- /dev/null +++ b/dxbc @@ -0,0 +1 @@ +Subproject commit 04ca5e9ae5fef6c0c65ea72bbaa7375327f11454 diff --git a/lsfg-vk-common/CMakeLists.txt b/lsfg-vk-common/CMakeLists.txt index 50b4525..5c5ab0f 100644 --- a/lsfg-vk-common/CMakeLists.txt +++ b/lsfg-vk-common/CMakeLists.txt @@ -21,8 +21,6 @@ target_include_directories(lsfg-vk-common PUBLIC include) target_link_libraries(lsfg-vk-common PRIVATE vulkan) -target_compile_options(lsfg-vk-common PRIVATE - -fPIC) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(lsfg-vk-common PROPERTIES diff --git a/lsfg-vk-v3.1/CMakeLists.txt b/lsfg-vk-v3.1/CMakeLists.txt index 314c0ad..d181cc4 100644 --- a/lsfg-vk-v3.1/CMakeLists.txt +++ b/lsfg-vk-v3.1/CMakeLists.txt @@ -24,8 +24,6 @@ target_include_directories(lsfg-vk-v3.1 PUBLIC public) target_link_libraries(lsfg-vk-v3.1 PUBLIC lsfg-vk-common vulkan) -target_compile_options(lsfg-vk-v3.1 PRIVATE - -fPIC) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(lsfg-vk-v3.1 PROPERTIES diff --git a/lsfg-vk-v3.1p/CMakeLists.txt b/lsfg-vk-v3.1p/CMakeLists.txt index 6e08b62..d889858 100644 --- a/lsfg-vk-v3.1p/CMakeLists.txt +++ b/lsfg-vk-v3.1p/CMakeLists.txt @@ -24,8 +24,6 @@ target_include_directories(lsfg-vk-v3.1p PUBLIC public) target_link_libraries(lsfg-vk-v3.1p PUBLIC lsfg-vk-common vulkan) -target_compile_options(lsfg-vk-v3.1p PRIVATE - -fPIC) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(lsfg-vk-v3.1p PROPERTIES diff --git a/pe-parse b/pe-parse new file mode 160000 index 0000000..7888f1f --- /dev/null +++ b/pe-parse @@ -0,0 +1 @@ +Subproject commit 7888f1f8de2f6bc302c291a5b4519fad926c0133 From 8cf8ea48a1fb5e2308f185920532f3592aed25cb Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 13 Jul 2025 17:47:39 +0200 Subject: [PATCH 137/253] workflows for each distro fixes #67 --- .../workflows/{build.yml => build-arch.yml} | 3 +- .github/workflows/build-fedora.yml | 45 ++++++++++++++++ .github/workflows/build-ubuntu.yml | 54 +++++++++++++++++++ CMakeLists.txt | 6 +-- lsfg-vk-common/CMakeLists.txt | 2 +- lsfg-vk-v3.1/CMakeLists.txt | 2 +- lsfg-vk-v3.1p/CMakeLists.txt | 2 +- 7 files changed, 106 insertions(+), 8 deletions(-) rename .github/workflows/{build.yml => build-arch.yml} (96%) create mode 100644 .github/workflows/build-fedora.yml create mode 100644 .github/workflows/build-ubuntu.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build-arch.yml similarity index 96% rename from .github/workflows/build.yml rename to .github/workflows/build-arch.yml index 0d467ef..2a6acbd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build-arch.yml @@ -15,8 +15,7 @@ jobs: - name: Install build dependencies run: | pacman -Syu --noconfirm \ - base-devel \ - git \ + base-devel git \ clang llvm \ cmake ninja \ spirv-headers vulkan-headers vulkan-icd-loader diff --git a/.github/workflows/build-fedora.yml b/.github/workflows/build-fedora.yml new file mode 100644 index 0000000..0ff2800 --- /dev/null +++ b/.github/workflows/build-fedora.yml @@ -0,0 +1,45 @@ +name: Build lsfg-vk on Fedora +on: [push] + +jobs: + build-fedora: + runs-on: ubuntu-latest + container: + image: fedora:latest + steps: + - name: Prepare cache for dnf packages + uses: actions/cache@v4 + with: + path: /var/cache/libdnf5 + key: fedora-dnf-cache + - name: Install build dependencies + run: | + echo "keepcache=1" >> /etc/dnf/dnf.conf + dnf update -y + dnf install -y \ + git \ + clang llvm \ + cmake ninja-build \ + spirv-headers-devel vulkan-headers vulkan-loader-devel + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Configure with CMake and Ninja + run: | + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=./build-release + - name: Build with Ninja + run: | + ninja -C build + - name: Install with CMake + run: | + cmake --install build + - name: Upload lsfg-vk artifact + uses: actions/upload-artifact@v4 + with: + name: lsfg-vk_fedora + path: | + build-release/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json + build-release/lib/liblsfg-vk.so diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml new file mode 100644 index 0000000..7a5f8ec --- /dev/null +++ b/.github/workflows/build-ubuntu.yml @@ -0,0 +1,54 @@ +name: Build lsfg-vk on Ubuntu +on: [push] + +jobs: + build-ubuntu: + runs-on: ubuntu-latest + steps: + - name: Prepare cache for apt packages + uses: actions/cache@v4 + with: + path: /var/cache/apt/archives + key: ubuntu-apt-cache + - name: Install build dependencies + run: | + export DEBIAN_FRONTEND=noninteractive + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + clang clang-tools llvm \ + git tar \ + cmake ninja-build \ + spirv-headers libvulkan-dev + - name: Checkout SPIR-V + uses: actions/checkout@v4 + with: + repository: 'KhronosGroup/SPIRV-Headers' + ref: 'vulkan-sdk-1.4.313.0' + path: 'spirv-headers' + - name: Build SPIR-V headers + run: | + cmake -S spirv-headers -B spirv-headers/build + sudo cmake --build spirv-headers/build --target install + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Configure with CMake and Ninja + run: | + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=./build-release + - name: Build with Ninja + run: | + ninja -C build + - name: Install with CMake + run: | + cmake --install build + - name: Upload lsfg-vk artifact + uses: actions/upload-artifact@v4 + with: + name: lsfg-vk_ubuntu + path: | + build-release/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json + build-release/lib/liblsfg-vk.so diff --git a/CMakeLists.txt b/CMakeLists.txt index de1f21b..dab32fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.29) +cmake_minimum_required(VERSION 3.28) set(CMAKE_C_COMPILER clang) set(CMAKE_CXX_COMPILER clang++) @@ -14,8 +14,8 @@ add_compile_options(-fPIC -Wno-deprecated-declarations -Wno-unused-template) -add_subdirectory(dxbc) -add_subdirectory(pe-parse/pe-parser-library) +add_subdirectory(dxbc EXCLUDE_FROM_ALL) +add_subdirectory(pe-parse/pe-parser-library EXCLUDE_FROM_ALL) add_subdirectory(lsfg-vk-common) add_subdirectory(lsfg-vk-v3.1) add_subdirectory(lsfg-vk-v3.1p) diff --git a/lsfg-vk-common/CMakeLists.txt b/lsfg-vk-common/CMakeLists.txt index 5c5ab0f..a6cf0c9 100644 --- a/lsfg-vk-common/CMakeLists.txt +++ b/lsfg-vk-common/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.29) +cmake_minimum_required(VERSION 3.28) # project project(lsfg-vk-common diff --git a/lsfg-vk-v3.1/CMakeLists.txt b/lsfg-vk-v3.1/CMakeLists.txt index d181cc4..cbc4abc 100644 --- a/lsfg-vk-v3.1/CMakeLists.txt +++ b/lsfg-vk-v3.1/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.29) +cmake_minimum_required(VERSION 3.28) # project project(lsfg-vk-v3.1 diff --git a/lsfg-vk-v3.1p/CMakeLists.txt b/lsfg-vk-v3.1p/CMakeLists.txt index d889858..94fa4c8 100644 --- a/lsfg-vk-v3.1p/CMakeLists.txt +++ b/lsfg-vk-v3.1p/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.29) +cmake_minimum_required(VERSION 3.28) # project project(lsfg-vk-v3.1p From 0d19e15834e35293c76a2aa947abe70bce995cc1 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 13 Jul 2025 18:21:59 +0200 Subject: [PATCH 138/253] workflow for debian fixes #64 --- .github/workflows/build-debian.yml | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/build-debian.yml diff --git a/.github/workflows/build-debian.yml b/.github/workflows/build-debian.yml new file mode 100644 index 0000000..b5a5a47 --- /dev/null +++ b/.github/workflows/build-debian.yml @@ -0,0 +1,46 @@ +name: Build lsfg-vk on Debian +on: [push] + +jobs: + build-debian: + runs-on: ubuntu-latest + container: + image: debian:trixie + steps: + - name: Prepare cache for apt packages + uses: actions/cache@v4 + with: + path: /var/cache/apt/archives + key: debian-apt-cache + - name: Install build dependencies + run: | + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y \ + build-essential \ + clang clang-tools llvm \ + git tar \ + cmake ninja-build \ + spirv-headers libvulkan-dev + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Configure with CMake and Ninja + run: | + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=./build-release + - name: Build with Ninja + run: | + ninja -C build + - name: Install with CMake + run: | + cmake --install build + - name: Upload lsfg-vk artifact + uses: actions/upload-artifact@v4 + with: + name: lsfg-vk_debian + path: | + build-release/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json + build-release/lib/liblsfg-vk.so From f998647d74051467e39de9de2df2ff9a5996db5f Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 13 Jul 2025 18:39:21 +0200 Subject: [PATCH 139/253] update installation script --- install.sh | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/install.sh b/install.sh index ce50d05..a0cf107 100755 --- a/install.sh +++ b/install.sh @@ -3,13 +3,34 @@ : "${INSTALL_PATH:=$HOME/.local}" BASE_URL='https://pancake.gay/lsfg-vk' +# prompt for distro +echo "Which version would you like to install?" +echo "1) Arch Linux (Artix Linux, CachyOS, Steam Deck, etc.)" +echo "2) Debian" +echo "3) Ubuntu" +echo "4) Fedora" +printf "Enter the number (1-4): " +read -r version_choice < /dev/tty + +case "$version_choice" in + 1) DISTRO="archlinux"; DISTRO_PRETTY="Arch Linux" ;; + 2) DISTRO="debian"; DISTRO_PRETTY="Debian" ;; + 3) DISTRO="ubuntu"; DISTRO_PRETTY="Ubuntu" ;; + 4) DISTRO="fedora"; DISTRO_PRETTY="Fedora" ;; + *) echo "Invalid choice."; exit 1 ;; +esac + +ZIP_NAME="lsfg-vk_${DISTRO}.zip" +SHA_NAME="lsfg-vk_${DISTRO}.zip.sha" +SHA_FILE="$INSTALL_PATH/share/lsfg-vk.sha" + # get local and remote versions -REMOTE_HASH=$(curl -fsSL "$BASE_URL/lsfg-vk.zip.sha") -LOCAL_HASH=$(test -f "$INSTALL_PATH/share/lsfg-vk.sha" && cat "$INSTALL_PATH/share/lsfg-vk.sha") +REMOTE_HASH=$(curl -fsSL "$BASE_URL/$SHA_NAME") +LOCAL_HASH=$(test -f "$SHA_FILE" && cat "$SHA_FILE") if [ "$REMOTE_HASH" != "$LOCAL_HASH" ]; then # prompt user for confirmation - echo -n "Do you wish to install the latest version of lsfg-vk? (y/n) " + echo -n "Are you sure you want to install lsfg-vk for ${DISTRO_PRETTY}? (y/n) " read -r answer < /dev/tty if [ "$answer" != "y" ]; then @@ -18,7 +39,7 @@ if [ "$REMOTE_HASH" != "$LOCAL_HASH" ]; then fi # download lsfg-vk - curl -fsSL -o "/tmp/lsfg-vk.zip" "$BASE_URL/lsfg-vk.zip" + curl -fsSL -o "/tmp/$ZIP_NAME" "$BASE_URL/$ZIP_NAME" if [ $? -ne 0 ]; then echo "Failed to download lsfg-vk. Please check your internet connection." exit 1 @@ -26,10 +47,21 @@ if [ "$REMOTE_HASH" != "$LOCAL_HASH" ]; then # install lsfg-vk cd "$INSTALL_PATH" || exit 1 - unzip -oqq "/tmp/lsfg-vk.zip" - echo "$REMOTE_HASH" > share/lsfg-vk.sha + unzip -o "/tmp/$ZIP_NAME" + echo "$REMOTE_HASH" > "$SHA_FILE" + rm -v "/tmp/$ZIP_NAME" - echo "lsfg-vk has been installed." + echo "lsfg-vk for ${DISTRO_PRETTY} has been installed." else - echo "lsfg-vk is already up to date." + echo "lsfg-vk is up to date." + + # offer to uninstall + echo -n "Do you want to uninstall lsfg-vk? (y/n) " + read -r uninstall_answer < /dev/tty + if [ "$uninstall_answer" = "y" ]; then + rm -v ~/.local/lib/liblsfg-vk.so + rm -v ~/.local/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json + rm -v "$SHA_FILE" + echo "Uninstallation completed." + fi fi From 83b869b0c4d4cd4da2e760126242c6ed76bafec8 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 14 Jul 2025 20:29:55 +0200 Subject: [PATCH 140/253] switch to index based shader discovery --- src/extract/extract.cpp | 123 ++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 67 deletions(-) diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index 6652448..a8bfff7 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -13,59 +13,59 @@ using namespace Extract; -const std::unordered_map nameHashTable = {{ - { "p_mipmaps", 0xe365474d }, - { "p_alpha[0]", 0x0bf6c705 }, - { "p_alpha[1]", 0x4badbc24 }, - { "p_alpha[2]", 0x08a0f71c }, - { "p_alpha[3]", 0x5cc4d794 }, - { "p_beta[0]", 0x598efc9c }, - { "p_beta[1]", 0x17e25e8d }, - { "p_beta[2]", 0x6b8971f7 }, - { "p_beta[3]", 0x80a351c9 }, - { "p_beta[4]", 0xda0fcd5a }, - { "p_gamma[0]", 0x9dae0419 }, - { "p_gamma[1]", 0x96285646 }, - { "p_gamma[2]", 0xca44bb67 }, - { "p_gamma[3]", 0xa942fb59 }, - { "p_gamma[4]", 0x31996b8d }, - { "p_delta[0]", 0x9dae0419 }, - { "p_delta[1]", 0x67912aaa }, - { "p_delta[2]", 0xec138d48 }, - { "p_delta[3]", 0xdcb8247f }, - { "p_delta[4]", 0xd37c9bc8 }, - { "p_delta[5]", 0x62973dce }, - { "p_delta[6]", 0xe2ed5f66 }, - { "p_delta[7]", 0xc964a42a }, - { "p_delta[8]", 0x74536ad9 }, - { "p_delta[9]", 0xf472482a }, - { "p_generate", 0x5c040bd5 }, - { "mipmaps", 0xe365474d }, - { "alpha[0]", 0xf37c8aa8 }, - { "alpha[1]", 0xeb7a52d4 }, - { "alpha[2]", 0x8901788e }, - { "alpha[3]", 0xa06a5e36 }, - { "beta[0]", 0x63c16b89 }, - { "beta[1]", 0xe3967ed5 }, - { "beta[2]", 0x570085ee }, - { "beta[3]", 0x4f4530db }, - { "beta[4]", 0x39727389 }, - { "gamma[0]", 0x94c4edf6 }, - { "gamma[1]", 0xf4e32702 }, - { "gamma[2]", 0xa3dc56fc }, - { "gamma[3]", 0x8b5ed8f6 }, - { "gamma[4]", 0x1cbf3c4d }, - { "delta[0]", 0x94c4edf6 }, - { "delta[1]", 0xacfd805b }, - { "delta[2]", 0x891dc48b }, - { "delta[3]", 0x98536d9d }, - { "delta[4]", 0x8e3f2155 }, - { "delta[5]", 0x8f0e70a1 }, - { "delta[6]", 0xd5eca8f1 }, - { "delta[7]", 0xa9e54e37 }, - { "delta[8]", 0x1dee8b84 }, - { "delta[9]", 0x1576c3f5 }, - { "generate", 0x5c040bd5 } +const std::unordered_map nameIdxTable = {{ + { "mipmaps", 255 }, + { "alpha[0]", 267 }, + { "alpha[1]", 268 }, + { "alpha[2]", 269 }, + { "alpha[3]", 270 }, + { "beta[0]", 275 }, + { "beta[1]", 276 }, + { "beta[2]", 277 }, + { "beta[3]", 278 }, + { "beta[4]", 279 }, + { "gamma[0]", 257 }, + { "gamma[1]", 259 }, + { "gamma[2]", 260 }, + { "gamma[3]", 261 }, + { "gamma[4]", 262 }, + { "delta[0]", 257 }, + { "delta[1]", 263 }, + { "delta[2]", 264 }, + { "delta[3]", 265 }, + { "delta[4]", 266 }, + { "delta[5]", 258 }, + { "delta[6]", 271 }, + { "delta[7]", 272 }, + { "delta[8]", 273 }, + { "delta[9]", 274 }, + { "generate", 256 }, + { "p_mipmaps", 255 }, + { "p_alpha[0]", 290 }, + { "p_alpha[1]", 291 }, + { "p_alpha[2]", 292 }, + { "p_alpha[3]", 293 }, + { "p_beta[0]", 298 }, + { "p_beta[1]", 299 }, + { "p_beta[2]", 300 }, + { "p_beta[3]", 301 }, + { "p_beta[4]", 302 }, + { "p_gamma[0]", 280 }, + { "p_gamma[1]", 282 }, + { "p_gamma[2]", 283 }, + { "p_gamma[3]", 284 }, + { "p_gamma[4]", 285 }, + { "p_delta[0]", 280 }, + { "p_delta[1]", 286 }, + { "p_delta[2]", 287 }, + { "p_delta[3]", 288 }, + { "p_delta[4]", 289 }, + { "p_delta[5]", 281 }, + { "p_delta[6]", 294 }, + { "p_delta[7]", 295 }, + { "p_delta[8]", 296 }, + { "p_delta[9]", 297 }, + { "p_generate", 256 }, }}; namespace { @@ -74,23 +74,12 @@ namespace { return shaderData; } - uint32_t fnv1a_hash(const std::vector& data) { - uint32_t hash = 0x811C9DC5; - for (auto byte : data) { - hash ^= byte; - hash *= 0x01000193; - } - return hash; - } - int on_resource(void*, const peparse::resource& res) { if (res.type != peparse::RT_RCDATA || res.buf == nullptr || res.buf->bufLen <= 0) return 0; std::vector resource_data(res.buf->bufLen); std::copy_n(res.buf->buf, res.buf->bufLen, resource_data.data()); - - const uint32_t hash = fnv1a_hash(resource_data); - shaders()[hash] = resource_data; + shaders()[res.name] = resource_data; return 0; } @@ -141,8 +130,8 @@ std::vector Extract::getShader(const std::string& name) { if (shaders().empty()) throw std::runtime_error("Shaders are not loaded."); - auto hit = nameHashTable.find(name); - if (hit == nameHashTable.end()) + auto hit = nameIdxTable.find(name); + if (hit == nameIdxTable.end()) throw std::runtime_error("Shader hash not found: " + name); auto sit = shaders().find(hit->second); From 67f5e4923bf2027c0d3e473846b0502b8febcaa5 Mon Sep 17 00:00:00 2001 From: Nojus Date: Sun, 13 Jul 2025 22:50:25 +0200 Subject: [PATCH 141/253] install.sh: nixos option and various improvements adds a nixos option with its related special handling, uses mktemp instead of a hardcoded tmp folder, adds a check on fetching remote hash and makes the script generally more talkative on what it is doing/failure --- install.sh | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/install.sh b/install.sh index a0cf107..b779bc0 100755 --- a/install.sh +++ b/install.sh @@ -2,6 +2,7 @@ : "${INSTALL_PATH:=$HOME/.local}" BASE_URL='https://pancake.gay/lsfg-vk' +NIX_FLAKE_REPO='https://github.com/pabloaul/lsfg-vk-flake' # prompt for distro echo "Which version would you like to install?" @@ -9,7 +10,8 @@ echo "1) Arch Linux (Artix Linux, CachyOS, Steam Deck, etc.)" echo "2) Debian" echo "3) Ubuntu" echo "4) Fedora" -printf "Enter the number (1-4): " +echo "5) NixOS (external flake project)" +printf "Enter the number (1-5): " read -r version_choice < /dev/tty case "$version_choice" in @@ -17,6 +19,7 @@ case "$version_choice" in 2) DISTRO="debian"; DISTRO_PRETTY="Debian" ;; 3) DISTRO="ubuntu"; DISTRO_PRETTY="Ubuntu" ;; 4) DISTRO="fedora"; DISTRO_PRETTY="Fedora" ;; + 5) DISTRO="nixos"; DISTRO_PRETTY="NixOS"; USE_NIX=true ;; *) echo "Invalid choice."; exit 1 ;; esac @@ -25,12 +28,18 @@ SHA_NAME="lsfg-vk_${DISTRO}.zip.sha" SHA_FILE="$INSTALL_PATH/share/lsfg-vk.sha" # get local and remote versions -REMOTE_HASH=$(curl -fsSL "$BASE_URL/$SHA_NAME") LOCAL_HASH=$(test -f "$SHA_FILE" && cat "$SHA_FILE") +if [ "$USE_NIX" ]; then + command -v nix >/dev/null 2>&1 || { echo "Error: nix command not found."; exit 1; } + REMOTE_HASH=$(curl -fsSL "${NIX_FLAKE_REPO/github.com/api.github.com/repos}/releases/latest" | grep '"tag_name"' | cut -d '"' -f 4) +else + REMOTE_HASH=$(curl -fsSL "$BASE_URL/$SHA_NAME") +fi +[ -z "$REMOTE_HASH" ] && { echo "Failed to fetch latest release."; exit 1; } if [ "$REMOTE_HASH" != "$LOCAL_HASH" ]; then # prompt user for confirmation - echo -n "Are you sure you want to install lsfg-vk for ${DISTRO_PRETTY}? (y/n) " + echo -n "Are you sure you want to install lsfg-vk ($REMOTE_HASH) for ${DISTRO_PRETTY}? (y/n) " read -r answer < /dev/tty if [ "$answer" != "y" ]; then @@ -38,18 +47,24 @@ if [ "$REMOTE_HASH" != "$LOCAL_HASH" ]; then exit 0 fi - # download lsfg-vk - curl -fsSL -o "/tmp/$ZIP_NAME" "$BASE_URL/$ZIP_NAME" - if [ $? -ne 0 ]; then - echo "Failed to download lsfg-vk. Please check your internet connection." - exit 1 - fi + TEMP_DIR=$(mktemp -d) && cd "$TEMP_DIR" || { echo "Failed to create temporary directory."; exit 1; } + if [ "$USE_NIX" ]; then + # download, build and install lsfg-vk-flake from GitHub + curl -fsSL "$NIX_FLAKE_REPO/archive/refs/tags/$REMOTE_HASH.tar.gz" | tar -xz + + cd lsfg-vk-flake-* && nix build || { echo "Build failed."; rm -vrf "$TEMP_DIR"; exit 1; } + + install -Dvm644 result/lib/liblsfg-vk.so "$INSTALL_PATH/lib/liblsfg-vk.so" + install -Dvm644 result/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json "$INSTALL_PATH/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json" + else + # download and install prebuilt lsfg-vk + curl -fsSL -o "$TEMP_DIR/$ZIP_NAME" "$BASE_URL/$ZIP_NAME" || { echo "Failed to download lsfg-vk. Please check your internet connection."; rm -vrf "$TEMP_DIR"; exit 1; } + + cd "$INSTALL_PATH" && unzip -o "$TEMP_DIR/$ZIP_NAME" || { echo "Extraction failed. Is install path writable or unzip installed?"; rm -vrf "$TEMP_DIR"; exit 1; } + fi + rm -vrf "$TEMP_DIR" - # install lsfg-vk - cd "$INSTALL_PATH" || exit 1 - unzip -o "/tmp/$ZIP_NAME" echo "$REMOTE_HASH" > "$SHA_FILE" - rm -v "/tmp/$ZIP_NAME" echo "lsfg-vk for ${DISTRO_PRETTY} has been installed." else From cebe5e2e7a5bc671183791e880a5e70301676106 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 15 Jul 2025 19:14:01 +0200 Subject: [PATCH 142/253] fixing uninstall --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index b779bc0..1f2a48a 100755 --- a/install.sh +++ b/install.sh @@ -74,8 +74,8 @@ else echo -n "Do you want to uninstall lsfg-vk? (y/n) " read -r uninstall_answer < /dev/tty if [ "$uninstall_answer" = "y" ]; then - rm -v ~/.local/lib/liblsfg-vk.so - rm -v ~/.local/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json + rm -v $INSTALL_PATH/lib/liblsfg-vk.so + rm -v $INSTALL_PATH/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json rm -v "$SHA_FILE" echo "Uninstallation completed." fi From 05b8413dbc6c0c48b65e557ddcd537e4d8e50df2 Mon Sep 17 00:00:00 2001 From: Pancake Date: Fri, 18 Jul 2025 12:23:09 +0200 Subject: [PATCH 143/253] Add ko-fi as an option for donation Closes #92 --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 6c0dd38..3de5e09 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ github: [PancakeTAS] +ko_fi: pancaketas From ffd72ee5989969a2446b19897d42f5abac9580df Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 14 Jul 2025 00:06:36 +0200 Subject: [PATCH 144/253] groundwork for a configuration file --- .gitmodules | 10 +- CMakeLists.txt | 5 +- include/config/config.hpp | 51 ++++++++++ include/utils/benchmark.hpp | 10 ++ pe-parse | 1 - src/config/config.cpp | 15 +++ src/main.cpp | 96 +++++++++++++++++++ src/utils/benchmark.cpp | 182 +++++++++++++++++------------------- dxbc => thirdparty/dxbc | 0 thirdparty/pe-parse | 1 + 10 files changed, 265 insertions(+), 106 deletions(-) create mode 100644 include/config/config.hpp create mode 100644 include/utils/benchmark.hpp delete mode 160000 pe-parse create mode 100644 src/config/config.cpp create mode 100644 src/main.cpp rename dxbc => thirdparty/dxbc (100%) create mode 160000 thirdparty/pe-parse diff --git a/.gitmodules b/.gitmodules index 57f075d..a01239c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "dxbc"] - path = dxbc - url = https://github.com/PancakeTAS/dxbc.git -[submodule "pe-parse"] - path = pe-parse +[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 diff --git a/CMakeLists.txt b/CMakeLists.txt index dab32fe..cef0e3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,8 @@ add_compile_options(-fPIC -Wno-deprecated-declarations -Wno-unused-template) -add_subdirectory(dxbc EXCLUDE_FROM_ALL) -add_subdirectory(pe-parse/pe-parser-library EXCLUDE_FROM_ALL) +add_subdirectory(thirdparty/dxbc EXCLUDE_FROM_ALL) +add_subdirectory(thirdparty/pe-parse/pe-parser-library EXCLUDE_FROM_ALL) add_subdirectory(lsfg-vk-common) add_subdirectory(lsfg-vk-v3.1) add_subdirectory(lsfg-vk-v3.1p) @@ -27,6 +27,7 @@ project(lsfg-vk LANGUAGES CXX) file(GLOB SOURCES + "src/config/*.cpp" "src/extract/*.cpp" "src/mini/*.cpp" "src/utils/*.cpp" diff --git a/include/config/config.hpp b/include/config/config.hpp new file mode 100644 index 0000000..733330b --- /dev/null +++ b/include/config/config.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Config { + + /// lsfg-vk configuration. + struct Configuration { + /// Whether lsfg-vk should be loaded in the first place. + bool enable{false}; + /// Path to Lossless.dll. + std::string dll; + + /// The frame generation muliplier + size_t multiplier{2}; + /// The internal flow scale factor + float flowScale{1.0F}; + /// Whether performance mode is enabled + bool performance{false}; + /// Whether HDR is enabled + bool hdr{false}; + + /// Atomic property to check if the configuration is valid or outdated. + std::shared_ptr valid; + }; + + /// + /// Load the config file and create a file watcher. + /// + /// @param file The path to the configuration file. + /// @return Whether a configuration exists or not. + /// + /// @throws std::runtime_error if an error occurs while loading the configuration file. + /// + bool loadAndWatchConfig(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(std::string_view name); + +} diff --git a/include/utils/benchmark.hpp b/include/utils/benchmark.hpp new file mode 100644 index 0000000..f940872 --- /dev/null +++ b/include/utils/benchmark.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace Benchmark { + + /// + /// Run the benchmark. + /// + void run(); + +} diff --git a/pe-parse b/pe-parse deleted file mode 160000 index 7888f1f..0000000 --- a/pe-parse +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7888f1f8de2f6bc302c291a5b4519fad926c0133 diff --git a/src/config/config.cpp b/src/config/config.cpp new file mode 100644 index 0000000..c6b856f --- /dev/null +++ b/src/config/config.cpp @@ -0,0 +1,15 @@ +#include "config/config.hpp" + +using namespace Config; + +const Configuration defaultConf{ + .enable = false +}; + +bool Config::loadAndWatchConfig(const std::string& file) { + return false; +} + +Configuration Config::getConfig(std::string_view name) { + return defaultConf; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..d35d624 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,96 @@ +#include "config/config.hpp" + +#include +#include +#include +#include +#include +#include + +#include // NOLINT +#include + +namespace { + /// Check if the library is preloaded or Vulkan loaded. + bool isPreload() { + const char* preload = std::getenv("LD_PRELOAD"); + if (!preload || *preload == '\0') + return false; + const std::string preload_str(preload); + return preload_str.find("liblsfg-vk.so") != std::string::npos; + } + + /// Check if a benchmark is requested. + bool requestedBenchmark() { + const char* benchmark = std::getenv("LSFG_BENCHMARK"); + if (!benchmark || *benchmark == '\0') + return false; + const std::string benchmark_str(benchmark); + return benchmark_str == "1"; + } + + /// Get the process name + std::string getProcessName() { + std::array exe{}; + const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1); + if (exe_len <= 0) + return "Unknown Process"; + exe.at(static_cast(exe_len)) = '\0'; + return{basename(exe.data())}; + } + + /// Get the config file + std::string getConfigFile() { + const char* configFile = std::getenv("LSFG_CONFIG"); + if (configFile && *configFile != '\0') + return{configFile}; + const char* homePath = std::getenv("HOME"); + if (homePath && *homePath != '\0') + return std::string(homePath) + "/.config/lsfg-vk.conf"; + return "/etc/lsfg-vk.conf"; + } + + __attribute__((constructor)) void lsfgvk_init() { + if (isPreload()) { + if (requestedBenchmark()) { + // TODO: Call benchmark function. + exit(0); + } + + std::cerr << "lsfg-vk: This library is not meant to be preloaded, unless you are running a benchmark.\n"; + exit(1); + } + + // read configuration (might block) + const std::string file = getConfigFile(); + try { + Config::loadAndWatchConfig(file); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: Unable to read configuration file, exiting." << '\n'; + std::cerr << e.what() << '\n'; + exit(0); + } + + const std::string name = getProcessName(); + Config::Configuration conf{}; + try { + conf = Config::getConfig(name); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: The configuration for " << name << " is invalid, exiting." << '\n'; + std::cerr << e.what() << '\n'; + exit(0); + } + + // exit silently if not enabled + if (!conf.enable) + return; + + // print config + std::cerr << "lsfg-vk: Loaded configuration for " << name << ":\n"; + std::cerr << "lsfg-vk: Using DLL from: " << conf.dll << '\n'; + std::cerr << "lsfg-vk: Multiplier: " << conf.multiplier << '\n'; + std::cerr << "lsfg-vk: Flow Scale: " << conf.flowScale << '\n'; + std::cerr << "lsfg-vk: Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; + std::cerr << "lsfg-vk: HDR: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; + } +} diff --git a/src/utils/benchmark.cpp b/src/utils/benchmark.cpp index 47b94fc..eb86702 100644 --- a/src/utils/benchmark.cpp +++ b/src/utils/benchmark.cpp @@ -1,3 +1,4 @@ +#include "utils/benchmark.hpp" #include "extract/extract.hpp" #include "extract/trans.hpp" #include "utils/log.hpp" @@ -12,106 +13,91 @@ #include #include -namespace { - void __attribute__((constructor)) benchmark_init() { - // continue if preloaded - const char* preload = std::getenv("LD_PRELOAD"); - if (!preload || *preload == '\0') - return; - const std::string preload_str(preload); - if (preload_str.find("liblsfg-vk.so") == std::string::npos) - return; - // continue if benchmark requested - const char* benchmark = std::getenv("LSFG_BENCHMARK"); - if (!benchmark || *benchmark == '\0') - return; - const std::string benchmark_str(benchmark); - if (benchmark_str != "1") - return; +using namespace Benchmark; - // fetch benchmark parameters - const char* lsfgFlowScale = std::getenv("LSFG_FLOW_SCALE"); - const char* lsfgHdr = std::getenv("LSFG_HDR"); - const char* lsfgMultiplier = std::getenv("LSFG_MULTIPLIER"); - const char* lsfgExtentWidth = std::getenv("LSFG_EXTENT_WIDTH"); - const char* lsfgExtentHeight = std::getenv("LSFG_EXTENT_HEIGHT"); - const char* lsfgPerfMode = std::getenv("LSFG_PERF_MODE"); +void Benchmark::run() { + // fetch benchmark parameters + const char* lsfgFlowScale = std::getenv("LSFG_FLOW_SCALE"); + const char* lsfgHdr = std::getenv("LSFG_HDR"); + const char* lsfgMultiplier = std::getenv("LSFG_MULTIPLIER"); + const char* lsfgExtentWidth = std::getenv("LSFG_EXTENT_WIDTH"); + const char* lsfgExtentHeight = std::getenv("LSFG_EXTENT_HEIGHT"); + const char* lsfgPerfMode = std::getenv("LSFG_PERF_MODE"); - const float flowScale = lsfgFlowScale - ? std::stof(lsfgFlowScale) : 1.0F; - const bool isHdr = lsfgHdr - ? *lsfgHdr == '1' : false; - const uint64_t multiplier = lsfgMultiplier - ? std::stoull(std::string(lsfgMultiplier)) : 2; - const uint32_t width = lsfgExtentWidth - ? static_cast(std::stoul(lsfgExtentWidth)) : 1920; - const uint32_t height = lsfgExtentHeight - ? static_cast(std::stoul(lsfgExtentHeight)) : 1080; - const bool perfMode = lsfgPerfMode - ? *lsfgPerfMode == '1' : false; + const float flowScale = lsfgFlowScale + ? std::stof(lsfgFlowScale) : 1.0F; + const bool isHdr = lsfgHdr + ? *lsfgHdr == '1' : false; + const uint64_t multiplier = lsfgMultiplier + ? std::stoull(std::string(lsfgMultiplier)) : 2; + const uint32_t width = lsfgExtentWidth + ? static_cast(std::stoul(lsfgExtentWidth)) : 1920; + const uint32_t height = lsfgExtentHeight + ? static_cast(std::stoul(lsfgExtentHeight)) : 1080; + const bool perfMode = lsfgPerfMode + ? *lsfgPerfMode == '1' : false; - auto* lsfgInitialize = LSFG_3_1::initialize; - auto* lsfgCreateContext = LSFG_3_1::createContext; - auto* lsfgPresentContext = LSFG_3_1::presentContext; - if (perfMode) { - lsfgInitialize = LSFG_3_1P::initialize; - lsfgCreateContext = LSFG_3_1P::createContext; - lsfgPresentContext = LSFG_3_1P::presentContext; - } - - Log::info("bench", "Running {}x benchmark with {}x{} extent and flow scale of {} {} HDR", - multiplier, width, height, flowScale, isHdr ? "with" : "without"); - - // create the benchmark context - const char* lsfgDeviceUUID = std::getenv("LSFG_DEVICE_UUID"); - const uint64_t deviceUUID = lsfgDeviceUUID - ? std::stoull(std::string(lsfgDeviceUUID), nullptr, 16) : 0x1463ABAC; - - Extract::extractShaders(); - lsfgInitialize( - deviceUUID, // some magic number if not given - isHdr, 1.0F / flowScale, multiplier - 1, - [](const std::string& name) -> std::vector { - auto dxbc = Extract::getShader(name); - auto spirv = Extract::translateShader(dxbc); - return spirv; - } - ); - const int32_t ctx = lsfgCreateContext(-1, -1, {}, - { .width = width, .height = height }, - isHdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM - ); - - Log::info("bench", "Benchmark context created, ready to run"); - - // run the benchmark (run 8*n + 1 so the fences are waited on) - const auto now = std::chrono::high_resolution_clock::now(); - const uint64_t iterations = (8 * 500) + 1; - for (uint64_t count = 0; count < iterations; count++) { - lsfgPresentContext(ctx, -1, {}); - - if (count % 500 == 0) - Log::info("bench", "{:.2f}% done ({}/{})", - static_cast(count) / static_cast(iterations) * 100.0F, - count + 1, iterations); - } - const auto then = std::chrono::high_resolution_clock::now(); - - // print results - const auto ms = std::chrono::duration_cast(then - now).count(); - - const auto perIteration = static_cast(ms) / static_cast(iterations); - - const uint64_t totalGen = (multiplier - 1) * iterations; - const auto genFps = static_cast(totalGen) / (static_cast(ms) / 1000.0F); - - const uint64_t totalFrames = iterations * multiplier; - const auto totalFps = static_cast(totalFrames) / (static_cast(ms) / 1000.0F); - - Log::info("bench", "Benchmark completed in {} ms", ms); - Log::info("bench", "Time per iteration: {:.2f} ms", perIteration); - Log::info("bench", "Generation FPS: {:.2f}", genFps); - Log::info("bench", "Final FPS: {:.2f}", totalFps); - Log::info("bench", "Benchmark finished, exiting"); + auto* lsfgInitialize = LSFG_3_1::initialize; + auto* lsfgCreateContext = LSFG_3_1::createContext; + auto* lsfgPresentContext = LSFG_3_1::presentContext; + if (perfMode) { + lsfgInitialize = LSFG_3_1P::initialize; + lsfgCreateContext = LSFG_3_1P::createContext; + lsfgPresentContext = LSFG_3_1P::presentContext; } + + Log::info("bench", "Running {}x benchmark with {}x{} extent and flow scale of {} {} HDR", + multiplier, width, height, flowScale, isHdr ? "with" : "without"); + + // create the benchmark context + const char* lsfgDeviceUUID = std::getenv("LSFG_DEVICE_UUID"); + const uint64_t deviceUUID = lsfgDeviceUUID + ? std::stoull(std::string(lsfgDeviceUUID), nullptr, 16) : 0x1463ABAC; + + Extract::extractShaders(); + lsfgInitialize( + deviceUUID, // some magic number if not given + isHdr, 1.0F / flowScale, multiplier - 1, + [](const std::string& name) -> std::vector { + auto dxbc = Extract::getShader(name); + auto spirv = Extract::translateShader(dxbc); + return spirv; + } + ); + const int32_t ctx = lsfgCreateContext(-1, -1, {}, + { .width = width, .height = height }, + isHdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM + ); + + Log::info("bench", "Benchmark context created, ready to run"); + + // run the benchmark (run 8*n + 1 so the fences are waited on) + const auto now = std::chrono::high_resolution_clock::now(); + const uint64_t iterations = (8 * 500) + 1; + for (uint64_t count = 0; count < iterations; count++) { + lsfgPresentContext(ctx, -1, {}); + + if (count % 500 == 0) + Log::info("bench", "{:.2f}% done ({}/{})", + static_cast(count) / static_cast(iterations) * 100.0F, + count + 1, iterations); + } + const auto then = std::chrono::high_resolution_clock::now(); + + // print results + const auto ms = std::chrono::duration_cast(then - now).count(); + + const auto perIteration = static_cast(ms) / static_cast(iterations); + + const uint64_t totalGen = (multiplier - 1) * iterations; + const auto genFps = static_cast(totalGen) / (static_cast(ms) / 1000.0F); + + const uint64_t totalFrames = iterations * multiplier; + const auto totalFps = static_cast(totalFrames) / (static_cast(ms) / 1000.0F); + + Log::info("bench", "Benchmark completed in {} ms", ms); + Log::info("bench", "Time per iteration: {:.2f} ms", perIteration); + Log::info("bench", "Generation FPS: {:.2f}", genFps); + Log::info("bench", "Final FPS: {:.2f}", totalFps); + Log::info("bench", "Benchmark finished, exiting"); } diff --git a/dxbc b/thirdparty/dxbc similarity index 100% rename from dxbc rename to thirdparty/dxbc diff --git a/thirdparty/pe-parse b/thirdparty/pe-parse new file mode 160000 index 0000000..31ac596 --- /dev/null +++ b/thirdparty/pe-parse @@ -0,0 +1 @@ +Subproject commit 31ac5966503689d5693cd9fb520bd525a8710e17 From 3f64d20d8e6ef6b471926ad0f8746d32ce4a6ef2 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 16 Jul 2025 17:32:45 +0200 Subject: [PATCH 145/253] move subprojects into dedicated folder --- CMakeLists.txt | 7 +-- {lsfg-vk-v3.1 => framegen}/.clang-tidy | 0 {lsfg-vk-v3.1 => framegen}/.gitattributes | 0 {lsfg-vk-v3.1 => framegen}/.gitignore | 0 {lsfg-vk-common => framegen}/CMakeLists.txt | 43 +++++++++---- {lsfg-vk-v3.1 => framegen}/LICENSE.md | 0 framegen/README.md | 14 +++++ .../include/common/exception.hpp | 0 .../include/common/utils.hpp | 0 .../include/core/buffer.hpp | 0 .../include/core/commandbuffer.hpp | 0 .../include/core/commandpool.hpp | 0 .../include/core/descriptorpool.hpp | 0 .../include/core/descriptorset.hpp | 0 .../include/core/device.hpp | 0 .../include/core/fence.hpp | 0 .../include/core/image.hpp | 0 .../include/core/instance.hpp | 0 .../include/core/pipeline.hpp | 0 .../include/core/sampler.hpp | 0 .../include/core/semaphore.hpp | 0 .../include/core/shadermodule.hpp | 0 .../include/pool/resourcepool.hpp | 0 .../include/pool/shaderpool.hpp | 0 .../public/lsfg_3_1.hpp | 0 .../public/lsfg_3_1p.hpp | 0 .../src/common/exception.cpp | 0 .../src/common/utils.cpp | 0 .../src/core/buffer.cpp | 0 .../src/core/commandbuffer.cpp | 0 .../src/core/commandpool.cpp | 0 .../src/core/descriptorpool.cpp | 0 .../src/core/descriptorset.cpp | 0 .../src/core/device.cpp | 0 .../src/core/fence.cpp | 0 .../src/core/image.cpp | 0 .../src/core/instance.cpp | 0 .../src/core/pipeline.cpp | 0 .../src/core/sampler.cpp | 0 .../src/core/semaphore.cpp | 0 .../src/core/shadermodule.cpp | 0 .../src/pool/resourcepool.cpp | 0 .../src/pool/shaderpool.cpp | 0 .../v3.1_include/v3_1}/context.hpp | 0 .../v3.1_include/v3_1}/shaders/alpha.hpp | 0 .../v3.1_include/v3_1}/shaders/beta.hpp | 0 .../v3.1_include/v3_1}/shaders/delta.hpp | 0 .../v3.1_include/v3_1}/shaders/gamma.hpp | 0 .../v3.1_include/v3_1}/shaders/generate.hpp | 0 .../v3.1_include/v3_1}/shaders/mipmaps.hpp | 0 .../src => framegen/v3.1_src}/context.cpp | 2 +- .../src => framegen/v3.1_src}/lsfg.cpp | 2 +- .../v3.1_src}/shaders/alpha.cpp | 2 +- .../v3.1_src}/shaders/beta.cpp | 2 +- .../v3.1_src}/shaders/delta.cpp | 2 +- .../v3.1_src}/shaders/gamma.cpp | 2 +- .../v3.1_src}/shaders/generate.cpp | 2 +- .../v3.1_src}/shaders/mipmaps.cpp | 2 +- .../v3.1p_include/v3_1p}/context.hpp | 0 .../v3.1p_include/v3_1p}/shaders/alpha.hpp | 0 .../v3.1p_include/v3_1p}/shaders/beta.hpp | 0 .../v3.1p_include/v3_1p}/shaders/delta.hpp | 0 .../v3.1p_include/v3_1p}/shaders/gamma.hpp | 0 .../v3.1p_include/v3_1p}/shaders/generate.hpp | 0 .../v3.1p_include/v3_1p}/shaders/mipmaps.hpp | 0 .../src => framegen/v3.1p_src}/context.cpp | 2 +- .../src => framegen/v3.1p_src}/lsfg.cpp | 2 +- .../v3.1p_src}/shaders/alpha.cpp | 2 +- .../v3.1p_src}/shaders/beta.cpp | 2 +- .../v3.1p_src}/shaders/delta.cpp | 2 +- .../v3.1p_src}/shaders/gamma.cpp | 2 +- .../v3.1p_src}/shaders/generate.cpp | 2 +- .../v3.1p_src}/shaders/mipmaps.cpp | 2 +- lsfg-vk-v3.1/CMakeLists.txt | 60 ------------------- lsfg-vk-v3.1/README.md | 14 ----- lsfg-vk-v3.1p/.clang-tidy | 26 -------- lsfg-vk-v3.1p/.gitattributes | 3 - lsfg-vk-v3.1p/.gitignore | 9 --- lsfg-vk-v3.1p/CMakeLists.txt | 60 ------------------- lsfg-vk-v3.1p/LICENSE.md | 21 ------- lsfg-vk-v3.1p/README.md | 14 ----- 81 files changed, 62 insertions(+), 241 deletions(-) rename {lsfg-vk-v3.1 => framegen}/.clang-tidy (100%) rename {lsfg-vk-v3.1 => framegen}/.gitattributes (100%) rename {lsfg-vk-v3.1 => framegen}/.gitignore (100%) rename {lsfg-vk-common => framegen}/CMakeLists.txt (54%) rename {lsfg-vk-v3.1 => framegen}/LICENSE.md (100%) create mode 100644 framegen/README.md rename {lsfg-vk-common => framegen}/include/common/exception.hpp (100%) rename {lsfg-vk-common => framegen}/include/common/utils.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/buffer.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/commandbuffer.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/commandpool.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/descriptorpool.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/descriptorset.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/device.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/fence.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/image.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/instance.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/pipeline.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/sampler.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/semaphore.hpp (100%) rename {lsfg-vk-common => framegen}/include/core/shadermodule.hpp (100%) rename {lsfg-vk-common => framegen}/include/pool/resourcepool.hpp (100%) rename {lsfg-vk-common => framegen}/include/pool/shaderpool.hpp (100%) rename {lsfg-vk-v3.1 => framegen}/public/lsfg_3_1.hpp (100%) rename {lsfg-vk-v3.1p => framegen}/public/lsfg_3_1p.hpp (100%) rename {lsfg-vk-common => framegen}/src/common/exception.cpp (100%) rename {lsfg-vk-common => framegen}/src/common/utils.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/buffer.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/commandbuffer.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/commandpool.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/descriptorpool.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/descriptorset.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/device.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/fence.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/image.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/instance.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/pipeline.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/sampler.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/semaphore.cpp (100%) rename {lsfg-vk-common => framegen}/src/core/shadermodule.cpp (100%) rename {lsfg-vk-common => framegen}/src/pool/resourcepool.cpp (100%) rename {lsfg-vk-common => framegen}/src/pool/shaderpool.cpp (100%) rename {lsfg-vk-v3.1/include => framegen/v3.1_include/v3_1}/context.hpp (100%) rename {lsfg-vk-v3.1/include => framegen/v3.1_include/v3_1}/shaders/alpha.hpp (100%) rename {lsfg-vk-v3.1/include => framegen/v3.1_include/v3_1}/shaders/beta.hpp (100%) rename {lsfg-vk-v3.1/include => framegen/v3.1_include/v3_1}/shaders/delta.hpp (100%) rename {lsfg-vk-v3.1/include => framegen/v3.1_include/v3_1}/shaders/gamma.hpp (100%) rename {lsfg-vk-v3.1/include => framegen/v3.1_include/v3_1}/shaders/generate.hpp (100%) rename {lsfg-vk-v3.1/include => framegen/v3.1_include/v3_1}/shaders/mipmaps.hpp (100%) rename {lsfg-vk-v3.1/src => framegen/v3.1_src}/context.cpp (99%) rename {lsfg-vk-v3.1/src => framegen/v3.1_src}/lsfg.cpp (99%) rename {lsfg-vk-v3.1/src => framegen/v3.1_src}/shaders/alpha.cpp (99%) rename {lsfg-vk-v3.1/src => framegen/v3.1_src}/shaders/beta.cpp (99%) rename {lsfg-vk-v3.1/src => framegen/v3.1_src}/shaders/delta.cpp (99%) rename {lsfg-vk-v3.1/src => framegen/v3.1_src}/shaders/gamma.cpp (99%) rename {lsfg-vk-v3.1/src => framegen/v3.1_src}/shaders/generate.cpp (98%) rename {lsfg-vk-v3.1/src => framegen/v3.1_src}/shaders/mipmaps.cpp (98%) rename {lsfg-vk-v3.1p/include => framegen/v3.1p_include/v3_1p}/context.hpp (100%) rename {lsfg-vk-v3.1p/include => framegen/v3.1p_include/v3_1p}/shaders/alpha.hpp (100%) rename {lsfg-vk-v3.1p/include => framegen/v3.1p_include/v3_1p}/shaders/beta.hpp (100%) rename {lsfg-vk-v3.1p/include => framegen/v3.1p_include/v3_1p}/shaders/delta.hpp (100%) rename {lsfg-vk-v3.1p/include => framegen/v3.1p_include/v3_1p}/shaders/gamma.hpp (100%) rename {lsfg-vk-v3.1p/include => framegen/v3.1p_include/v3_1p}/shaders/generate.hpp (100%) rename {lsfg-vk-v3.1p/include => framegen/v3.1p_include/v3_1p}/shaders/mipmaps.hpp (100%) rename {lsfg-vk-v3.1p/src => framegen/v3.1p_src}/context.cpp (99%) rename {lsfg-vk-v3.1p/src => framegen/v3.1p_src}/lsfg.cpp (99%) rename {lsfg-vk-v3.1p/src => framegen/v3.1p_src}/shaders/alpha.cpp (99%) rename {lsfg-vk-v3.1p/src => framegen/v3.1p_src}/shaders/beta.cpp (99%) rename {lsfg-vk-v3.1p/src => framegen/v3.1p_src}/shaders/delta.cpp (99%) rename {lsfg-vk-v3.1p/src => framegen/v3.1p_src}/shaders/gamma.cpp (99%) rename {lsfg-vk-v3.1p/src => framegen/v3.1p_src}/shaders/generate.cpp (98%) rename {lsfg-vk-v3.1p/src => framegen/v3.1p_src}/shaders/mipmaps.cpp (98%) delete mode 100644 lsfg-vk-v3.1/CMakeLists.txt delete mode 100644 lsfg-vk-v3.1/README.md delete mode 100644 lsfg-vk-v3.1p/.clang-tidy delete mode 100644 lsfg-vk-v3.1p/.gitattributes delete mode 100644 lsfg-vk-v3.1p/.gitignore delete mode 100644 lsfg-vk-v3.1p/CMakeLists.txt delete mode 100644 lsfg-vk-v3.1p/LICENSE.md delete mode 100644 lsfg-vk-v3.1p/README.md diff --git a/CMakeLists.txt b/CMakeLists.txt index cef0e3b..0eb28de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,9 +16,7 @@ add_compile_options(-fPIC add_subdirectory(thirdparty/dxbc EXCLUDE_FROM_ALL) add_subdirectory(thirdparty/pe-parse/pe-parser-library EXCLUDE_FROM_ALL) -add_subdirectory(lsfg-vk-common) -add_subdirectory(lsfg-vk-v3.1) -add_subdirectory(lsfg-vk-v3.1p) +add_subdirectory(framegen) # main project project(lsfg-vk @@ -43,8 +41,7 @@ set_target_properties(lsfg-vk PROPERTIES target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk PRIVATE - lsfg-vk-common lsfg-vk-v3.1 lsfg-vk-v3.1p - pe-parse dxbc vulkan) + lsfg-vk-framegen pe-parse dxbc vulkan) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(lsfg-vk PROPERTIES diff --git a/lsfg-vk-v3.1/.clang-tidy b/framegen/.clang-tidy similarity index 100% rename from lsfg-vk-v3.1/.clang-tidy rename to framegen/.clang-tidy diff --git a/lsfg-vk-v3.1/.gitattributes b/framegen/.gitattributes similarity index 100% rename from lsfg-vk-v3.1/.gitattributes rename to framegen/.gitattributes diff --git a/lsfg-vk-v3.1/.gitignore b/framegen/.gitignore similarity index 100% rename from lsfg-vk-v3.1/.gitignore rename to framegen/.gitignore diff --git a/lsfg-vk-common/CMakeLists.txt b/framegen/CMakeLists.txt similarity index 54% rename from lsfg-vk-common/CMakeLists.txt rename to framegen/CMakeLists.txt index a6cf0c9..58bcea2 100644 --- a/lsfg-vk-common/CMakeLists.txt +++ b/framegen/CMakeLists.txt @@ -1,40 +1,57 @@ cmake_minimum_required(VERSION 3.28) -# project -project(lsfg-vk-common +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) + +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/common/*.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-common STATIC ${SOURCES}) +add_library(lsfg-vk-framegen STATIC ${SOURCES}) # target -set_target_properties(lsfg-vk-common PROPERTIES +set_target_properties(lsfg-vk-framegen PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON) -target_include_directories(lsfg-vk-common - PUBLIC include) -target_link_libraries(lsfg-vk-common - PRIVATE vulkan) +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 vulkan) if(CMAKE_BUILD_TYPE STREQUAL "Release") - set_target_properties(lsfg-vk-common PROPERTIES + set_target_properties(lsfg-vk-framegen PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON) endif() # diagnostics if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set_target_properties(lsfg-vk-common PROPERTIES + set_target_properties(lsfg-vk-framegen PROPERTIES EXPORT_COMPILE_COMMANDS ON) endif() if(LSFGVK_EXCESS_DEBUG) - target_compile_options(lsfg-vk-common PRIVATE + target_compile_options(lsfg-vk-framegen PRIVATE -Weverything # disable compat c++ flags -Wno-pre-c++20-compat-pedantic @@ -52,6 +69,6 @@ if(LSFGVK_EXCESS_DEBUG) -Wno-cast-function-type-strict # for vulkan ) - set_target_properties(lsfg-vk-common PROPERTIES + set_target_properties(lsfg-vk-framegen PROPERTIES CXX_CLANG_TIDY clang-tidy) endif() diff --git a/lsfg-vk-v3.1/LICENSE.md b/framegen/LICENSE.md similarity index 100% rename from lsfg-vk-v3.1/LICENSE.md rename to framegen/LICENSE.md diff --git a/framegen/README.md b/framegen/README.md new file mode 100644 index 0000000..aff3912 --- /dev/null +++ b/framegen/README.md @@ -0,0 +1,14 @@ +## lsfg-vk-framegen +Lossless Scaling Frame Generation + +This is a subproject of lsfg-vk and contains the dedicated Vulkan logic for generating frames. + +The project is intentionally structured as a fully external project, such that it can be integrated into other applications. + +### Interface + +Interfacing with lsfg-vk-framegen is done via `lsfg_x_x.hpp` header. The internal Vulkan instance is created using `LSFG_X_X::initialize()` and requires a specific deviceUUID, as well as parts of the lsfg-vk configuration, including a function loading SPIR-V shaders by name. Cleanup is done via `LSFG_X_X::finalize()` after which `LSFG_X_X::initialize()` may be called again. Please note that the initialization process is expensive and may take a while. It is recommended to call this function once during the applications lifetime. + +Once the format and extent of the requested images is determined, `LSFG_X_X::createContext()` should be called to initialize a frame generation context. The Vulkan images are created from backing memory, which is passed through the file descriptor arguments. A context can be destroyed using `LSFG_X_X::deleteContext()`. + +Presenting the context can be done via `LSFG_X_X::presentContext()`. Before calling the function a second time, make sure the outgoing semaphores have been signaled. diff --git a/lsfg-vk-common/include/common/exception.hpp b/framegen/include/common/exception.hpp similarity index 100% rename from lsfg-vk-common/include/common/exception.hpp rename to framegen/include/common/exception.hpp diff --git a/lsfg-vk-common/include/common/utils.hpp b/framegen/include/common/utils.hpp similarity index 100% rename from lsfg-vk-common/include/common/utils.hpp rename to framegen/include/common/utils.hpp diff --git a/lsfg-vk-common/include/core/buffer.hpp b/framegen/include/core/buffer.hpp similarity index 100% rename from lsfg-vk-common/include/core/buffer.hpp rename to framegen/include/core/buffer.hpp diff --git a/lsfg-vk-common/include/core/commandbuffer.hpp b/framegen/include/core/commandbuffer.hpp similarity index 100% rename from lsfg-vk-common/include/core/commandbuffer.hpp rename to framegen/include/core/commandbuffer.hpp diff --git a/lsfg-vk-common/include/core/commandpool.hpp b/framegen/include/core/commandpool.hpp similarity index 100% rename from lsfg-vk-common/include/core/commandpool.hpp rename to framegen/include/core/commandpool.hpp diff --git a/lsfg-vk-common/include/core/descriptorpool.hpp b/framegen/include/core/descriptorpool.hpp similarity index 100% rename from lsfg-vk-common/include/core/descriptorpool.hpp rename to framegen/include/core/descriptorpool.hpp diff --git a/lsfg-vk-common/include/core/descriptorset.hpp b/framegen/include/core/descriptorset.hpp similarity index 100% rename from lsfg-vk-common/include/core/descriptorset.hpp rename to framegen/include/core/descriptorset.hpp diff --git a/lsfg-vk-common/include/core/device.hpp b/framegen/include/core/device.hpp similarity index 100% rename from lsfg-vk-common/include/core/device.hpp rename to framegen/include/core/device.hpp diff --git a/lsfg-vk-common/include/core/fence.hpp b/framegen/include/core/fence.hpp similarity index 100% rename from lsfg-vk-common/include/core/fence.hpp rename to framegen/include/core/fence.hpp diff --git a/lsfg-vk-common/include/core/image.hpp b/framegen/include/core/image.hpp similarity index 100% rename from lsfg-vk-common/include/core/image.hpp rename to framegen/include/core/image.hpp diff --git a/lsfg-vk-common/include/core/instance.hpp b/framegen/include/core/instance.hpp similarity index 100% rename from lsfg-vk-common/include/core/instance.hpp rename to framegen/include/core/instance.hpp diff --git a/lsfg-vk-common/include/core/pipeline.hpp b/framegen/include/core/pipeline.hpp similarity index 100% rename from lsfg-vk-common/include/core/pipeline.hpp rename to framegen/include/core/pipeline.hpp diff --git a/lsfg-vk-common/include/core/sampler.hpp b/framegen/include/core/sampler.hpp similarity index 100% rename from lsfg-vk-common/include/core/sampler.hpp rename to framegen/include/core/sampler.hpp diff --git a/lsfg-vk-common/include/core/semaphore.hpp b/framegen/include/core/semaphore.hpp similarity index 100% rename from lsfg-vk-common/include/core/semaphore.hpp rename to framegen/include/core/semaphore.hpp diff --git a/lsfg-vk-common/include/core/shadermodule.hpp b/framegen/include/core/shadermodule.hpp similarity index 100% rename from lsfg-vk-common/include/core/shadermodule.hpp rename to framegen/include/core/shadermodule.hpp diff --git a/lsfg-vk-common/include/pool/resourcepool.hpp b/framegen/include/pool/resourcepool.hpp similarity index 100% rename from lsfg-vk-common/include/pool/resourcepool.hpp rename to framegen/include/pool/resourcepool.hpp diff --git a/lsfg-vk-common/include/pool/shaderpool.hpp b/framegen/include/pool/shaderpool.hpp similarity index 100% rename from lsfg-vk-common/include/pool/shaderpool.hpp rename to framegen/include/pool/shaderpool.hpp diff --git a/lsfg-vk-v3.1/public/lsfg_3_1.hpp b/framegen/public/lsfg_3_1.hpp similarity index 100% rename from lsfg-vk-v3.1/public/lsfg_3_1.hpp rename to framegen/public/lsfg_3_1.hpp diff --git a/lsfg-vk-v3.1p/public/lsfg_3_1p.hpp b/framegen/public/lsfg_3_1p.hpp similarity index 100% rename from lsfg-vk-v3.1p/public/lsfg_3_1p.hpp rename to framegen/public/lsfg_3_1p.hpp diff --git a/lsfg-vk-common/src/common/exception.cpp b/framegen/src/common/exception.cpp similarity index 100% rename from lsfg-vk-common/src/common/exception.cpp rename to framegen/src/common/exception.cpp diff --git a/lsfg-vk-common/src/common/utils.cpp b/framegen/src/common/utils.cpp similarity index 100% rename from lsfg-vk-common/src/common/utils.cpp rename to framegen/src/common/utils.cpp diff --git a/lsfg-vk-common/src/core/buffer.cpp b/framegen/src/core/buffer.cpp similarity index 100% rename from lsfg-vk-common/src/core/buffer.cpp rename to framegen/src/core/buffer.cpp diff --git a/lsfg-vk-common/src/core/commandbuffer.cpp b/framegen/src/core/commandbuffer.cpp similarity index 100% rename from lsfg-vk-common/src/core/commandbuffer.cpp rename to framegen/src/core/commandbuffer.cpp diff --git a/lsfg-vk-common/src/core/commandpool.cpp b/framegen/src/core/commandpool.cpp similarity index 100% rename from lsfg-vk-common/src/core/commandpool.cpp rename to framegen/src/core/commandpool.cpp diff --git a/lsfg-vk-common/src/core/descriptorpool.cpp b/framegen/src/core/descriptorpool.cpp similarity index 100% rename from lsfg-vk-common/src/core/descriptorpool.cpp rename to framegen/src/core/descriptorpool.cpp diff --git a/lsfg-vk-common/src/core/descriptorset.cpp b/framegen/src/core/descriptorset.cpp similarity index 100% rename from lsfg-vk-common/src/core/descriptorset.cpp rename to framegen/src/core/descriptorset.cpp diff --git a/lsfg-vk-common/src/core/device.cpp b/framegen/src/core/device.cpp similarity index 100% rename from lsfg-vk-common/src/core/device.cpp rename to framegen/src/core/device.cpp diff --git a/lsfg-vk-common/src/core/fence.cpp b/framegen/src/core/fence.cpp similarity index 100% rename from lsfg-vk-common/src/core/fence.cpp rename to framegen/src/core/fence.cpp diff --git a/lsfg-vk-common/src/core/image.cpp b/framegen/src/core/image.cpp similarity index 100% rename from lsfg-vk-common/src/core/image.cpp rename to framegen/src/core/image.cpp diff --git a/lsfg-vk-common/src/core/instance.cpp b/framegen/src/core/instance.cpp similarity index 100% rename from lsfg-vk-common/src/core/instance.cpp rename to framegen/src/core/instance.cpp diff --git a/lsfg-vk-common/src/core/pipeline.cpp b/framegen/src/core/pipeline.cpp similarity index 100% rename from lsfg-vk-common/src/core/pipeline.cpp rename to framegen/src/core/pipeline.cpp diff --git a/lsfg-vk-common/src/core/sampler.cpp b/framegen/src/core/sampler.cpp similarity index 100% rename from lsfg-vk-common/src/core/sampler.cpp rename to framegen/src/core/sampler.cpp diff --git a/lsfg-vk-common/src/core/semaphore.cpp b/framegen/src/core/semaphore.cpp similarity index 100% rename from lsfg-vk-common/src/core/semaphore.cpp rename to framegen/src/core/semaphore.cpp diff --git a/lsfg-vk-common/src/core/shadermodule.cpp b/framegen/src/core/shadermodule.cpp similarity index 100% rename from lsfg-vk-common/src/core/shadermodule.cpp rename to framegen/src/core/shadermodule.cpp diff --git a/lsfg-vk-common/src/pool/resourcepool.cpp b/framegen/src/pool/resourcepool.cpp similarity index 100% rename from lsfg-vk-common/src/pool/resourcepool.cpp rename to framegen/src/pool/resourcepool.cpp diff --git a/lsfg-vk-common/src/pool/shaderpool.cpp b/framegen/src/pool/shaderpool.cpp similarity index 100% rename from lsfg-vk-common/src/pool/shaderpool.cpp rename to framegen/src/pool/shaderpool.cpp diff --git a/lsfg-vk-v3.1/include/context.hpp b/framegen/v3.1_include/v3_1/context.hpp similarity index 100% rename from lsfg-vk-v3.1/include/context.hpp rename to framegen/v3.1_include/v3_1/context.hpp diff --git a/lsfg-vk-v3.1/include/shaders/alpha.hpp b/framegen/v3.1_include/v3_1/shaders/alpha.hpp similarity index 100% rename from lsfg-vk-v3.1/include/shaders/alpha.hpp rename to framegen/v3.1_include/v3_1/shaders/alpha.hpp diff --git a/lsfg-vk-v3.1/include/shaders/beta.hpp b/framegen/v3.1_include/v3_1/shaders/beta.hpp similarity index 100% rename from lsfg-vk-v3.1/include/shaders/beta.hpp rename to framegen/v3.1_include/v3_1/shaders/beta.hpp diff --git a/lsfg-vk-v3.1/include/shaders/delta.hpp b/framegen/v3.1_include/v3_1/shaders/delta.hpp similarity index 100% rename from lsfg-vk-v3.1/include/shaders/delta.hpp rename to framegen/v3.1_include/v3_1/shaders/delta.hpp diff --git a/lsfg-vk-v3.1/include/shaders/gamma.hpp b/framegen/v3.1_include/v3_1/shaders/gamma.hpp similarity index 100% rename from lsfg-vk-v3.1/include/shaders/gamma.hpp rename to framegen/v3.1_include/v3_1/shaders/gamma.hpp diff --git a/lsfg-vk-v3.1/include/shaders/generate.hpp b/framegen/v3.1_include/v3_1/shaders/generate.hpp similarity index 100% rename from lsfg-vk-v3.1/include/shaders/generate.hpp rename to framegen/v3.1_include/v3_1/shaders/generate.hpp diff --git a/lsfg-vk-v3.1/include/shaders/mipmaps.hpp b/framegen/v3.1_include/v3_1/shaders/mipmaps.hpp similarity index 100% rename from lsfg-vk-v3.1/include/shaders/mipmaps.hpp rename to framegen/v3.1_include/v3_1/shaders/mipmaps.hpp diff --git a/lsfg-vk-v3.1/src/context.cpp b/framegen/v3.1_src/context.cpp similarity index 99% rename from lsfg-vk-v3.1/src/context.cpp rename to framegen/v3.1_src/context.cpp index 362d337..c594ed9 100644 --- a/lsfg-vk-v3.1/src/context.cpp +++ b/framegen/v3.1_src/context.cpp @@ -1,4 +1,4 @@ -#include "context.hpp" +#include "v3_1/context.hpp" #include "common/utils.hpp" #include "common/exception.hpp" diff --git a/lsfg-vk-v3.1/src/lsfg.cpp b/framegen/v3.1_src/lsfg.cpp similarity index 99% rename from lsfg-vk-v3.1/src/lsfg.cpp rename to framegen/v3.1_src/lsfg.cpp index 75e0992..9221730 100644 --- a/lsfg-vk-v3.1/src/lsfg.cpp +++ b/framegen/v3.1_src/lsfg.cpp @@ -1,5 +1,5 @@ #include "lsfg_3_1.hpp" -#include "context.hpp" +#include "v3_1/context.hpp" #include "core/commandpool.hpp" #include "core/descriptorpool.hpp" #include "core/instance.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/alpha.cpp b/framegen/v3.1_src/shaders/alpha.cpp similarity index 99% rename from lsfg-vk-v3.1/src/shaders/alpha.cpp rename to framegen/v3.1_src/shaders/alpha.cpp index 58b03ec..7e9ef6f 100644 --- a/lsfg-vk-v3.1/src/shaders/alpha.cpp +++ b/framegen/v3.1_src/shaders/alpha.cpp @@ -1,4 +1,4 @@ -#include "shaders/alpha.hpp" +#include "v3_1/shaders/alpha.hpp" #include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/beta.cpp b/framegen/v3.1_src/shaders/beta.cpp similarity index 99% rename from lsfg-vk-v3.1/src/shaders/beta.cpp rename to framegen/v3.1_src/shaders/beta.cpp index 96c49b5..34d8f69 100644 --- a/lsfg-vk-v3.1/src/shaders/beta.cpp +++ b/framegen/v3.1_src/shaders/beta.cpp @@ -1,4 +1,4 @@ -#include "shaders/beta.hpp" +#include "v3_1/shaders/beta.hpp" #include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/delta.cpp b/framegen/v3.1_src/shaders/delta.cpp similarity index 99% rename from lsfg-vk-v3.1/src/shaders/delta.cpp rename to framegen/v3.1_src/shaders/delta.cpp index 1183e51..0928428 100644 --- a/lsfg-vk-v3.1/src/shaders/delta.cpp +++ b/framegen/v3.1_src/shaders/delta.cpp @@ -1,4 +1,4 @@ -#include "shaders/delta.hpp" +#include "v3_1/shaders/delta.hpp" #include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/gamma.cpp b/framegen/v3.1_src/shaders/gamma.cpp similarity index 99% rename from lsfg-vk-v3.1/src/shaders/gamma.cpp rename to framegen/v3.1_src/shaders/gamma.cpp index baf0c0f..c4a64df 100644 --- a/lsfg-vk-v3.1/src/shaders/gamma.cpp +++ b/framegen/v3.1_src/shaders/gamma.cpp @@ -1,4 +1,4 @@ -#include "shaders/gamma.hpp" +#include "v3_1/shaders/gamma.hpp" #include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/generate.cpp b/framegen/v3.1_src/shaders/generate.cpp similarity index 98% rename from lsfg-vk-v3.1/src/shaders/generate.cpp rename to framegen/v3.1_src/shaders/generate.cpp index 6d2569b..77aa441 100644 --- a/lsfg-vk-v3.1/src/shaders/generate.cpp +++ b/framegen/v3.1_src/shaders/generate.cpp @@ -1,4 +1,4 @@ -#include "shaders/generate.hpp" +#include "v3_1/shaders/generate.hpp" #include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1/src/shaders/mipmaps.cpp b/framegen/v3.1_src/shaders/mipmaps.cpp similarity index 98% rename from lsfg-vk-v3.1/src/shaders/mipmaps.cpp rename to framegen/v3.1_src/shaders/mipmaps.cpp index 0bc5065..f4d0f74 100644 --- a/lsfg-vk-v3.1/src/shaders/mipmaps.cpp +++ b/framegen/v3.1_src/shaders/mipmaps.cpp @@ -1,4 +1,4 @@ -#include "shaders/mipmaps.hpp" +#include "v3_1/shaders/mipmaps.hpp" #include "common/utils.hpp" #include "core/image.hpp" #include "core/commandbuffer.hpp" diff --git a/lsfg-vk-v3.1p/include/context.hpp b/framegen/v3.1p_include/v3_1p/context.hpp similarity index 100% rename from lsfg-vk-v3.1p/include/context.hpp rename to framegen/v3.1p_include/v3_1p/context.hpp diff --git a/lsfg-vk-v3.1p/include/shaders/alpha.hpp b/framegen/v3.1p_include/v3_1p/shaders/alpha.hpp similarity index 100% rename from lsfg-vk-v3.1p/include/shaders/alpha.hpp rename to framegen/v3.1p_include/v3_1p/shaders/alpha.hpp diff --git a/lsfg-vk-v3.1p/include/shaders/beta.hpp b/framegen/v3.1p_include/v3_1p/shaders/beta.hpp similarity index 100% rename from lsfg-vk-v3.1p/include/shaders/beta.hpp rename to framegen/v3.1p_include/v3_1p/shaders/beta.hpp diff --git a/lsfg-vk-v3.1p/include/shaders/delta.hpp b/framegen/v3.1p_include/v3_1p/shaders/delta.hpp similarity index 100% rename from lsfg-vk-v3.1p/include/shaders/delta.hpp rename to framegen/v3.1p_include/v3_1p/shaders/delta.hpp diff --git a/lsfg-vk-v3.1p/include/shaders/gamma.hpp b/framegen/v3.1p_include/v3_1p/shaders/gamma.hpp similarity index 100% rename from lsfg-vk-v3.1p/include/shaders/gamma.hpp rename to framegen/v3.1p_include/v3_1p/shaders/gamma.hpp diff --git a/lsfg-vk-v3.1p/include/shaders/generate.hpp b/framegen/v3.1p_include/v3_1p/shaders/generate.hpp similarity index 100% rename from lsfg-vk-v3.1p/include/shaders/generate.hpp rename to framegen/v3.1p_include/v3_1p/shaders/generate.hpp diff --git a/lsfg-vk-v3.1p/include/shaders/mipmaps.hpp b/framegen/v3.1p_include/v3_1p/shaders/mipmaps.hpp similarity index 100% rename from lsfg-vk-v3.1p/include/shaders/mipmaps.hpp rename to framegen/v3.1p_include/v3_1p/shaders/mipmaps.hpp diff --git a/lsfg-vk-v3.1p/src/context.cpp b/framegen/v3.1p_src/context.cpp similarity index 99% rename from lsfg-vk-v3.1p/src/context.cpp rename to framegen/v3.1p_src/context.cpp index 5a18353..00d07a3 100644 --- a/lsfg-vk-v3.1p/src/context.cpp +++ b/framegen/v3.1p_src/context.cpp @@ -1,4 +1,4 @@ -#include "context.hpp" +#include "v3_1p/context.hpp" #include "common/utils.hpp" #include "common/exception.hpp" diff --git a/lsfg-vk-v3.1p/src/lsfg.cpp b/framegen/v3.1p_src/lsfg.cpp similarity index 99% rename from lsfg-vk-v3.1p/src/lsfg.cpp rename to framegen/v3.1p_src/lsfg.cpp index 276385a..e0d1b69 100644 --- a/lsfg-vk-v3.1p/src/lsfg.cpp +++ b/framegen/v3.1p_src/lsfg.cpp @@ -1,5 +1,5 @@ #include "lsfg_3_1p.hpp" -#include "context.hpp" +#include "v3_1p/context.hpp" #include "core/commandpool.hpp" #include "core/descriptorpool.hpp" #include "core/instance.hpp" diff --git a/lsfg-vk-v3.1p/src/shaders/alpha.cpp b/framegen/v3.1p_src/shaders/alpha.cpp similarity index 99% rename from lsfg-vk-v3.1p/src/shaders/alpha.cpp rename to framegen/v3.1p_src/shaders/alpha.cpp index 2209adb..c2d4dcd 100644 --- a/lsfg-vk-v3.1p/src/shaders/alpha.cpp +++ b/framegen/v3.1p_src/shaders/alpha.cpp @@ -1,4 +1,4 @@ -#include "shaders/alpha.hpp" +#include "v3_1p/shaders/alpha.hpp" #include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1p/src/shaders/beta.cpp b/framegen/v3.1p_src/shaders/beta.cpp similarity index 99% rename from lsfg-vk-v3.1p/src/shaders/beta.cpp rename to framegen/v3.1p_src/shaders/beta.cpp index c82976a..76bb751 100644 --- a/lsfg-vk-v3.1p/src/shaders/beta.cpp +++ b/framegen/v3.1p_src/shaders/beta.cpp @@ -1,4 +1,4 @@ -#include "shaders/beta.hpp" +#include "v3_1p/shaders/beta.hpp" #include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1p/src/shaders/delta.cpp b/framegen/v3.1p_src/shaders/delta.cpp similarity index 99% rename from lsfg-vk-v3.1p/src/shaders/delta.cpp rename to framegen/v3.1p_src/shaders/delta.cpp index 4d222dc..987a8cc 100644 --- a/lsfg-vk-v3.1p/src/shaders/delta.cpp +++ b/framegen/v3.1p_src/shaders/delta.cpp @@ -1,4 +1,4 @@ -#include "shaders/delta.hpp" +#include "v3_1p/shaders/delta.hpp" #include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1p/src/shaders/gamma.cpp b/framegen/v3.1p_src/shaders/gamma.cpp similarity index 99% rename from lsfg-vk-v3.1p/src/shaders/gamma.cpp rename to framegen/v3.1p_src/shaders/gamma.cpp index 350f7a6..08f8eab 100644 --- a/lsfg-vk-v3.1p/src/shaders/gamma.cpp +++ b/framegen/v3.1p_src/shaders/gamma.cpp @@ -1,4 +1,4 @@ -#include "shaders/gamma.hpp" +#include "v3_1p/shaders/gamma.hpp" #include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1p/src/shaders/generate.cpp b/framegen/v3.1p_src/shaders/generate.cpp similarity index 98% rename from lsfg-vk-v3.1p/src/shaders/generate.cpp rename to framegen/v3.1p_src/shaders/generate.cpp index 5d3fe78..59447c8 100644 --- a/lsfg-vk-v3.1p/src/shaders/generate.cpp +++ b/framegen/v3.1p_src/shaders/generate.cpp @@ -1,4 +1,4 @@ -#include "shaders/generate.hpp" +#include "v3_1p/shaders/generate.hpp" #include "common/utils.hpp" #include "core/commandbuffer.hpp" #include "core/image.hpp" diff --git a/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp b/framegen/v3.1p_src/shaders/mipmaps.cpp similarity index 98% rename from lsfg-vk-v3.1p/src/shaders/mipmaps.cpp rename to framegen/v3.1p_src/shaders/mipmaps.cpp index e47af8a..ebf9503 100644 --- a/lsfg-vk-v3.1p/src/shaders/mipmaps.cpp +++ b/framegen/v3.1p_src/shaders/mipmaps.cpp @@ -1,4 +1,4 @@ -#include "shaders/mipmaps.hpp" +#include "v3_1p/shaders/mipmaps.hpp" #include "common/utils.hpp" #include "core/image.hpp" #include "core/commandbuffer.hpp" diff --git a/lsfg-vk-v3.1/CMakeLists.txt b/lsfg-vk-v3.1/CMakeLists.txt deleted file mode 100644 index cbc4abc..0000000 --- a/lsfg-vk-v3.1/CMakeLists.txt +++ /dev/null @@ -1,60 +0,0 @@ -cmake_minimum_required(VERSION 3.28) - -# project -project(lsfg-vk-v3.1 - DESCRIPTION "Lossless Scaling Frame Generation v3.1" - LANGUAGES CXX) - -file(GLOB SOURCES - "src/core/*.cpp" - "src/pool/*.cpp" - "src/shaders/*.cpp" - "src/utils/*.cpp" - "src/*.cpp" -) - -add_library(lsfg-vk-v3.1 STATIC ${SOURCES}) - -# target -set_target_properties(lsfg-vk-v3.1 PROPERTIES - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED ON) -target_include_directories(lsfg-vk-v3.1 - PRIVATE include - PUBLIC public) -target_link_libraries(lsfg-vk-v3.1 - PUBLIC lsfg-vk-common vulkan) - -if(CMAKE_BUILD_TYPE STREQUAL "Release") - set_target_properties(lsfg-vk-v3.1 PROPERTIES - INTERPROCEDURAL_OPTIMIZATION ON) -endif() - -# diagnostics -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set_target_properties(lsfg-vk-v3.1 PROPERTIES - EXPORT_COMPILE_COMMANDS ON) -endif() - -if(LSFGVK_EXCESS_DEBUG) - target_compile_options(lsfg-vk-v3.1 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-v3.1 PROPERTIES - CXX_CLANG_TIDY clang-tidy) -endif() diff --git a/lsfg-vk-v3.1/README.md b/lsfg-vk-v3.1/README.md deleted file mode 100644 index 238e37b..0000000 --- a/lsfg-vk-v3.1/README.md +++ /dev/null @@ -1,14 +0,0 @@ -## lsfg-vk-v3.1 -Version 3.1 of Lossless Scaling Frame Generation - -This is a subproject of lsfg-vk and contains the external 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-v3.1 is done via `lsfg.hpp` header. The internal Vulkan instance is created using `LSFG_3_1::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_3_1::finalize()` after which `LSFG_3_1::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_3_1::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_3_1::deleteContext()`. - -Presenting the context can be done via `LSFG_3_1::presentContext()`. Before calling the function a second time, make sure the outgoing semaphores have been signaled. diff --git a/lsfg-vk-v3.1p/.clang-tidy b/lsfg-vk-v3.1p/.clang-tidy deleted file mode 100644 index cf513e9..0000000 --- a/lsfg-vk-v3.1p/.clang-tidy +++ /dev/null @@ -1,26 +0,0 @@ -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" -- "-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" diff --git a/lsfg-vk-v3.1p/.gitattributes b/lsfg-vk-v3.1p/.gitattributes deleted file mode 100644 index 8d476d4..0000000 --- a/lsfg-vk-v3.1p/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -*.cpp diff=cpp eol=lf -*.hpp diff=cpp eol=lf -*.md diff=markdown eol=lf diff --git a/lsfg-vk-v3.1p/.gitignore b/lsfg-vk-v3.1p/.gitignore deleted file mode 100644 index 43ab8ae..0000000 --- a/lsfg-vk-v3.1p/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# cmake files -/build - -# ide/lsp files -/.zed -/.vscode -/.clangd -/.cache -/.ccls diff --git a/lsfg-vk-v3.1p/CMakeLists.txt b/lsfg-vk-v3.1p/CMakeLists.txt deleted file mode 100644 index 94fa4c8..0000000 --- a/lsfg-vk-v3.1p/CMakeLists.txt +++ /dev/null @@ -1,60 +0,0 @@ -cmake_minimum_required(VERSION 3.28) - -# project -project(lsfg-vk-v3.1p - DESCRIPTION "Lossless Scaling Frame Generation v3.1 (Performance Mode)" - LANGUAGES CXX) - -file(GLOB SOURCES - "src/core/*.cpp" - "src/pool/*.cpp" - "src/shaders/*.cpp" - "src/utils/*.cpp" - "src/*.cpp" -) - -add_library(lsfg-vk-v3.1p STATIC ${SOURCES}) - -# target -set_target_properties(lsfg-vk-v3.1p PROPERTIES - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED ON) -target_include_directories(lsfg-vk-v3.1p - PRIVATE include - PUBLIC public) -target_link_libraries(lsfg-vk-v3.1p - PUBLIC lsfg-vk-common vulkan) - -if(CMAKE_BUILD_TYPE STREQUAL "Release") - set_target_properties(lsfg-vk-v3.1p PROPERTIES - INTERPROCEDURAL_OPTIMIZATION ON) -endif() - -# diagnostics -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set_target_properties(lsfg-vk-v3.1p PROPERTIES - EXPORT_COMPILE_COMMANDS ON) -endif() - -if(LSFGVK_EXCESS_DEBUG) - target_compile_options(lsfg-vk-v3.1p 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-v3.1p PROPERTIES - CXX_CLANG_TIDY clang-tidy) -endif() diff --git a/lsfg-vk-v3.1p/LICENSE.md b/lsfg-vk-v3.1p/LICENSE.md deleted file mode 100644 index b5c8a3e..0000000 --- a/lsfg-vk-v3.1p/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -## MIT License - -Copyright (c) 2025 lsfg-vk - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/lsfg-vk-v3.1p/README.md b/lsfg-vk-v3.1p/README.md deleted file mode 100644 index f99ba40..0000000 --- a/lsfg-vk-v3.1p/README.md +++ /dev/null @@ -1,14 +0,0 @@ -## lsfg-vk-v3.1p -Version 3.1 (Performance Mode) of Lossless Scaling Frame Generation - -This is a subproject of lsfg-vk and contains the external 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-v3.1p is done via `lsfg.hpp` header. The internal Vulkan instance is created using `LSFG_3_1P::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_3_1P::finalize()` after which `LSFG_3_1P::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_3_1P::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_3_1P::deleteContext()`. - -Presenting the context can be done via `LSFG_3_1P::presentContext()`. Before calling the function a second time, make sure the outgoing semaphores have been signaled. From 455e114aef571e204cb61af5649d82046d58d39c Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 16 Jul 2025 17:47:55 +0200 Subject: [PATCH 146/253] only run on develop branch --- .github/workflows/build-arch.yml | 5 ++++- .github/workflows/build-debian.yml | 4 +++- .github/workflows/build-fedora.yml | 5 ++++- .github/workflows/build-ubuntu.yml | 5 ++++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-arch.yml b/.github/workflows/build-arch.yml index 2a6acbd..4c92d1e 100644 --- a/.github/workflows/build-arch.yml +++ b/.github/workflows/build-arch.yml @@ -1,5 +1,8 @@ name: Build lsfg-vk on Arch Linux -on: [push] +on: + push: + branches: ["develop"] + jobs: build-arch: diff --git a/.github/workflows/build-debian.yml b/.github/workflows/build-debian.yml index b5a5a47..ff36867 100644 --- a/.github/workflows/build-debian.yml +++ b/.github/workflows/build-debian.yml @@ -1,5 +1,7 @@ name: Build lsfg-vk on Debian -on: [push] +on: + push: + branches: ["develop"] jobs: build-debian: diff --git a/.github/workflows/build-fedora.yml b/.github/workflows/build-fedora.yml index 0ff2800..a021f18 100644 --- a/.github/workflows/build-fedora.yml +++ b/.github/workflows/build-fedora.yml @@ -1,5 +1,8 @@ name: Build lsfg-vk on Fedora -on: [push] +on: + push: + branches: ["develop"] + jobs: build-fedora: diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 7a5f8ec..b62b4d7 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -1,5 +1,8 @@ name: Build lsfg-vk on Ubuntu -on: [push] +on: + push: + branches: ["develop"] + jobs: build-ubuntu: From a5ea1893abeed3920db47553e2bc159af413fa87 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 16 Jul 2025 20:32:23 +0200 Subject: [PATCH 147/253] initial config file --- .gitmodules | 3 + CMakeLists.txt | 7 +- framegen/include/common/exception.hpp | 28 ++++++++ framegen/src/common/exception.cpp | 8 +++ include/config/config.hpp | 2 +- src/config/config.cpp | 92 +++++++++++++++++++++++++-- src/main.cpp | 19 +++--- thirdparty/toml11 | 1 + 8 files changed, 145 insertions(+), 15 deletions(-) create mode 160000 thirdparty/toml11 diff --git a/.gitmodules b/.gitmodules index a01239c..d5ff308 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0eb28de..38cbcd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,8 +16,10 @@ add_compile_options(-fPIC 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(framegen) + # main project project(lsfg-vk VERSION 0.0.1 @@ -41,7 +43,10 @@ set_target_properties(lsfg-vk PROPERTIES target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk PRIVATE - lsfg-vk-framegen pe-parse dxbc vulkan) + lsfg-vk-framegen pe-parse dxbc toml11 vulkan) + +get_target_property(TOML11_INCLUDE_DIRS toml11 INTERFACE_INCLUDE_DIRECTORIES) +target_include_directories(lsfg-vk SYSTEM PRIVATE ${TOML11_INCLUDE_DIRS}) if(CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(lsfg-vk PROPERTIES diff --git a/framegen/include/common/exception.hpp b/framegen/include/common/exception.hpp index 3808ca8..a0e6a35 100644 --- a/framegen/include/common/exception.hpp +++ b/framegen/include/common/exception.hpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -31,4 +32,31 @@ namespace LSFG { VkResult result; }; + /// Simple exception class for stacking errors. + class rethrowable_error : public std::runtime_error { + public: + /// + /// Construct a new rethrowable_error with a message. + /// + /// @param message The error message. + /// @param exe The original exception to rethrow. + /// + explicit rethrowable_error(const std::string& message, + const std::exception& exe); + + /// Get the exception as a string. + [[nodiscard]] const char* what() const noexcept override { + return message.c_str(); + } + + // Trivially copyable, moveable and destructible + rethrowable_error(const rethrowable_error&) = default; + rethrowable_error(rethrowable_error&&) = default; + rethrowable_error& operator=(const rethrowable_error&) = default; + rethrowable_error& operator=(rethrowable_error&&) = default; + ~rethrowable_error() noexcept override; + private: + std::string message; + }; + } diff --git a/framegen/src/common/exception.cpp b/framegen/src/common/exception.cpp index f675833..5ea7b0d 100644 --- a/framegen/src/common/exception.cpp +++ b/framegen/src/common/exception.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -14,3 +15,10 @@ vulkan_error::vulkan_error(VkResult result, const std::string& message) result(result) {} vulkan_error::~vulkan_error() noexcept = default; + +rethrowable_error::rethrowable_error(const std::string& message, const std::exception& exe) + : std::runtime_error(message) { + this->message = std::format("{}\n- {}", message, exe.what()); +} + +rethrowable_error::~rethrowable_error() noexcept = default; diff --git a/include/config/config.hpp b/include/config/config.hpp index 733330b..fd63222 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -8,7 +8,7 @@ namespace Config { - /// lsfg-vk configuration. + /// lsfg-vk configuration struct Configuration { /// Whether lsfg-vk should be loaded in the first place. bool enable{false}; diff --git a/src/config/config.cpp b/src/config/config.cpp index c6b856f..e026c7a 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -1,15 +1,99 @@ #include "config/config.hpp" +#include "common/exception.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include using namespace Config; -const Configuration defaultConf{ - .enable = false -}; +namespace { + Configuration globalConf{}; + std::optional> gameConfs; +} bool Config::loadAndWatchConfig(const std::string& file) { + // parse config file + toml::value toml{}; + if (std::filesystem::exists(file)) { + try { + toml = toml::parse(file); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Unable to parse configuration file", e); + } + } + + // parse global configuration + auto& global = globalConf; + const toml::value globalTable = toml::find_or_default(toml, "global"); + global.enable = toml::find_or(globalTable, "enable", false); + global.dll = toml::find_or(globalTable, "dll", std::string()); + global.multiplier = toml::find_or(globalTable, "multiplier", size_t(2)); + global.flowScale = toml::find_or(globalTable, "flow_scale", 1.0F); + global.performance = toml::find_or(globalTable, "performance_mode", false); + global.hdr = toml::find_or(globalTable, "hdr_mode", false); + global.valid = std::make_shared(true); + + // validate global configuration + if (global.multiplier < 2) + throw std::runtime_error("Multiplier cannot be less than 2"); + if (global.flowScale < 0.25F || global.flowScale > 1.0F) + throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); + + // parse game-specific configuration + auto& games = gameConfs.emplace(); + const toml::value gamesList = toml::find_or_default(toml, "game"); + for (const auto& gameTable : gamesList.as_array()) { + if (!gameTable.is_table()) + throw std::runtime_error("Invalid game configuration entry"); + if (!gameTable.contains("exe")) + throw std::runtime_error("Game override missing 'exe' field"); + + const std::string exe = toml::find(gameTable, "exe"); + Configuration game{ + .enable = toml::find_or(gameTable, "enable", global.enable), + .dll = toml::find_or(gameTable, "dll", global.dll), + .multiplier = toml::find_or(gameTable, "multiplier", global.multiplier), + .flowScale = toml::find_or(gameTable, "flow_scale", global.flowScale), + .performance = toml::find_or(gameTable, "performance_mode", global.performance), + .hdr = toml::find_or(gameTable, "hdr_mode", global.hdr), + .valid = global.valid // only need a single validity flag + }; + + // validate the configuration + if (game.multiplier < 2) + throw std::runtime_error("Multiplier cannot be less than 2"); + if (game.flowScale < 0.25F || game.flowScale > 1.0F) + throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); + games[exe] = std::move(game); + } + + // prepare config watcher + // (TODO) + return false; } Configuration Config::getConfig(std::string_view name) { - return defaultConf; + if (name.empty() || !gameConfs.has_value()) + return globalConf; + + const auto& games = *gameConfs; + auto it = games.find(std::string(name)); + if (it != games.end()) + return it->second; + + return globalConf; } diff --git a/src/main.cpp b/src/main.cpp index d35d624..26afa20 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,8 +46,8 @@ namespace { return{configFile}; const char* homePath = std::getenv("HOME"); if (homePath && *homePath != '\0') - return std::string(homePath) + "/.config/lsfg-vk.conf"; - return "/etc/lsfg-vk.conf"; + return std::string(homePath) + "/.config/lsfg-vk.toml"; + return "/etc/lsfg-vk.toml"; } __attribute__((constructor)) void lsfgvk_init() { @@ -57,6 +57,7 @@ namespace { exit(0); } + // TODO: health check, maybe? std::cerr << "lsfg-vk: This library is not meant to be preloaded, unless you are running a benchmark.\n"; exit(1); } @@ -66,8 +67,8 @@ namespace { try { Config::loadAndWatchConfig(file); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: Unable to read configuration file, exiting." << '\n'; - std::cerr << e.what() << '\n'; + std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, exiting:" << '\n'; + std::cerr << "- " << e.what() << '\n'; exit(0); } @@ -87,10 +88,10 @@ namespace { // print config std::cerr << "lsfg-vk: Loaded configuration for " << name << ":\n"; - std::cerr << "lsfg-vk: Using DLL from: " << conf.dll << '\n'; - std::cerr << "lsfg-vk: Multiplier: " << conf.multiplier << '\n'; - std::cerr << "lsfg-vk: Flow Scale: " << conf.flowScale << '\n'; - std::cerr << "lsfg-vk: Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; - std::cerr << "lsfg-vk: HDR: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; + if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n'; + std::cerr << " Multiplier: " << conf.multiplier << '\n'; + std::cerr << " Flow Scale: " << conf.flowScale << '\n'; + std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; + std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; } } diff --git a/thirdparty/toml11 b/thirdparty/toml11 new file mode 160000 index 0000000..be08ba2 --- /dev/null +++ b/thirdparty/toml11 @@ -0,0 +1 @@ +Subproject commit be08ba2be2a964edcdb3d3e3ea8d100abc26f286 From 7a460c7faf71d4fbc136759f1735b816c7e5b8be Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 16 Jul 2025 21:16:03 +0200 Subject: [PATCH 148/253] watch config file --- src/config/config.cpp | 90 +++++++++++++++++++++++++++++++++++++------ src/main.cpp | 4 +- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/config/config.cpp b/src/config/config.cpp index e026c7a..8fab2cc 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -1,21 +1,30 @@ #include "config/config.hpp" #include "common/exception.hpp" -#include -#include +#include +#include #include #include #include +#include #include #include +#include #include #include +#include +#include #include #include +#include +#include +#include +#include #include #include #include +#include using namespace Config; @@ -25,15 +34,17 @@ namespace { } bool Config::loadAndWatchConfig(const std::string& file) { + if (!std::filesystem::exists(file)) + return false; + // parse config file - toml::value toml{}; - if (std::filesystem::exists(file)) { - try { - toml = toml::parse(file); - } catch (const std::exception& e) { - throw LSFG::rethrowable_error("Unable to parse configuration file", e); - } + std::optional parsed; + try { + parsed.emplace(toml::parse(file)); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Unable to parse configuration file", e); } + auto& toml = *parsed; // parse global configuration auto& global = globalConf; @@ -81,9 +92,66 @@ bool Config::loadAndWatchConfig(const std::string& file) { } // prepare config watcher - // (TODO) + std::thread([file = file, valid = global.valid]() { + try { + const int fd = inotify_init(); + if (fd < 0) + throw std::runtime_error("Failed to initialize inotify\n" + "- " + std::string(strerror(errno))); - return false; + const int wd = inotify_add_watch(fd, file.c_str(), IN_MODIFY | IN_CLOSE_WRITE); + if (wd < 0) { + close(fd); + + throw std::runtime_error("Failed to add inotify watch for " + file + "\n" + "- " + std::string(strerror(errno))); + } + + // watch for changes + std::optional discard_until; + + std::array buffer{}; + while (true) { + const ssize_t len = read(fd, buffer.data(), buffer.size()); + if (len <= 0 && errno != EINTR) { + inotify_rm_watch(fd, wd); + close(fd); + + throw std::runtime_error("Error reading inotify event\n" + "- " + std::string(strerror(errno))); + } + + size_t i{}; + while (std::cmp_less(i, len)) { + auto* event = reinterpret_cast(&buffer.at(i)); + i += sizeof(inotify_event) + event->len; + if (event->len <= 0) + continue; + + // stall a bit, then mark as invalid + discard_until.emplace(std::chrono::steady_clock::now() + + std::chrono::milliseconds(500)); + } + + auto now = std::chrono::steady_clock::now(); + if (discard_until.has_value() && now > *discard_until) { + discard_until.reset(); + + // mark config as invalid + valid->store(false, std::memory_order_release); + + // and wait until it has been marked as valid again + while (!valid->load(std::memory_order_acquire)) + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + } + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: Error in config watcher thread:\n"; + std::cerr << "- " << e.what() << '\n'; + } + }).detach(); + + return true; } Configuration Config::getConfig(std::string_view name) { diff --git a/src/main.cpp b/src/main.cpp index 26afa20..39d54ca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,7 +67,7 @@ namespace { try { Config::loadAndWatchConfig(file); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, exiting:" << '\n'; + std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, exiting:\n"; std::cerr << "- " << e.what() << '\n'; exit(0); } @@ -77,7 +77,7 @@ namespace { try { conf = Config::getConfig(name); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: The configuration for " << name << " is invalid, exiting." << '\n'; + std::cerr << "lsfg-vk: The configuration for " << name << " is invalid, exiting.\n"; std::cerr << e.what() << '\n'; exit(0); } From cfdf4e1d1bdbb4500b0888dd16715433f3c252ae Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 16 Jul 2025 22:14:13 +0200 Subject: [PATCH 149/253] dynamically unload layer and remove excess logging --- VkLayer_LS_frame_generation.json | 3 - include/config/config.hpp | 3 + src/config/config.cpp | 2 + src/layer.cpp | 1000 ++++++++++++------------------ src/main.cpp | 4 +- 5 files changed, 418 insertions(+), 594 deletions(-) diff --git a/VkLayer_LS_frame_generation.json b/VkLayer_LS_frame_generation.json index ece2a5f..8ad2906 100644 --- a/VkLayer_LS_frame_generation.json +++ b/VkLayer_LS_frame_generation.json @@ -11,9 +11,6 @@ "vkGetInstanceProcAddr": "layer_vkGetInstanceProcAddr", "vkGetDeviceProcAddr": "layer_vkGetDeviceProcAddr" }, - "enable_environment": { - "ENABLE_LSFG": "1" - }, "disable_environment": { "DISABLE_LSFG": "1" } diff --git a/include/config/config.hpp b/include/config/config.hpp index fd63222..69a0d41 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -28,6 +28,9 @@ namespace Config { std::shared_ptr valid; }; + /// Active configuration. Must be set in main.cpp. + extern Configuration activeConf; + /// /// Load the config file and create a file watcher. /// diff --git a/src/config/config.cpp b/src/config/config.cpp index 8fab2cc..d028ee8 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -33,6 +33,8 @@ namespace { std::optional> gameConfs; } +Configuration Config::activeConf{}; + bool Config::loadAndWatchConfig(const std::string& file) { if (!std::filesystem::exists(file)) return false; diff --git a/src/layer.cpp b/src/layer.cpp index bc8bb9d..d19dabb 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -1,12 +1,16 @@ #include "layer.hpp" +#include "common/exception.hpp" +#include "config/config.hpp" #include "hooks.hpp" -#include "utils/log.hpp" #include #include -#include #include +#include +#include +#include +#include namespace { PFN_vkCreateInstance next_vkCreateInstance{}; @@ -55,7 +59,7 @@ namespace { bool initInstanceFunc(VkInstance instance, const char* name, T* func) { *func = reinterpret_cast(next_vkGetInstanceProcAddr(instance, name)); if (!*func) { - Log::error("layer", "Failed to get instance function pointer for {}", name); + std::cerr << "(no function pointer for " << name << ")\n"; return false; } return true; @@ -65,205 +69,201 @@ namespace { bool initDeviceFunc(VkDevice device, const char* name, T* func) { *func = reinterpret_cast(next_vkGetDeviceProcAddr(device, name)); if (!*func) { - Log::error("layer", "Failed to get device function pointer for {}", name); + std::cerr << "(no function pointer for " << name << ")\n"; return false; } return true; } } - namespace { - VkInstance gInstance; - VkResult layer_vkCreateInstance( // NOLINTBEGIN + VkResult layer_vkCreateInstance( const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { - Log::debug("layer", "Initializing lsfg-vk instance layer..."); + try { + // prepare layer | NOLINTBEGIN + auto* layerDesc = const_cast( + reinterpret_cast(pCreateInfo->pNext)); + while (layerDesc && (layerDesc->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO + || layerDesc->function != VK_LAYER_LINK_INFO)) { + layerDesc = const_cast( + reinterpret_cast(layerDesc->pNext)); + } + if (!layerDesc) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "No layer creation info found in pNext chain"); - // find layer creation info - auto* layerDesc = const_cast( - reinterpret_cast(pCreateInfo->pNext)); - while (layerDesc && (layerDesc->sType != VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO - || layerDesc->function != VK_LAYER_LINK_INFO)) { - layerDesc = const_cast( - reinterpret_cast(layerDesc->pNext)); - } - if (!layerDesc) { - Log::error("layer", "No layer creation info found in pNext chain"); + next_vkGetInstanceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetInstanceProcAddr; + layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; + + bool success = initInstanceFunc(nullptr, "vkCreateInstance", &next_vkCreateInstance); + if (!success) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "Failed to get instance function pointer for vkCreateInstance"); + success &= initInstanceFunc(*pInstance, + "vkCreateDevice", &next_vkCreateDevice); + if (!success) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "Failed to get instance function pointer for vkCreateDevice"); + + // NOLINTEND | skip initialization if the layer is disabled + if (!Config::activeConf.enable) + return next_vkCreateInstance(pCreateInfo, pAllocator, pInstance); + + // create instance + try { + auto* createInstanceHook = reinterpret_cast( + Hooks::hooks["vkCreateInstance"]); + auto res = createInstanceHook(pCreateInfo, pAllocator, pInstance); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unknown error"); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Failed to create Vulkan instance", e); + } + + // get relevant function pointers from the next layer + success = true; + success &= initInstanceFunc(*pInstance, + "vkDestroyInstance", &next_vkDestroyInstance); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceQueueFamilyProperties", &next_vkGetPhysicalDeviceQueueFamilyProperties); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceMemoryProperties", &next_vkGetPhysicalDeviceMemoryProperties); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceProperties", &next_vkGetPhysicalDeviceProperties); + success &= initInstanceFunc(*pInstance, + "vkGetPhysicalDeviceSurfaceCapabilitiesKHR", &next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR); + if (!success) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "Failed to get instance function pointers"); + + std::cerr << "lsfg-vk: Vulkan instance layer initialized successfully.\n"; + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred while initializing the Vulkan instance layer:\n"; + std::cerr << "- " << e.what() << '\n'; return VK_ERROR_INITIALIZATION_FAILED; } - - // advance link info (i don't really know what this does) - next_vkGetInstanceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetInstanceProcAddr; - Log::debug("layer", "Next instance proc addr: {:x}", - reinterpret_cast(next_vkGetInstanceProcAddr)); - - layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; - - // create instance - auto success = initInstanceFunc(nullptr, "vkCreateInstance", &next_vkCreateInstance); - if (!success) return VK_ERROR_INITIALIZATION_FAILED; - - auto* layer_vkCreateInstance2 = reinterpret_cast( - Hooks::hooks["vkCreateInstance"]); - auto res = layer_vkCreateInstance2(pCreateInfo, pAllocator, pInstance); - if (res != VK_SUCCESS) { - Log::error("layer", "Failed to create Vulkan instance: {:x}", - static_cast(res)); - return res; - } - - // get relevant function pointers from the next layer - success = true; - success &= initInstanceFunc(*pInstance, "vkDestroyInstance", &next_vkDestroyInstance); - success &= initInstanceFunc(*pInstance, - "vkGetPhysicalDeviceQueueFamilyProperties", &next_vkGetPhysicalDeviceQueueFamilyProperties); - success &= initInstanceFunc(*pInstance, - "vkGetPhysicalDeviceMemoryProperties", &next_vkGetPhysicalDeviceMemoryProperties); - success &= initInstanceFunc(*pInstance, - "vkGetPhysicalDeviceProperties", &next_vkGetPhysicalDeviceProperties); - success &= initInstanceFunc(*pInstance, - "vkGetPhysicalDeviceSurfaceCapabilitiesKHR", &next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR); - if (!success) { - Log::error("layer", "Failed to get instance function pointers"); - return VK_ERROR_INITIALIZATION_FAILED; - } - - gInstance = *pInstance; // workaround mesa bug - - Log::debug("layer", "Successfully initialized lsfg-vk instance layer"); - return res; - } // NOLINTEND + return VK_SUCCESS; + } VkResult layer_vkCreateDevice( // NOLINTBEGIN VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { - Log::debug("layer", "Initializing lsfg-vk device layer..."); + try { + // prepare layer | NOLINTBEGIN + auto* layerDesc = const_cast( + reinterpret_cast(pCreateInfo->pNext)); + while (layerDesc && (layerDesc->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO + || layerDesc->function != VK_LAYER_LINK_INFO)) { + layerDesc = const_cast( + reinterpret_cast(layerDesc->pNext)); + } + if (!layerDesc) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "No layer creation info found in pNext chain"); - // find layer creation info - auto* layerDesc = const_cast( - reinterpret_cast(pCreateInfo->pNext)); - while (layerDesc && (layerDesc->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO - || layerDesc->function != VK_LAYER_LINK_INFO)) { - layerDesc = const_cast( - reinterpret_cast(layerDesc->pNext)); - } - if (!layerDesc) { - Log::error("layer", "No layer creation info found in pNext chain"); + next_vkGetDeviceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetDeviceProcAddr; + layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; + + auto* layerDesc2 = const_cast( + reinterpret_cast(pCreateInfo->pNext)); + while (layerDesc2 && (layerDesc2->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO + || layerDesc2->function != VK_LOADER_DATA_CALLBACK)) { + layerDesc2 = const_cast( + reinterpret_cast(layerDesc2->pNext)); + } + if (!layerDesc2) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "No layer device loader data found in pNext chain"); + + next_vSetDeviceLoaderData = layerDesc2->u.pfnSetDeviceLoaderData; + + // NOLINTEND | skip initialization if the layer is disabled + if (!Config::activeConf.enable) + return next_vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); + + // create device + try { + auto* createDeviceHook = reinterpret_cast( + Hooks::hooks["vkCreateDevicePre"]); + auto res = createDeviceHook(physicalDevice, pCreateInfo, pAllocator, pDevice); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unknown error"); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Failed to create Vulkan device", e); + } + + // get relevant function pointers from the next layer + bool success = true; + success &= initDeviceFunc(*pDevice, "vkDestroyDevice", &next_vkDestroyDevice); + success &= initDeviceFunc(*pDevice, "vkCreateSwapchainKHR", &next_vkCreateSwapchainKHR); + success &= initDeviceFunc(*pDevice, "vkQueuePresentKHR", &next_vkQueuePresentKHR); + success &= initDeviceFunc(*pDevice, "vkDestroySwapchainKHR", &next_vkDestroySwapchainKHR); + success &= initDeviceFunc(*pDevice, "vkGetSwapchainImagesKHR", &next_vkGetSwapchainImagesKHR); + success &= initDeviceFunc(*pDevice, "vkAllocateCommandBuffers", &next_vkAllocateCommandBuffers); + success &= initDeviceFunc(*pDevice, "vkFreeCommandBuffers", &next_vkFreeCommandBuffers); + success &= initDeviceFunc(*pDevice, "vkBeginCommandBuffer", &next_vkBeginCommandBuffer); + success &= initDeviceFunc(*pDevice, "vkEndCommandBuffer", &next_vkEndCommandBuffer); + success &= initDeviceFunc(*pDevice, "vkCreateCommandPool", &next_vkCreateCommandPool); + success &= initDeviceFunc(*pDevice, "vkDestroyCommandPool", &next_vkDestroyCommandPool); + success &= initDeviceFunc(*pDevice, "vkCreateImage", &next_vkCreateImage); + success &= initDeviceFunc(*pDevice, "vkDestroyImage", &next_vkDestroyImage); + success &= initDeviceFunc(*pDevice, "vkGetImageMemoryRequirements", &next_vkGetImageMemoryRequirements); + success &= initDeviceFunc(*pDevice, "vkBindImageMemory", &next_vkBindImageMemory); + success &= initDeviceFunc(*pDevice, "vkGetMemoryFdKHR", &next_vkGetMemoryFdKHR); + success &= initDeviceFunc(*pDevice, "vkAllocateMemory", &next_vkAllocateMemory); + success &= initDeviceFunc(*pDevice, "vkFreeMemory", &next_vkFreeMemory); + success &= initDeviceFunc(*pDevice, "vkCreateSemaphore", &next_vkCreateSemaphore); + success &= initDeviceFunc(*pDevice, "vkDestroySemaphore", &next_vkDestroySemaphore); + success &= initDeviceFunc(*pDevice, "vkGetSemaphoreFdKHR", &next_vkGetSemaphoreFdKHR); + success &= initDeviceFunc(*pDevice, "vkGetDeviceQueue", &next_vkGetDeviceQueue); + success &= initDeviceFunc(*pDevice, "vkQueueSubmit", &next_vkQueueSubmit); + success &= initDeviceFunc(*pDevice, "vkCmdPipelineBarrier", &next_vkCmdPipelineBarrier); + success &= initDeviceFunc(*pDevice, "vkCmdBlitImage", &next_vkCmdBlitImage); + success &= initDeviceFunc(*pDevice, "vkAcquireNextImageKHR", &next_vkAcquireNextImageKHR); + if (!success) + throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, + "Failed to get device function pointers"); + + auto postCreateDeviceHook = reinterpret_cast( + Hooks::hooks["vkCreateDevicePost"]); + auto res = postCreateDeviceHook(physicalDevice, pCreateInfo, pAllocator, pDevice); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Unknown error"); + + std::cerr << "lsfg-vk: Vulkan device layer initialized successfully.\n"; + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred while initializing the Vulkan device layer:\n"; + std::cerr << "- " << e.what() << '\n'; return VK_ERROR_INITIALIZATION_FAILED; } - - next_vkGetDeviceProcAddr = layerDesc->u.pLayerInfo->pfnNextGetDeviceProcAddr; - Log::debug("layer", "Next device proc addr: {:x}", - reinterpret_cast(next_vkGetDeviceProcAddr)); - - // find second layer creation info - auto* layerDesc2 = const_cast( - reinterpret_cast(pCreateInfo->pNext)); - while (layerDesc2 && (layerDesc2->sType != VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO - || layerDesc2->function != VK_LOADER_DATA_CALLBACK)) { - layerDesc2 = const_cast( - reinterpret_cast(layerDesc2->pNext)); - } - if (!layerDesc2) { - Log::error("layer", "No layer creation info found in pNext chain"); - return VK_ERROR_INITIALIZATION_FAILED; - } - - next_vSetDeviceLoaderData = layerDesc2->u.pfnSetDeviceLoaderData; - Log::debug("layer", "Next device loader data: {:x}", - reinterpret_cast(next_vSetDeviceLoaderData)); - - // advance link info (i don't really know what this does) - layerDesc->u.pLayerInfo = layerDesc->u.pLayerInfo->pNext; - - // create device - auto success = initInstanceFunc(gInstance, "vkCreateDevice", &next_vkCreateDevice); - if (!success) return VK_ERROR_INITIALIZATION_FAILED; - - auto* layer_vkCreateDevice2 = reinterpret_cast( - Hooks::hooks["vkCreateDevicePre"]); - auto res = layer_vkCreateDevice2(physicalDevice, pCreateInfo, pAllocator, pDevice); - if (res != VK_SUCCESS) { - Log::error("layer", "Failed to create Vulkan device: {:x}", - static_cast(res)); - return res; - } - - // get relevant function pointers from the next layer - success = true; - success &= initDeviceFunc(*pDevice, "vkDestroyDevice", &next_vkDestroyDevice); - success &= initDeviceFunc(*pDevice, "vkCreateSwapchainKHR", &next_vkCreateSwapchainKHR); - success &= initDeviceFunc(*pDevice, "vkQueuePresentKHR", &next_vkQueuePresentKHR); - success &= initDeviceFunc(*pDevice, "vkDestroySwapchainKHR", &next_vkDestroySwapchainKHR); - success &= initDeviceFunc(*pDevice, "vkGetSwapchainImagesKHR", &next_vkGetSwapchainImagesKHR); - success &= initDeviceFunc(*pDevice, "vkAllocateCommandBuffers", &next_vkAllocateCommandBuffers); - success &= initDeviceFunc(*pDevice, "vkFreeCommandBuffers", &next_vkFreeCommandBuffers); - success &= initDeviceFunc(*pDevice, "vkBeginCommandBuffer", &next_vkBeginCommandBuffer); - success &= initDeviceFunc(*pDevice, "vkEndCommandBuffer", &next_vkEndCommandBuffer); - success &= initDeviceFunc(*pDevice, "vkCreateCommandPool", &next_vkCreateCommandPool); - success &= initDeviceFunc(*pDevice, "vkDestroyCommandPool", &next_vkDestroyCommandPool); - success &= initDeviceFunc(*pDevice, "vkCreateImage", &next_vkCreateImage); - success &= initDeviceFunc(*pDevice, "vkDestroyImage", &next_vkDestroyImage); - success &= initDeviceFunc(*pDevice, "vkGetImageMemoryRequirements", &next_vkGetImageMemoryRequirements); - success &= initDeviceFunc(*pDevice, "vkBindImageMemory", &next_vkBindImageMemory); - success &= initDeviceFunc(*pDevice, "vkGetMemoryFdKHR", &next_vkGetMemoryFdKHR); - success &= initDeviceFunc(*pDevice, "vkAllocateMemory", &next_vkAllocateMemory); - success &= initDeviceFunc(*pDevice, "vkFreeMemory", &next_vkFreeMemory); - success &= initDeviceFunc(*pDevice, "vkCreateSemaphore", &next_vkCreateSemaphore); - success &= initDeviceFunc(*pDevice, "vkDestroySemaphore", &next_vkDestroySemaphore); - success &= initDeviceFunc(*pDevice, "vkGetSemaphoreFdKHR", &next_vkGetSemaphoreFdKHR); - success &= initDeviceFunc(*pDevice, "vkGetDeviceQueue", &next_vkGetDeviceQueue); - success &= initDeviceFunc(*pDevice, "vkQueueSubmit", &next_vkQueueSubmit); - success &= initDeviceFunc(*pDevice, "vkCmdPipelineBarrier", &next_vkCmdPipelineBarrier); - success &= initDeviceFunc(*pDevice, "vkCmdBlitImage", &next_vkCmdBlitImage); - success &= initDeviceFunc(*pDevice, "vkAcquireNextImageKHR", &next_vkAcquireNextImageKHR); - if (!success) { - Log::error("layer", "Failed to get device function pointers"); - return VK_ERROR_INITIALIZATION_FAILED; - } - - layer_vkCreateDevice2 = reinterpret_cast( - Hooks::hooks["vkCreateDevicePost"]); - res = layer_vkCreateDevice2(physicalDevice, pCreateInfo, pAllocator, pDevice); - if (res != VK_SUCCESS) { - Log::error("layer", "Failed to create Vulkan device: {:x}", - static_cast(res)); - return res; - } - - Log::debug("layer", "Successfully initialized lsfg-vk device layer"); - return res; + return VK_SUCCESS; } // NOLINTEND } const std::unordered_map layerFunctions = { { "vkCreateInstance", reinterpret_cast(&layer_vkCreateInstance) }, + { "vkCreateDevice", + reinterpret_cast(&layer_vkCreateDevice) }, { "vkGetInstanceProcAddr", reinterpret_cast(&layer_vkGetInstanceProcAddr) }, { "vkGetDeviceProcAddr", reinterpret_cast(&layer_vkGetDeviceProcAddr) }, - { "vkCreateDevice", - reinterpret_cast(&layer_vkCreateDevice) }, }; PFN_vkVoidFunction layer_vkGetInstanceProcAddr(VkInstance instance, const char* pName) { const std::string name(pName); auto it = layerFunctions.find(name); - if (it != layerFunctions.end()) { - Log::debug("layer", "Inserted layer function for {}", name); + if (it != layerFunctions.end()) return it->second; - } it = Hooks::hooks.find(name); - if (it != Hooks::hooks.end()) { - Log::debug("layer", "Inserted hook function for {}", name); + if (it != Hooks::hooks.end() && Config::activeConf.enable) return it->second; - } return next_vkGetInstanceProcAddr(instance, pName); } @@ -271,443 +271,265 @@ PFN_vkVoidFunction layer_vkGetInstanceProcAddr(VkInstance instance, const char* PFN_vkVoidFunction layer_vkGetDeviceProcAddr(VkDevice device, const char* pName) { const std::string name(pName); auto it = layerFunctions.find(name); - if (it != layerFunctions.end()) { - Log::debug("layer", "Inserted layer function for {}", name); + if (it != layerFunctions.end()) return it->second; - } it = Hooks::hooks.find(name); - if (it != Hooks::hooks.end()) { - Log::debug("layer", "Inserted hook function for {}", name); + if (it != Hooks::hooks.end() && Config::activeConf.enable) return it->second; - } return next_vkGetDeviceProcAddr(device, pName); } // original functions - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" -// NOLINTBEGIN - -VkResult Layer::ovkCreateInstance( - const VkInstanceCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkInstance* pInstance) { - Log::debug("vulkan", "vkCreateInstance called with {} extensions:", - pCreateInfo->enabledExtensionCount); - for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; ++i) - Log::debug("vulkan", " - {}", pCreateInfo->ppEnabledExtensionNames[i]); - auto res = next_vkCreateInstance(pCreateInfo, pAllocator, pInstance); - Log::debug("vulkan", "vkCreateInstance({}) returned handle {:x}", - static_cast(res), - reinterpret_cast(*pInstance)); - return res; -} -void Layer::ovkDestroyInstance( - VkInstance instance, - const VkAllocationCallbacks* pAllocator) { - Log::debug("vulkan", "vkDestroyInstance called for instance {:x}", - reinterpret_cast(instance)); - next_vkDestroyInstance(instance, pAllocator); -} - -VkResult Layer::ovkCreateDevice( - VkPhysicalDevice physicalDevice, - const VkDeviceCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkDevice* pDevice) { - Log::debug("vulkan", "vkCreateDevice called with {} extensions:", - pCreateInfo->enabledExtensionCount); - for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; ++i) - Log::debug("vulkan", " - {}", pCreateInfo->ppEnabledExtensionNames[i]); - auto res = next_vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); - Log::debug("vulkan", "vkCreateDevice({}) returned handle {:x}", - static_cast(res), - reinterpret_cast(*pDevice)); - return res; -} -void Layer::ovkDestroyDevice( - VkDevice device, - const VkAllocationCallbacks* pAllocator) { - Log::debug("vulkan", "vkDestroyDevice called for device {:x}", - reinterpret_cast(device)); - next_vkDestroyDevice(device, pAllocator); - Log::debug("vulkan", "Device {:x} destroyed successfully", - reinterpret_cast(device)); -} - -VkResult Layer::ovkSetDeviceLoaderData(VkDevice device, void* object) { - Log::debug("vulkan", "vkSetDeviceLoaderData called for object {:x}", - reinterpret_cast(object)); - return next_vSetDeviceLoaderData(device, object); -} - -PFN_vkVoidFunction Layer::ovkGetInstanceProcAddr( - VkInstance instance, - const char* pName) { - return next_vkGetInstanceProcAddr(instance, pName); -} -PFN_vkVoidFunction Layer::ovkGetDeviceProcAddr( - VkDevice device, - const char* pName) { - return next_vkGetDeviceProcAddr(device, pName); -} - -void Layer::ovkGetPhysicalDeviceQueueFamilyProperties( - VkPhysicalDevice physicalDevice, - uint32_t* pQueueFamilyPropertyCount, - VkQueueFamilyProperties* pQueueFamilyProperties) { - Log::debug("vulkan", "vkGetPhysicalDeviceQueueFamilyProperties called for physical device {:x}", - reinterpret_cast(physicalDevice)); - next_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties); -} -void Layer::ovkGetPhysicalDeviceMemoryProperties( - VkPhysicalDevice physicalDevice, - VkPhysicalDeviceMemoryProperties* pMemoryProperties) { - Log::debug("vulkan", "vkGetPhysicalDeviceMemoryProperties called for physical device {:x}", - reinterpret_cast(physicalDevice)); - next_vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties); -} -void Layer::ovkGetPhysicalDeviceProperties( - VkPhysicalDevice physicalDevice, - VkPhysicalDeviceProperties* pProperties) { - Log::debug("vulkan", "vkGetPhysicalDeviceProperties called for physical device {:x}", - reinterpret_cast(physicalDevice)); - next_vkGetPhysicalDeviceProperties(physicalDevice, pProperties); -} -VkResult Layer::ovkGetPhysicalDeviceSurfaceCapabilitiesKHR( - VkPhysicalDevice physicalDevice, - VkSurfaceKHR surface, - VkSurfaceCapabilitiesKHR* pSurfaceCapabilities) { - Log::debug("vulkan", "vkGetPhysicalDeviceSurfaceCapabilitiesKHR called for physical device {:x} and surface {:x}", - reinterpret_cast(physicalDevice), - reinterpret_cast(surface)); - return next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, pSurfaceCapabilities); -} - -VkResult Layer::ovkCreateSwapchainKHR( - VkDevice device, - const VkSwapchainCreateInfoKHR* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkSwapchainKHR* pSwapchain) { - Log::debug("vulkan", "vkCreateSwapchainKHR called with {} images, extent: {}x{}", - pCreateInfo->minImageCount, pCreateInfo->imageExtent.width, pCreateInfo->imageExtent.height); - auto res = next_vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); - Log::debug("vulkan", "vkCreateSwapchainKHR({}) returned handle {:x}", - static_cast(res), - reinterpret_cast(*pSwapchain)); - return res; -} -VkResult Layer::ovkQueuePresentKHR( - VkQueue queue, - const VkPresentInfoKHR* pPresentInfo) { - Log::debug("vulkan2", "vkQueuePresentKHR called with {} wait semaphores:", - pPresentInfo->waitSemaphoreCount); - for (uint32_t i = 0; i < pPresentInfo->waitSemaphoreCount; i++) - Log::debug("vulkan2", " - {:x}", reinterpret_cast(pPresentInfo->pWaitSemaphores[i])); - Log::debug("vulkan2", "and {} signal semaphores:", - pPresentInfo->swapchainCount); - for (uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) - Log::debug("vulkan2", " - {:x}", reinterpret_cast(pPresentInfo->pSwapchains[i])); - Log::debug("vulkan2", "and queue: {:x}, image: {}", - reinterpret_cast(queue), - *pPresentInfo->pImageIndices); - auto res = next_vkQueuePresentKHR(queue, pPresentInfo); - Log::debug("vulkan2", "vkQueuePresentKHR({}) returned", - static_cast(res)); - return res; -} -void Layer::ovkDestroySwapchainKHR( - VkDevice device, - VkSwapchainKHR swapchain, - const VkAllocationCallbacks* pAllocator) { - Log::debug("vulkan", "vkDestroySwapchainKHR called for swapchain {:x}", - reinterpret_cast(swapchain)); - next_vkDestroySwapchainKHR(device, swapchain, pAllocator); -} - -VkResult Layer::ovkGetSwapchainImagesKHR( - VkDevice device, - VkSwapchainKHR swapchain, - uint32_t* pSwapchainImageCount, - VkImage* pSwapchainImages) { - Log::debug("vulkan", "vkGetSwapchainImagesKHR called for swapchain {:x}", - reinterpret_cast(swapchain)); - auto res = next_vkGetSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, pSwapchainImages); - Log::debug("vulkan", "vkGetSwapchainImagesKHR({}) returned {} images", - static_cast(res), - *pSwapchainImageCount); - return res; -} - -VkResult Layer::ovkAllocateCommandBuffers( - VkDevice device, - const VkCommandBufferAllocateInfo* pAllocateInfo, - VkCommandBuffer* pCommandBuffers) { - Log::debug("vulkan2", "vkAllocateCommandBuffers called for command pool {:x}", - reinterpret_cast(pAllocateInfo->commandPool)); - auto res = next_vkAllocateCommandBuffers(device, pAllocateInfo, pCommandBuffers); - Log::debug("vulkan2", "vkAllocateCommandBuffers({}) returned command buffer: {}", - static_cast(res), - reinterpret_cast(*pCommandBuffers)); - return res; -} -void Layer::ovkFreeCommandBuffers( - VkDevice device, - VkCommandPool commandPool, - uint32_t commandBufferCount, - const VkCommandBuffer* pCommandBuffers) { - Log::debug("vulkan2", "vkFreeCommandBuffers called for command buffer: {:x}", - reinterpret_cast(*pCommandBuffers)); - next_vkFreeCommandBuffers(device, commandPool, commandBufferCount, pCommandBuffers); -} - -VkResult Layer::ovkBeginCommandBuffer( - VkCommandBuffer commandBuffer, - const VkCommandBufferBeginInfo* pBeginInfo) { - Log::debug("vulkan2", "vkBeginCommandBuffer called for command buffer {:x}", - reinterpret_cast(commandBuffer)); - return next_vkBeginCommandBuffer(commandBuffer, pBeginInfo); -} -VkResult Layer::ovkEndCommandBuffer( - VkCommandBuffer commandBuffer) { - Log::debug("vulkan2", "vkEndCommandBuffer called for command buffer {:x}", - reinterpret_cast(commandBuffer)); - return next_vkEndCommandBuffer(commandBuffer); -} - -VkResult Layer::ovkCreateCommandPool( - VkDevice device, - const VkCommandPoolCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkCommandPool* pCommandPool) { - Log::debug("vulkan", "vkCreateCommandPool called"); - auto res = next_vkCreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool); - Log::debug("vulkan", "vkCreateCommandPool({}) returned handle {:x}", - static_cast(res), - reinterpret_cast(*pCommandPool)); - return res; -} -void Layer::ovkDestroyCommandPool( - VkDevice device, - VkCommandPool commandPool, - const VkAllocationCallbacks* pAllocator) { - Log::debug("vulkan", "vkDestroyCommandPool called for command pool {:x}", - reinterpret_cast(commandPool)); - next_vkDestroyCommandPool(device, commandPool, pAllocator); -} - -VkResult Layer::ovkCreateImage( - VkDevice device, - const VkImageCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkImage* pImage) { - Log::debug("vulkan", "vkCreateImage called with format: {}, extent: {}x{}, usage: {}", - static_cast(pCreateInfo->format), - pCreateInfo->extent.width, pCreateInfo->extent.height, - static_cast(pCreateInfo->usage)); - auto res = next_vkCreateImage(device, pCreateInfo, pAllocator, pImage); - Log::debug("vulkan", "vkCreateImage({}) returned handle {:x}", - static_cast(res), - reinterpret_cast(*pImage)); - return res; -} -void Layer::ovkDestroyImage( - VkDevice device, - VkImage image, - const VkAllocationCallbacks* pAllocator) { - Log::debug("vulkan", "vkDestroyImage called for image {:x}", - reinterpret_cast(image)); - next_vkDestroyImage(device, image, pAllocator); -} - -void Layer::ovkGetImageMemoryRequirements( - VkDevice device, - VkImage image, - VkMemoryRequirements* pMemoryRequirements) { - Log::debug("vulkan", "vkGetImageMemoryRequirements called for image {:x}", - reinterpret_cast(image)); - next_vkGetImageMemoryRequirements(device, image, pMemoryRequirements); -} -VkResult Layer::ovkBindImageMemory( - VkDevice device, - VkImage image, - VkDeviceMemory memory, - VkDeviceSize memoryOffset) { - Log::debug("vulkan", "vkBindImageMemory called for image {:x}, memory {:x}, offset: {}", - reinterpret_cast(image), - reinterpret_cast(memory), - memoryOffset); - auto res = next_vkBindImageMemory(device, image, memory, memoryOffset); - Log::debug("vulkan", "vkBindImageMemory({}) returned", - static_cast(res)); - return res; -} - -VkResult Layer::ovkAllocateMemory( - VkDevice device, - const VkMemoryAllocateInfo* pAllocateInfo, - const VkAllocationCallbacks* pAllocator, - VkDeviceMemory* pMemory) { - Log::debug("vulkan", "vkAllocateMemory called with size: {}, memory type index: {}", - pAllocateInfo->allocationSize, - pAllocateInfo->memoryTypeIndex); - auto res = next_vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); - Log::debug("vulkan", "vkAllocateMemory({}) returned handle {:x}", - static_cast(res), - reinterpret_cast(*pMemory)); - return res; -} -void Layer::ovkFreeMemory( - VkDevice device, - VkDeviceMemory memory, - const VkAllocationCallbacks* pAllocator) { - Log::debug("vulkan", "vkFreeMemory called for memory {:x}", - reinterpret_cast(memory)); - next_vkFreeMemory(device, memory, pAllocator); -} - -VkResult Layer::ovkCreateSemaphore( - VkDevice device, - const VkSemaphoreCreateInfo* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkSemaphore* pSemaphore) { - Log::debug("vulkan2", "vkCreateSemaphore called", - static_cast(pCreateInfo->flags)); - auto res = next_vkCreateSemaphore(device, pCreateInfo, pAllocator, pSemaphore); - Log::debug("vulkan2", "vkCreateSemaphore({}) returned handle {:x}", - static_cast(res), - reinterpret_cast(*pSemaphore)); - return res; -} -void Layer::ovkDestroySemaphore( - VkDevice device, - VkSemaphore semaphore, - const VkAllocationCallbacks* pAllocator) { - Log::debug("vulkan2", "vkDestroySemaphore called for semaphore {:x}", - reinterpret_cast(semaphore)); - next_vkDestroySemaphore(device, semaphore, pAllocator); -} - -VkResult Layer::ovkGetMemoryFdKHR( - VkDevice device, - const VkMemoryGetFdInfoKHR* pGetFdInfo, - int* pFd) { - Log::debug("vulkan", "vkGetMemoryFdKHR called for memory {:x}, handle type: {}", - reinterpret_cast(pGetFdInfo->memory), - static_cast(pGetFdInfo->handleType)); - auto res = next_vkGetMemoryFdKHR(device, pGetFdInfo, pFd); - Log::debug("vulkan", "vkGetMemoryFdKHR({}) returned fd: {}", - static_cast(res), *pFd); - return res; -} -VkResult Layer::ovkGetSemaphoreFdKHR( - VkDevice device, - const VkSemaphoreGetFdInfoKHR* pGetFdInfo, - int* pFd) { - Log::debug("vulkan2", "vkGetSemaphoreFdKHR called for semaphore {:x}", - reinterpret_cast(pGetFdInfo->semaphore)); - auto res = next_vkGetSemaphoreFdKHR(device, pGetFdInfo, pFd); - Log::debug("vulkan2", "vkGetSemaphoreFdKHR({}) returned fd: {}", - static_cast(res), *pFd); - return res; -} - -void Layer::ovkGetDeviceQueue( - VkDevice device, - uint32_t queueFamilyIndex, - uint32_t queueIndex, - VkQueue* pQueue) { - Log::debug("vulkan", "vkGetDeviceQueue called for device {:x}, queue family index: {}, queue index: {}", - reinterpret_cast(device), - queueFamilyIndex, - queueIndex); - next_vkGetDeviceQueue(device, queueFamilyIndex, queueIndex, pQueue); -} -VkResult Layer::ovkQueueSubmit( - VkQueue queue, - uint32_t submitCount, - const VkSubmitInfo* pSubmits, - VkFence fence) { - Log::debug("vulkan2", "vkQueueSubmit called for queue {:x}, submitting: {} with wait semaphores:", - reinterpret_cast(queue), - reinterpret_cast(*pSubmits->pCommandBuffers)); - for (uint32_t i = 0; i < pSubmits->waitSemaphoreCount; ++i) - Log::debug("vulkan2", " - {:x}", reinterpret_cast(pSubmits->pWaitSemaphores[i])); - Log::debug("vulkan2", "and {} signal semaphores:", - pSubmits->waitSemaphoreCount); - for (uint32_t i = 0; i < submitCount; ++i) - Log::debug("vulkan2", " - {:x}", reinterpret_cast(pSubmits[i].pSignalSemaphores)); - Log::debug("vulkan2", "and fence: {:x}", - reinterpret_cast(fence)); - auto res = next_vkQueueSubmit(queue, submitCount, pSubmits, fence); - Log::debug("vulkan2", "vkQueueSubmit({}) returned", - static_cast(res)); - return res; -} - -void Layer::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) { - Log::debug("vulkan2", "vkCmdPipelineBarrier called for command buffer {:x}, src stage: {}, dst stage: {}, transitioning:", - reinterpret_cast(commandBuffer), - static_cast(srcStageMask), - static_cast(dstStageMask)); - for (uint32_t i = 0; i < imageMemoryBarrierCount; ++i) { - Log::debug("vulkan2", " - image {:x}, old layout: {}, new layout: {}", - reinterpret_cast(pImageMemoryBarriers[i].image), - static_cast(pImageMemoryBarriers[i].oldLayout), - static_cast(pImageMemoryBarriers[i].newLayout)); +namespace Layer { + VkResult ovkCreateInstance( + const VkInstanceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkInstance* pInstance) { + return next_vkCreateInstance(pCreateInfo, pAllocator, pInstance); + } + void ovkDestroyInstance( + VkInstance instance, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyInstance(instance, pAllocator); } - next_vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, dependencyFlags, - memoryBarrierCount, pMemoryBarriers, - bufferMemoryBarrierCount, pBufferMemoryBarriers, - imageMemoryBarrierCount, pImageMemoryBarriers); -} -void Layer::ovkCmdBlitImage( - VkCommandBuffer commandBuffer, - VkImage srcImage, - VkImageLayout srcImageLayout, - VkImage dstImage, - VkImageLayout dstImageLayout, - uint32_t regionCount, - const VkImageBlit* pRegions, - VkFilter filter) { - Log::debug("vulkan2", "vkCmdBlitImage called for command buffer {:x}, src image {:x}, dst image {:x}", - reinterpret_cast(commandBuffer), - reinterpret_cast(srcImage), - reinterpret_cast(dstImage)); - next_vkCmdBlitImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions, filter); -} -VkResult Layer::ovkAcquireNextImageKHR( - VkDevice device, - VkSwapchainKHR swapchain, - uint64_t timeout, - VkSemaphore semaphore, - VkFence fence, - uint32_t* pImageIndex) { - Log::debug("vulkan", "vkAcquireNextImageKHR called for swapchain {:x}, timeout: {}, semaphore: {:x}, fence: {:x}", - reinterpret_cast(swapchain), - timeout, - reinterpret_cast(semaphore), - reinterpret_cast(fence)); - auto res = next_vkAcquireNextImageKHR(device, swapchain, timeout, semaphore, fence, pImageIndex); - Log::debug("vulkan", "vkAcquireNextImageKHR({}) returned image index: {}", - static_cast(res), - *pImageIndex); - return res; -} + VkResult ovkCreateDevice( + VkPhysicalDevice physicalDevice, + const VkDeviceCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDevice* pDevice) { + return next_vkCreateDevice(physicalDevice, pCreateInfo, pAllocator, pDevice); + } + void ovkDestroyDevice( + VkDevice device, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyDevice(device, pAllocator); + } -#pragma clang diagnostic pop -// NOLINTEND + VkResult ovkSetDeviceLoaderData(VkDevice device, void* object) { + return next_vSetDeviceLoaderData(device, object); + } + + PFN_vkVoidFunction ovkGetInstanceProcAddr( + VkInstance instance, + const char* pName) { + return next_vkGetInstanceProcAddr(instance, pName); + } + PFN_vkVoidFunction ovkGetDeviceProcAddr( + VkDevice device, + const char* pName) { + return next_vkGetDeviceProcAddr(device, pName); + } + + void ovkGetPhysicalDeviceQueueFamilyProperties( + VkPhysicalDevice physicalDevice, + uint32_t* pQueueFamilyPropertyCount, + VkQueueFamilyProperties* pQueueFamilyProperties) { + next_vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties); + } + void ovkGetPhysicalDeviceMemoryProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceMemoryProperties* pMemoryProperties) { + next_vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties); + } + void ovkGetPhysicalDeviceProperties( + VkPhysicalDevice physicalDevice, + VkPhysicalDeviceProperties* pProperties) { + next_vkGetPhysicalDeviceProperties(physicalDevice, pProperties); + } + VkResult ovkGetPhysicalDeviceSurfaceCapabilitiesKHR( + VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface, + VkSurfaceCapabilitiesKHR* pSurfaceCapabilities) { + return next_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, pSurfaceCapabilities); + } + + VkResult ovkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain) { + return next_vkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + } + VkResult ovkQueuePresentKHR( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo) { + return next_vkQueuePresentKHR(queue, pPresentInfo); + } + void ovkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroySwapchainKHR(device, swapchain, pAllocator); + } + + VkResult ovkGetSwapchainImagesKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint32_t* pSwapchainImageCount, + VkImage* pSwapchainImages) { + return next_vkGetSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, pSwapchainImages); + } + + VkResult ovkAllocateCommandBuffers( + VkDevice device, + const VkCommandBufferAllocateInfo* pAllocateInfo, + VkCommandBuffer* pCommandBuffers) { + return next_vkAllocateCommandBuffers(device, pAllocateInfo, pCommandBuffers); + } + void ovkFreeCommandBuffers( + VkDevice device, + VkCommandPool commandPool, + uint32_t commandBufferCount, + const VkCommandBuffer* pCommandBuffers) { + next_vkFreeCommandBuffers(device, commandPool, commandBufferCount, pCommandBuffers); + } + + VkResult ovkBeginCommandBuffer( + VkCommandBuffer commandBuffer, + const VkCommandBufferBeginInfo* pBeginInfo) { + return next_vkBeginCommandBuffer(commandBuffer, pBeginInfo); + } + VkResult ovkEndCommandBuffer( + VkCommandBuffer commandBuffer) { + return next_vkEndCommandBuffer(commandBuffer); + } + + VkResult ovkCreateCommandPool( + VkDevice device, + const VkCommandPoolCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkCommandPool* pCommandPool) { + return next_vkCreateCommandPool(device, pCreateInfo, pAllocator, pCommandPool); + } + void ovkDestroyCommandPool( + VkDevice device, + VkCommandPool commandPool, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyCommandPool(device, commandPool, pAllocator); + } + + VkResult ovkCreateImage( + VkDevice device, + const VkImageCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkImage* pImage) { + return next_vkCreateImage(device, pCreateInfo, pAllocator, pImage); + } + void ovkDestroyImage( + VkDevice device, + VkImage image, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroyImage(device, image, pAllocator); + } + + void ovkGetImageMemoryRequirements( + VkDevice device, + VkImage image, + VkMemoryRequirements* pMemoryRequirements) { + next_vkGetImageMemoryRequirements(device, image, pMemoryRequirements); + } + VkResult ovkBindImageMemory( + VkDevice device, + VkImage image, + VkDeviceMemory memory, + VkDeviceSize memoryOffset) { + return next_vkBindImageMemory(device, image, memory, memoryOffset); + } + + VkResult ovkAllocateMemory( + VkDevice device, + const VkMemoryAllocateInfo* pAllocateInfo, + const VkAllocationCallbacks* pAllocator, + VkDeviceMemory* pMemory) { + return next_vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); + } + void ovkFreeMemory( + VkDevice device, + VkDeviceMemory memory, + const VkAllocationCallbacks* pAllocator) { + next_vkFreeMemory(device, memory, pAllocator); + } + + VkResult ovkCreateSemaphore( + VkDevice device, + const VkSemaphoreCreateInfo* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSemaphore* pSemaphore) { + return next_vkCreateSemaphore(device, pCreateInfo, pAllocator, pSemaphore); + } + void ovkDestroySemaphore( + VkDevice device, + VkSemaphore semaphore, + const VkAllocationCallbacks* pAllocator) { + next_vkDestroySemaphore(device, semaphore, pAllocator); + } + + VkResult ovkGetMemoryFdKHR( + VkDevice device, + const VkMemoryGetFdInfoKHR* pGetFdInfo, + int* pFd) { + return next_vkGetMemoryFdKHR(device, pGetFdInfo, pFd); + } + VkResult ovkGetSemaphoreFdKHR( + VkDevice device, + const VkSemaphoreGetFdInfoKHR* pGetFdInfo, + int* pFd) { + return next_vkGetSemaphoreFdKHR(device, pGetFdInfo, pFd); + } + + void ovkGetDeviceQueue( + VkDevice device, + uint32_t queueFamilyIndex, + uint32_t queueIndex, + VkQueue* pQueue) { + next_vkGetDeviceQueue(device, queueFamilyIndex, queueIndex, pQueue); + } + VkResult ovkQueueSubmit( + VkQueue queue, + uint32_t submitCount, + const VkSubmitInfo* pSubmits, + VkFence fence) { + return next_vkQueueSubmit(queue, submitCount, pSubmits, fence); + } + + void ovkCmdPipelineBarrier( + VkCommandBuffer commandBuffer, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, + VkDependencyFlags dependencyFlags, + uint32_t memoryBarrierCount, + const VkMemoryBarrier* pMemoryBarriers, + uint32_t bufferMemoryBarrierCount, + const VkBufferMemoryBarrier* pBufferMemoryBarriers, + uint32_t imageMemoryBarrierCount, + const VkImageMemoryBarrier* pImageMemoryBarriers) { + next_vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, dependencyFlags, + memoryBarrierCount, pMemoryBarriers, + bufferMemoryBarrierCount, pBufferMemoryBarriers, + imageMemoryBarrierCount, pImageMemoryBarriers); + } + void ovkCmdBlitImage( + VkCommandBuffer commandBuffer, + VkImage srcImage, + VkImageLayout srcImageLayout, + VkImage dstImage, + VkImageLayout dstImageLayout, + uint32_t regionCount, + const VkImageBlit* pRegions, + VkFilter filter) { + next_vkCmdBlitImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions, filter); + } + + VkResult ovkAcquireNextImageKHR( + VkDevice device, + VkSwapchainKHR swapchain, + uint64_t timeout, + VkSemaphore semaphore, + VkFence fence, + uint32_t* pImageIndex) { + return next_vkAcquireNextImageKHR(device, swapchain, timeout, semaphore, fence, pImageIndex); + } +} diff --git a/src/main.cpp b/src/main.cpp index 39d54ca..7137e25 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -73,9 +73,8 @@ namespace { } const std::string name = getProcessName(); - Config::Configuration conf{}; try { - conf = Config::getConfig(name); + Config::activeConf = Config::getConfig(name); } catch (const std::exception& e) { std::cerr << "lsfg-vk: The configuration for " << name << " is invalid, exiting.\n"; std::cerr << e.what() << '\n'; @@ -83,6 +82,7 @@ namespace { } // exit silently if not enabled + auto& conf = Config::activeConf; if (!conf.enable) return; From 3526dde26cf2e78e6a399a17fcc7b206490a3060 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 16 Jul 2025 23:53:59 +0200 Subject: [PATCH 150/253] update hooks to exceptions --- include/hooks.hpp | 1 - include/utils/utils.hpp | 19 ++- src/hooks.cpp | 285 ++++++++++++++++++---------------------- src/utils/utils.cpp | 27 +++- 4 files changed, 168 insertions(+), 164 deletions(-) diff --git a/include/hooks.hpp b/include/hooks.hpp index 487686e..e38212c 100644 --- a/include/hooks.hpp +++ b/include/hooks.hpp @@ -14,7 +14,6 @@ namespace Hooks { VkDevice device; VkPhysicalDevice physicalDevice; std::pair queue; // graphics family - uint64_t frameGen; // amount of frames to generate }; /// Map of hooked Vulkan functions. diff --git a/include/utils/utils.hpp b/include/utils/utils.hpp index 138eb00..27b8264 100644 --- a/include/utils/utils.hpp +++ b/include/utils/utils.hpp @@ -4,8 +4,9 @@ #include #include -#include #include +#include +#include namespace Utils { @@ -68,4 +69,20 @@ namespace Utils { 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; + } diff --git a/src/hooks.cpp b/src/hooks.cpp index c589b79..6393aa3 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -1,248 +1,227 @@ #include "hooks.hpp" +#include "common/exception.hpp" +#include "utils/utils.hpp" #include "context.hpp" #include "layer.hpp" -#include "utils/log.hpp" -#include "utils/utils.hpp" -#include "common/exception.hpp" #include -#include -#include +#include +#include #include #include +#include +#include #include -#include #include using namespace Hooks; namespace { - // instance hooks - + /// + /// Add extensions to the instance create info. + /// VkResult myvkCreateInstance( const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) { - // add extensions - auto extensions = Utils::addExtensions(pCreateInfo->ppEnabledExtensionNames, - pCreateInfo->enabledExtensionCount, { + auto extensions = Utils::addExtensions( + pCreateInfo->ppEnabledExtensionNames, + pCreateInfo->enabledExtensionCount, + { "VK_KHR_get_physical_device_properties2", "VK_KHR_external_memory_capabilities", "VK_KHR_external_semaphore_capabilities" - }); + } + ); VkInstanceCreateInfo createInfo = *pCreateInfo; createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); auto res = Layer::ovkCreateInstance(&createInfo, pAllocator, pInstance); - if (res != VK_SUCCESS) { - Log::error("hooks", "Failed to create Vulkan instance: {:x}", - static_cast(res)); - return res; - } - - Log::info("hooks", "Instance created successfully: {:x}", - reinterpret_cast(*pInstance)); + if (res == VK_ERROR_EXTENSION_NOT_PRESENT) + throw std::runtime_error( + "Required Vulkan instance extensions are not present." + "Your GPU driver is not supported."); return res; } - void myvkDestroyInstance( - VkInstance instance, - const VkAllocationCallbacks* pAllocator) { - Log::info("hooks", "Instance destroyed successfully: {:x}", - reinterpret_cast(instance)); - Layer::ovkDestroyInstance(instance, pAllocator); - } - - // device hooks - - std::unordered_map devices; + /// Map of devices to related information. + std::unordered_map deviceToInfo; + /// + /// Add extensions to the device create info. + /// (function pointers are not initialized yet) + /// VkResult myvkCreateDevicePre( VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) { // add extensions - auto extensions = Utils::addExtensions(pCreateInfo->ppEnabledExtensionNames, - pCreateInfo->enabledExtensionCount, { + auto extensions = Utils::addExtensions( + pCreateInfo->ppEnabledExtensionNames, + pCreateInfo->enabledExtensionCount, + { "VK_KHR_external_memory", "VK_KHR_external_memory_fd", "VK_KHR_external_semaphore", "VK_KHR_external_semaphore_fd" - }); - + } + ); VkDeviceCreateInfo createInfo = *pCreateInfo; createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); auto res = Layer::ovkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice); - if (res != VK_SUCCESS) { - Log::error("hooks", "Failed to create Vulkan device: {:x}", - static_cast(res)); - return res; - } - - Log::info("hooks", "Device created successfully: {:x}", - reinterpret_cast(*pDevice)); + if (res == VK_ERROR_EXTENSION_NOT_PRESENT) + throw std::runtime_error( + "Required Vulkan device extensions are not present." + "Your GPU driver is not supported."); return res; } + /// + /// Add related device information after the device is created. + /// VkResult myvkCreateDevicePost( VkPhysicalDevice physicalDevice, VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks*, VkDevice* pDevice) { - // store device info - Log::debug("hooks", "Creating device info for device: {:x}", - reinterpret_cast(*pDevice)); - try { - const char* frameGenEnv = std::getenv("LSFG_MULTIPLIER"); - const uint64_t frameGen = static_cast( - std::max(1, std::stol(frameGenEnv ? frameGenEnv : "2") - 1)); - Log::debug("hooks", "Using {}x frame generation", - frameGen + 1); - - auto queue = Utils::findQueue(*pDevice, physicalDevice, pCreateInfo, - VK_QUEUE_GRAPHICS_BIT); - Log::debug("hooks", "Found queue at index {}: {:x}", - queue.first, reinterpret_cast(queue.second)); - - devices.emplace(*pDevice, DeviceInfo { - .device = *pDevice, - .physicalDevice = physicalDevice, - .queue = queue, - .frameGen = frameGen, - }); - } catch (const std::exception& e) { - Log::error("hooks", "Failed to create device info: {}", e.what()); - return VK_ERROR_INITIALIZATION_FAILED; - } - - Log::info("hooks", "Device info created successfully for: {:x}", - reinterpret_cast(*pDevice)); + deviceToInfo.emplace(*pDevice, DeviceInfo { + .device = *pDevice, + .physicalDevice = physicalDevice, + .queue = Utils::findQueue(*pDevice, physicalDevice, pCreateInfo, VK_QUEUE_GRAPHICS_BIT) + }); return VK_SUCCESS; } - void myvkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) { - devices.erase(device); // erase device info - - Log::info("hooks", "Device & Device info destroyed successfully: {:x}", - reinterpret_cast(device)); + /// Erase the device information when the device is destroyed. + void myvkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) noexcept { + deviceToInfo.erase(device); Layer::ovkDestroyDevice(device, pAllocator); } - // swapchain hooks - std::unordered_map swapchains; std::unordered_map swapchainToDeviceTable; + /// + /// Adjust swapchain creation parameters and create a swapchain context. + /// VkResult myvkCreateSwapchainKHR( VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, - VkSwapchainKHR* pSwapchain) { - auto it = devices.find(device); - if (it == devices.end()) { - Log::warn("hooks", "Created swapchain without device info present"); + VkSwapchainKHR* pSwapchain) noexcept { + // find device + auto it = deviceToInfo.find(device); + if (it == deviceToInfo.end()) { + Utils::logLimitN("swapMap", 5, "Device not found in map"); return Layer::ovkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); } + Utils::resetLimitN("swapMap"); auto& deviceInfo = it->second; - // update swapchain create info + // increase amount of images in swapchain VkSwapchainCreateInfoKHR createInfo = *pCreateInfo; - const uint32_t maxImageCount = Utils::getMaxImageCount(deviceInfo.physicalDevice, pCreateInfo->surface); - const uint32_t imageCount = createInfo.minImageCount + 1 + static_cast(deviceInfo.frameGen); - Log::debug("hooks", "Creating swapchain with max image count: {}/{}", imageCount, maxImageCount); - if (imageCount > maxImageCount) { - Log::warn("hooks", "LSFG_MULTIPLIER is set very high. This might lead to performance degradation"); - createInfo.minImageCount = maxImageCount; // limit to max possible + const auto maxImages = Utils::getMaxImageCount( + deviceInfo.physicalDevice, pCreateInfo->surface); + createInfo.minImageCount = createInfo.minImageCount + 1 + + static_cast(deviceInfo.queue.first); + if (createInfo.minImageCount > maxImages) { + createInfo.minImageCount = maxImages; + Utils::logLimitN("swapCount", 10, + "Requested image count (" + + std::to_string(pCreateInfo->minImageCount) + ") " + "exceeds maximum allowed (" + + std::to_string(maxImages) + "). " + "Continuing with maximum allowed image count. " + "This might lead to performance degradation."); } else { - createInfo.minImageCount = imageCount; // set to frameGen + 1 + Utils::resetLimitN("swapCount"); } - createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; // allow copy from/to images - createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; // force vsync - auto res = Layer::ovkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); - if (res != VK_SUCCESS) { - Log::error("hooks", "Failed to create swapchain: {}", static_cast(res)); - return res; - } - Log::info("hooks", "Swapchain created successfully: {:x}", - reinterpret_cast(*pSwapchain)); - // retire previous swapchain if it exists + // allow copy operations on swapchain images + createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + + // enforce vsync + createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; + + // retire potential old swapchain if (pCreateInfo->oldSwapchain) { - Log::debug("hooks", "Retiring previous swapchain context: {:x}", - reinterpret_cast(pCreateInfo->oldSwapchain)); swapchains.erase(pCreateInfo->oldSwapchain); swapchainToDeviceTable.erase(pCreateInfo->oldSwapchain); - Log::info("hooks", "Previous swapchain context retired successfully: {:x}", - reinterpret_cast(pCreateInfo->oldSwapchain)); } - // create swapchain context - Log::debug("hooks", "Creating swapchain context for device: {:x}", - reinterpret_cast(device)); + // create swapchain + auto res = Layer::ovkCreateSwapchainKHR(device, &createInfo, pAllocator, pSwapchain); + if (res != VK_SUCCESS) + return res; // can't be caused by lsfg-vk (yet) + try { - // get swapchain images + // get all swapchain images uint32_t imageCount{}; res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr); if (res != VK_SUCCESS || imageCount == 0) - throw LSFG::vulkan_error(res, "Failed to get swapchain images count"); + throw LSFG::vulkan_error(res, "Failed to get swapchain image count"); std::vector swapchainImages(imageCount); - res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, swapchainImages.data()); + res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, + &imageCount, swapchainImages.data()); if (res != VK_SUCCESS) throw LSFG::vulkan_error(res, "Failed to get swapchain images"); - Log::debug("hooks", "Swapchain has {} images", swapchainImages.size()); // create swapchain context + swapchainToDeviceTable.emplace(*pSwapchain, device); swapchains.emplace(*pSwapchain, LsContext( deviceInfo, *pSwapchain, pCreateInfo->imageExtent, swapchainImages )); - swapchainToDeviceTable.emplace(*pSwapchain, device); - } catch (const LSFG::vulkan_error& e) { - Log::error("hooks", "Encountered Vulkan error {:x} while creating swapchain context: {}", - static_cast(e.error()), e.what()); - return e.error(); + + Utils::resetLimitN("swapCtxCreate"); } catch (const std::exception& e) { - Log::error("hooks", "Encountered error while creating swapchain context: {}", e.what()); + Utils::logLimitN("swapCtxCreate", 5, + "An error occurred while creating the swapchain wrapper:\n" + "- " + std::string(e.what())); return VK_ERROR_INITIALIZATION_FAILED; } - - Log::info("hooks", "Swapchain context created successfully for: {:x}", - reinterpret_cast(*pSwapchain)); - return res; + return VK_SUCCESS; } + /// + /// Update presentation parameters and present the next frame(s). + /// VkResult myvkQueuePresentKHR( VkQueue queue, - const VkPresentInfoKHR* pPresentInfo) { + const VkPresentInfoKHR* pPresentInfo) noexcept { + // find swapchain device auto it = swapchainToDeviceTable.find(*pPresentInfo->pSwapchains); if (it == swapchainToDeviceTable.end()) { - Log::warn("hooks2", "Swapchain {:x} not found in swapchainToDeviceTable", - reinterpret_cast(*pPresentInfo->pSwapchains)); + Utils::logLimitN("swapMap", 5, + "Swapchain not found in map"); return Layer::ovkQueuePresentKHR(queue, pPresentInfo); } - auto it2 = devices.find(it->second); - if (it2 == devices.end()) { - Log::warn("hooks2", "Device {:x} not found in devices", - reinterpret_cast(it->second)); + + // find device info + auto it2 = deviceToInfo.find(it->second); + if (it2 == deviceToInfo.end()) { + Utils::logLimitN("swapMap", 5, + "Device not found in map"); return Layer::ovkQueuePresentKHR(queue, pPresentInfo); } + auto& deviceInfo = it2->second; + + // find swapchain context auto it3 = swapchains.find(*pPresentInfo->pSwapchains); if (it3 == swapchains.end()) { - Log::warn("hooks2", "Swapchain {:x} not found in swapchains", - reinterpret_cast(*pPresentInfo->pSwapchains)); + Utils::logLimitN("swapMap", 5, + "Swapchain context not found in map"); return Layer::ovkQueuePresentKHR(queue, pPresentInfo); } - - auto& deviceInfo = it2->second; auto& swapchain = it3->second; - // patch vsync NOLINTBEGIN + // enforce vsync | NOLINTBEGIN #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" const VkSwapchainPresentModeInfoEXT* presentModeInfo = @@ -255,48 +234,35 @@ namespace { } presentModeInfo = reinterpret_cast(presentModeInfo->pNext); - } // NOLINTEND -#pragma clang diagnostic pop + } + #pragma clang diagnostic pop - // present the next frame - Log::debug("hooks2", "Presenting swapchain: {:x} on queue: {:x}", - reinterpret_cast(*pPresentInfo->pSwapchains), - reinterpret_cast(queue)); - VkResult res{}; + // NOLINTEND | present the next frame + VkResult res{}; // might return VK_SUBOPTIMAL_KHR try { std::vector semaphores(pPresentInfo->waitSemaphoreCount); std::copy_n(pPresentInfo->pWaitSemaphores, semaphores.size(), semaphores.data()); - Log::debug("hooks2", "Waiting on {} semaphores", semaphores.size()); - // present the next frame res = swapchain.present(deviceInfo, pPresentInfo->pNext, queue, semaphores, *pPresentInfo->pImageIndices); - } catch (const LSFG::vulkan_error& e) { - Log::error("hooks2", "Encountered Vulkan error {:x} while presenting: {}", - static_cast(e.error()), e.what()); - return e.error(); + + Utils::resetLimitN("swapPresent"); } catch (const std::exception& e) { - Log::error("hooks2", "Encountered error while creating presenting: {}", - e.what()); + Utils::logLimitN("swapPresent", 5, + "An error occurred while presenting the swapchain:\n" + "- " + std::string(e.what())); return VK_ERROR_INITIALIZATION_FAILED; } - - // non VK_SUCCESS or VK_SUBOPTIMAL_KHR doesn't reach here - Log::debug("hooks2", "Presented swapchain {:x} on queue {:x} successfully", - reinterpret_cast(*pPresentInfo->pSwapchains), - reinterpret_cast(queue)); return res; } + /// Erase the swapchain context and mapping when the swapchain is destroyed. void myvkDestroySwapchainKHR( VkDevice device, VkSwapchainKHR swapchain, - const VkAllocationCallbacks* pAllocator) { - swapchains.erase(swapchain); // erase swapchain context + const VkAllocationCallbacks* pAllocator) noexcept { + swapchains.erase(swapchain); swapchainToDeviceTable.erase(swapchain); - - Log::info("hooks", "Swapchain & Swapchain context destroyed successfully: {:x}", - reinterpret_cast(swapchain)); Layer::ovkDestroySwapchainKHR(device, swapchain, pAllocator); } } @@ -304,7 +270,6 @@ namespace { std::unordered_map Hooks::hooks = { // instance hooks {"vkCreateInstance", reinterpret_cast(myvkCreateInstance)}, - {"vkDestroyInstance", reinterpret_cast(myvkDestroyInstance)}, // device hooks {"vkCreateDevicePre", reinterpret_cast(myvkCreateDevicePre)}, diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 8340464..4d10e08 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -5,11 +5,14 @@ #include -#include -#include +#include #include #include +#include +#include +#include #include +#include #include using namespace Utils; @@ -183,3 +186,23 @@ void Utils::copyImage(VkCommandBuffer buf, 1, &presentBarrier); } } + +namespace { + auto& logCounts() { + static std::unordered_map map; + return map; + } +} + +void Utils::logLimitN(const std::string& id, size_t n, const std::string& message) { + auto& count = logCounts()[id]; + if (count <= n) + std::cerr << "lsfg-vk: " << message << '\n'; + if (count == n) + std::cerr << "(above message has been repeated " << n << " times, suppressing further)\n"; + count++; +} + +void Utils::resetLimitN(const std::string& id) noexcept { + logCounts().erase(id); +} From 5e68315c2fdb4bb2990a42a006f6c59d972c62d2 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 16 Jul 2025 23:54:27 +0200 Subject: [PATCH 151/253] colon correctness --- src/config/config.cpp | 6 +++--- src/main.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config/config.cpp b/src/config/config.cpp index d028ee8..8a63cf2 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -98,14 +98,14 @@ bool Config::loadAndWatchConfig(const std::string& file) { try { const int fd = inotify_init(); if (fd < 0) - throw std::runtime_error("Failed to initialize inotify\n" + throw std::runtime_error("Failed to initialize inotify:\n" "- " + std::string(strerror(errno))); const int wd = inotify_add_watch(fd, file.c_str(), IN_MODIFY | IN_CLOSE_WRITE); if (wd < 0) { close(fd); - throw std::runtime_error("Failed to add inotify watch for " + file + "\n" + throw std::runtime_error("Failed to add inotify watch for " + file + ":\n" "- " + std::string(strerror(errno))); } @@ -119,7 +119,7 @@ bool Config::loadAndWatchConfig(const std::string& file) { inotify_rm_watch(fd, wd); close(fd); - throw std::runtime_error("Error reading inotify event\n" + throw std::runtime_error("Error reading inotify event:\n" "- " + std::string(strerror(errno))); } diff --git a/src/main.cpp b/src/main.cpp index 7137e25..47c3a68 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -76,7 +76,7 @@ namespace { try { Config::activeConf = Config::getConfig(name); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: The configuration for " << name << " is invalid, exiting.\n"; + std::cerr << "lsfg-vk: The configuration for " << name << " is invalid, exiting:\n"; std::cerr << e.what() << '\n'; exit(0); } From 9f528a688af106e866458633412ce18c3cf4a051 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 00:42:03 +0200 Subject: [PATCH 152/253] translate context to new config system --- include/config/config.hpp | 11 +++ include/context.hpp | 1 - include/utils/utils.hpp | 14 ++++ src/config/config.cpp | 128 ++++++++++++++++++-------------- src/context.cpp | 151 +++++++++++++++----------------------- src/hooks.cpp | 5 ++ src/main.cpp | 42 ++++------- src/utils/utils.cpp | 26 +++++++ 8 files changed, 201 insertions(+), 177 deletions(-) diff --git a/include/config/config.hpp b/include/config/config.hpp index 69a0d41..bc77cc7 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -41,6 +41,17 @@ namespace Config { /// bool loadAndWatchConfig(const std::string& file); + /// + /// Reread the configuration file while preserving the old configuration + /// in case of an error. + /// + /// @param file The path to the configuration file. + /// @return Whether a configuration exists or not. + /// + /// @throws std::runtime_error if an error occurs while loading the configuration file. + /// + bool updateConfig(const std::string& file); + /// /// Get the configuration for a game. /// diff --git a/include/context.hpp b/include/context.hpp index 37269b2..3438e48 100644 --- a/include/context.hpp +++ b/include/context.hpp @@ -63,7 +63,6 @@ private: Mini::CommandPool cmdPool; uint64_t frameIdx{0}; - bool isPerfMode{false}; struct RenderPassInfo { Mini::CommandBuffer preCopyBuf; // copy from swapchain image to frame_0/frame_1 diff --git a/include/utils/utils.hpp b/include/utils/utils.hpp index 27b8264..90185de 100644 --- a/include/utils/utils.hpp +++ b/include/utils/utils.hpp @@ -85,4 +85,18 @@ namespace Utils { /// void resetLimitN(const std::string& id) noexcept; + /// + /// Get the process name of the current executable. + /// + /// @return The name of the process. + /// + std::string getProcessName(); + + /// + /// Get the configuration file path. + /// + /// @return The path to the configuration file. + /// + std::string getConfigFile(); + } diff --git a/src/config/config.cpp b/src/config/config.cpp index 8a63cf2..9b14e2f 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -36,65 +36,14 @@ namespace { Configuration Config::activeConf{}; bool Config::loadAndWatchConfig(const std::string& file) { - if (!std::filesystem::exists(file)) + globalConf.valid = std::make_shared(true); + + auto res = updateConfig(file); + if (!res) return false; - // parse config file - std::optional parsed; - try { - parsed.emplace(toml::parse(file)); - } catch (const std::exception& e) { - throw LSFG::rethrowable_error("Unable to parse configuration file", e); - } - auto& toml = *parsed; - - // parse global configuration - auto& global = globalConf; - const toml::value globalTable = toml::find_or_default(toml, "global"); - global.enable = toml::find_or(globalTable, "enable", false); - global.dll = toml::find_or(globalTable, "dll", std::string()); - global.multiplier = toml::find_or(globalTable, "multiplier", size_t(2)); - global.flowScale = toml::find_or(globalTable, "flow_scale", 1.0F); - global.performance = toml::find_or(globalTable, "performance_mode", false); - global.hdr = toml::find_or(globalTable, "hdr_mode", false); - global.valid = std::make_shared(true); - - // validate global configuration - if (global.multiplier < 2) - throw std::runtime_error("Multiplier cannot be less than 2"); - if (global.flowScale < 0.25F || global.flowScale > 1.0F) - throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); - - // parse game-specific configuration - auto& games = gameConfs.emplace(); - const toml::value gamesList = toml::find_or_default(toml, "game"); - for (const auto& gameTable : gamesList.as_array()) { - if (!gameTable.is_table()) - throw std::runtime_error("Invalid game configuration entry"); - if (!gameTable.contains("exe")) - throw std::runtime_error("Game override missing 'exe' field"); - - const std::string exe = toml::find(gameTable, "exe"); - Configuration game{ - .enable = toml::find_or(gameTable, "enable", global.enable), - .dll = toml::find_or(gameTable, "dll", global.dll), - .multiplier = toml::find_or(gameTable, "multiplier", global.multiplier), - .flowScale = toml::find_or(gameTable, "flow_scale", global.flowScale), - .performance = toml::find_or(gameTable, "performance_mode", global.performance), - .hdr = toml::find_or(gameTable, "hdr_mode", global.hdr), - .valid = global.valid // only need a single validity flag - }; - - // validate the configuration - if (game.multiplier < 2) - throw std::runtime_error("Multiplier cannot be less than 2"); - if (game.flowScale < 0.25F || game.flowScale > 1.0F) - throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); - games[exe] = std::move(game); - } - // prepare config watcher - std::thread([file = file, valid = global.valid]() { + std::thread([file = file, valid = globalConf.valid]() { try { const int fd = inotify_init(); if (fd < 0) @@ -131,6 +80,7 @@ bool Config::loadAndWatchConfig(const std::string& file) { continue; // stall a bit, then mark as invalid + std::cerr << "lsfg-vk: Configuration file changed, invalidating config...\n"; discard_until.emplace(std::chrono::steady_clock::now() + std::chrono::milliseconds(500)); } @@ -156,6 +106,72 @@ bool Config::loadAndWatchConfig(const std::string& file) { return true; } +bool Config::updateConfig(const std::string& file) { + if (!std::filesystem::exists(file)) + return false; + + // parse config file + std::optional parsed; + try { + parsed.emplace(toml::parse(file)); + } catch (const std::exception& e) { + throw LSFG::rethrowable_error("Unable to parse configuration file", e); + } + auto& toml = *parsed; + + // parse global configuration + const toml::value globalTable = toml::find_or_default(toml, "global"); + const Configuration global{ + .enable = toml::find_or(globalTable, "enable", false), + .dll = toml::find_or(globalTable, "dll", std::string()), + .multiplier = toml::find_or(globalTable, "multiplier", size_t(2)), + .flowScale = toml::find_or(globalTable, "flow_scale", 1.0F), + .performance = toml::find_or(globalTable, "performance_mode", false), + .hdr = toml::find_or(globalTable, "hdr_mode", false), + .valid = globalConf.valid // use the same validity flag + }; + + // validate global configuration + if (global.multiplier < 2) + throw std::runtime_error("Multiplier cannot be less than 2"); + if (global.flowScale < 0.25F || global.flowScale > 1.0F) + throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); + + // parse game-specific configuration + std::unordered_map games; + const toml::value gamesList = toml::find_or_default(toml, "game"); + for (const auto& gameTable : gamesList.as_array()) { + if (!gameTable.is_table()) + throw std::runtime_error("Invalid game configuration entry"); + if (!gameTable.contains("exe")) + throw std::runtime_error("Game override missing 'exe' field"); + + const std::string exe = toml::find(gameTable, "exe"); + Configuration game{ + .enable = toml::find_or(gameTable, "enable", global.enable), + .dll = toml::find_or(gameTable, "dll", global.dll), + .multiplier = toml::find_or(gameTable, "multiplier", global.multiplier), + .flowScale = toml::find_or(gameTable, "flow_scale", global.flowScale), + .performance = toml::find_or(gameTable, "performance_mode", global.performance), + .hdr = toml::find_or(gameTable, "hdr_mode", global.hdr), + .valid = global.valid // only need a single validity flag + }; + + // validate the configuration + if (game.multiplier < 2) + throw std::runtime_error("Multiplier cannot be less than 2"); + if (game.flowScale < 0.25F || game.flowScale > 1.0F) + throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); + games[exe] = std::move(game); + } + + // store configurations + global.valid->store(true, std::memory_order_release); + globalConf = global; + gameConfs = std::move(games); + return true; +} + Configuration Config::getConfig(std::string_view name) { if (name.empty() || !gameConfs.has_value()) return globalConf; diff --git a/src/context.cpp b/src/context.cpp index 712ef23..ee203bd 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -1,147 +1,124 @@ #include "context.hpp" +#include "config/config.hpp" +#include "common/exception.hpp" #include "extract/extract.hpp" #include "extract/trans.hpp" #include "utils/utils.hpp" -#include "utils/log.hpp" #include "hooks.hpp" #include "layer.hpp" -#include "common/exception.hpp" #include #include #include -#include +#include +#include #include #include +#include #include #include #include +#include LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, VkExtent2D extent, const std::vector& swapchainImages) : swapchain(swapchain), swapchainImages(swapchainImages), extent(extent) { - // read environment variables - const char* lsfgFlowScaleStr = getenv("LSFG_FLOW_SCALE"); - float flowScale = lsfgFlowScaleStr - ? std::stof(lsfgFlowScaleStr) - : 1.0F; - flowScale = std::max(0.25F, std::min(flowScale, 1.0F)); + // get updated configuration + const auto& conf = Config::activeConf; + if (!conf.valid->load(std::memory_order_relaxed)) { + std::cerr << "lsfg-vk: Configuration is no longer valid, rereading...\n"; - const char* lsfgHdrStr = getenv("LSFG_HDR"); - const bool isHdr = lsfgHdrStr - ? *lsfgHdrStr == '1' - : false; + const bool wasPerformance = conf.performance; - const char* lsfgPerfModeStr = getenv("LSFG_PERF_MODE"); - const bool perfMode = lsfgPerfModeStr - ? *lsfgPerfModeStr == '1' - : false; + // reread configuration + try { + const std::string file = Utils::getConfigFile(); + Config::updateConfig(file); + const std::string name = Utils::getProcessName(); + Config::activeConf = Config::getConfig(name); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: Failed to update configuration, continuing using old:\n"; + std::cerr << "- " << e.what() << '\n'; + } + if (wasPerformance && !Config::activeConf.performance) + LSFG_3_1P::finalize(); + if (!wasPerformance && Config::activeConf.performance) + LSFG_3_1::finalize(); + } // we could take the format from the swapchain, // but honestly this is safer. - const VkFormat format = isHdr + const VkFormat format = conf.hdr ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_R16G16B16A16_SFLOAT; // prepare textures for lsfg - int frame_0_fd{}; - this->frame_0 = Mini::Image( - info.device, info.physicalDevice, - extent, format, - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, - &frame_0_fd); - Log::info("context", "Created frame_0 image and obtained fd: {}", - frame_0_fd); + std::array fds{}; + this->frame_0 = Mini::Image(info.device, info.physicalDevice, + extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, + &fds.at(0)); + this->frame_1 = Mini::Image(info.device, info.physicalDevice, + extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, + &fds.at(1)); - int frame_1_fd{}; - this->frame_1 = Mini::Image( - info.device, info.physicalDevice, - extent, format, - VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, - &frame_1_fd); - Log::info("context", "Created frame_1 image and obtained fd: {}", - frame_1_fd); - - std::vector out_n_fds(info.frameGen); - for (size_t i = 0; i < info.frameGen; ++i) { - this->out_n.emplace_back( - info.device, info.physicalDevice, + std::vector outFds(conf.multiplier - 1); + for (size_t i = 0; i < (conf.multiplier - 1); ++i) + this->out_n.emplace_back(info.device, info.physicalDevice, extent, format, - VK_IMAGE_USAGE_TRANSFER_SRC_BIT, - VK_IMAGE_ASPECT_COLOR_BIT, - &out_n_fds.at(i)); - Log::info("context", "Created out_n[{}] image and obtained fd: {}", - i, out_n_fds.at(i)); - } + VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_ASPECT_COLOR_BIT, + &outFds.at(i)); - this->isPerfMode = false; + // initialize lsfg auto* lsfgInitialize = LSFG_3_1::initialize; auto* lsfgCreateContext = LSFG_3_1::createContext; auto* lsfgDeleteContext = LSFG_3_1::deleteContext; - if (perfMode) { - Log::debug("context", "Using performance mode"); - this->isPerfMode = true; + if (conf.performance) { lsfgInitialize = LSFG_3_1P::initialize; lsfgCreateContext = LSFG_3_1P::createContext; lsfgDeleteContext = LSFG_3_1P::deleteContext; } - // initialize lsfg - Log::debug("context", "(entering LSFG initialization)"); + setenv("DISABLE_LSFG", "1", 1); // NOLINT - Extract::extractShaders(); + lsfgInitialize( Utils::getDeviceUUID(info.physicalDevice), - isHdr, 1.0F / flowScale, info.frameGen, + conf.hdr, 1.0F / conf.flowScale, conf.multiplier - 1, [](const std::string& name) { auto dxbc = Extract::getShader(name); auto spirv = Extract::translateShader(dxbc); return spirv; } ); - unsetenv("DISABLE_LSFG"); // NOLINT - Log::debug("context", "(exiting LSFG initialization)"); - // create lsfg context - Log::debug("context", "(entering LSFG context creation)"); this->lsfgCtxId = std::shared_ptr( - new int32_t(lsfgCreateContext(frame_0_fd, frame_1_fd, out_n_fds, - extent, format)), + new int32_t(lsfgCreateContext(fds.at(0), fds.at(1), outFds, extent, format)), [lsfgDeleteContext = lsfgDeleteContext](const int32_t* id) { - Log::info("context", - "(entering LSFG context deletion with id: {})", *id); lsfgDeleteContext(*id); - Log::info("context", - "(exiting LSFG context deletion with id: {})", *id); } ); - Log::info("context", "(exiting LSFG context creation with id: {})", - *this->lsfgCtxId); + + unsetenv("DISABLE_LSFG"); // NOLINT // prepare render passes this->cmdPool = Mini::CommandPool(info.device, info.queue.first); for (size_t i = 0; i < 8; i++) { auto& pass = this->passInfos.at(i); - pass.renderSemaphores.resize(info.frameGen); - pass.acquireSemaphores.resize(info.frameGen); - pass.postCopyBufs.resize(info.frameGen); - pass.postCopySemaphores.resize(info.frameGen); - pass.prevPostCopySemaphores.resize(info.frameGen); + pass.renderSemaphores.resize(conf.multiplier - 1); + pass.acquireSemaphores.resize(conf.multiplier - 1); + pass.postCopyBufs.resize(conf.multiplier - 1); + pass.postCopySemaphores.resize(conf.multiplier - 1); + pass.prevPostCopySemaphores.resize(conf.multiplier - 1); } - - Log::info("context", "Remaining misc context init finished successfully."); } VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue, const std::vector& gameRenderSemaphores, uint32_t presentIdx) { + const auto& conf = Config::activeConf; auto& pass = this->passInfos.at(this->frameIdx % 8); // 1. copy swapchain image to frame_0/frame_1 - Log::debug("context2", "1. Copying swapchain image {} to frame {}", - presentIdx, this->frameIdx % 2 == 0 ? "0" : "1"); int preCopySemaphoreFd{}; pass.preCopySemaphores.at(0) = Mini::Semaphore(info.device, &preCopySemaphoreFd); pass.preCopySemaphores.at(1) = Mini::Semaphore(info.device); @@ -167,28 +144,21 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk pass.preCopySemaphores.at(1).handle() }); // 2. render intermediary frames - Log::debug("context2", "2. Rendering intermediary frames"); - std::vector renderSemaphoreFds(info.frameGen); - for (size_t i = 0; i < info.frameGen; ++i) + std::vector renderSemaphoreFds(conf.multiplier - 1); + for (size_t i = 0; i < (conf.multiplier - 1); ++i) pass.renderSemaphores.at(i) = Mini::Semaphore(info.device, &renderSemaphoreFds.at(i)); - Log::debug("context2", - "(entering LSFG present with id: {})", *this->lsfgCtxId); - if (this->isPerfMode) { + if (conf.performance) LSFG_3_1P::presentContext(*this->lsfgCtxId, preCopySemaphoreFd, renderSemaphoreFds); - } else { + else LSFG_3_1::presentContext(*this->lsfgCtxId, preCopySemaphoreFd, renderSemaphoreFds); - } - Log::debug("context2", - "(exiting LSFG present with id: {})", *this->lsfgCtxId); - for (size_t i = 0; i < info.frameGen; i++) { + for (size_t i = 0; i < (conf.multiplier - 1); i++) { // 3. acquire next swapchain image - Log::debug("context2", "3. Acquiring next swapchain image for frame {}", i); pass.acquireSemaphores.at(i) = Mini::Semaphore(info.device); uint32_t imageIdx{}; auto res = Layer::ovkAcquireNextImageKHR(info.device, this->swapchain, UINT64_MAX, @@ -197,7 +167,6 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image"); // 4. copy output image to swapchain image - Log::debug("context2", "4. Copying output image to swapchain image for frame {}", i); pass.postCopySemaphores.at(i) = Mini::Semaphore(info.device); pass.prevPostCopySemaphores.at(i) = Mini::Semaphore(info.device); pass.postCopyBufs.at(i) = Mini::CommandBuffer(info.device, this->cmdPool); @@ -218,7 +187,6 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk pass.prevPostCopySemaphores.at(i).handle() }); // 5. present swapchain image - Log::debug("context2", "5. Presenting swapchain image for frame {}", i); std::vector waitSemaphores{ pass.postCopySemaphores.at(i).handle() }; if (i != 0) waitSemaphores.emplace_back(pass.prevPostCopySemaphores.at(i - 1).handle()); @@ -237,9 +205,8 @@ VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, Vk } // 6. present actual next frame - Log::debug("context2", "6. Presenting actual next frame"); VkSemaphore lastPrevPostCopySemaphore = - pass.prevPostCopySemaphores.at(info.frameGen - 1).handle(); + pass.prevPostCopySemaphores.at(conf.multiplier - 1 - 1).handle(); const VkPresentInfoKHR presentInfo{ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, diff --git a/src/hooks.cpp b/src/hooks.cpp index 6393aa3..4615aed 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -4,6 +4,7 @@ #include "context.hpp" #include "layer.hpp" +#include #include #include @@ -179,6 +180,10 @@ namespace { swapchainImages )); + std::cerr << "lsfg-vk: Swapchain context " << + (createInfo.oldSwapchain ? "recreated" : "created") + << " (using " << imageCount << " images).\n"; + Utils::resetLimitN("swapCtxCreate"); } catch (const std::exception& e) { Utils::logLimitN("swapCtxCreate", 5, diff --git a/src/main.cpp b/src/main.cpp index 47c3a68..7b26d73 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,12 @@ #include "config/config.hpp" +#include "extract/extract.hpp" +#include "utils/utils.hpp" -#include #include -#include #include #include #include -#include // NOLINT -#include - namespace { /// Check if the library is preloaded or Vulkan loaded. bool isPreload() { @@ -29,27 +26,6 @@ namespace { return benchmark_str == "1"; } - /// Get the process name - std::string getProcessName() { - std::array exe{}; - const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1); - if (exe_len <= 0) - return "Unknown Process"; - exe.at(static_cast(exe_len)) = '\0'; - return{basename(exe.data())}; - } - - /// Get the config file - std::string getConfigFile() { - const char* configFile = std::getenv("LSFG_CONFIG"); - if (configFile && *configFile != '\0') - return{configFile}; - const char* homePath = std::getenv("HOME"); - if (homePath && *homePath != '\0') - return std::string(homePath) + "/.config/lsfg-vk.toml"; - return "/etc/lsfg-vk.toml"; - } - __attribute__((constructor)) void lsfgvk_init() { if (isPreload()) { if (requestedBenchmark()) { @@ -63,7 +39,7 @@ namespace { } // read configuration (might block) - const std::string file = getConfigFile(); + const std::string file = Utils::getConfigFile(); try { Config::loadAndWatchConfig(file); } catch (const std::exception& e) { @@ -72,7 +48,7 @@ namespace { exit(0); } - const std::string name = getProcessName(); + const std::string name = Utils::getProcessName(); try { Config::activeConf = Config::getConfig(name); } catch (const std::exception& e) { @@ -93,5 +69,15 @@ namespace { std::cerr << " Flow Scale: " << conf.flowScale << '\n'; std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; + + // load shaders + try { + Extract::extractShaders(); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred while trying to extract the shaders, exiting:\n"; + std::cerr << "- " << e.what() << '\n'; + exit(0); + } + std::cerr << "lsfg-vk: Shaders extracted successfully.\n"; } } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 4d10e08..855d5a9 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -4,16 +4,21 @@ #include "layer.hpp" #include +#include +#include // NOLINT +#include #include #include #include #include +#include #include #include #include #include #include +#include using namespace Utils; @@ -206,3 +211,24 @@ void Utils::logLimitN(const std::string& id, size_t n, const std::string& messag void Utils::resetLimitN(const std::string& id) noexcept { logCounts().erase(id); } + +/// Get the process name +std::string Utils::getProcessName() { + std::array exe{}; + const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1); + if (exe_len <= 0) + return "Unknown Process"; + exe.at(static_cast(exe_len)) = '\0'; + return{basename(exe.data())}; +} + +/// Get the config file +std::string Utils::getConfigFile() { + const char* configFile = std::getenv("LSFG_CONFIG"); + if (configFile && *configFile != '\0') + return{configFile}; + const char* homePath = std::getenv("HOME"); + if (homePath && *homePath != '\0') + return std::string(homePath) + "/.config/lsfg-vk.toml"; + return "/etc/lsfg-vk.toml"; +} From f8bdfd90de40def50c7c4d9bddcf220e23cdaa8f Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 01:09:32 +0200 Subject: [PATCH 153/253] fix the config updates --- src/config/config.cpp | 128 +++++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 52 deletions(-) diff --git a/src/config/config.cpp b/src/config/config.cpp index 9b14e2f..eafc1f4 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -3,10 +3,12 @@ #include #include +#include #include #include #include #include +#include #include #include @@ -35,6 +37,79 @@ namespace { Configuration Config::activeConf{}; +namespace { + [[noreturn]] void thread( + const std::string& file, + const std::shared_ptr& valid) { + const int fd = inotify_init(); + if (fd < 0) + throw std::runtime_error("Failed to initialize inotify:\n" + "- " + std::string(strerror(errno))); + + const int wd = inotify_add_watch(fd, file.c_str(), + IN_MODIFY | IN_CLOSE_WRITE | IN_MOVE_SELF); + if (wd < 0) { + close(fd); + + throw std::runtime_error("Failed to add inotify watch for " + file + ":\n" + "- " + std::string(strerror(errno))); + } + + // watch for changes + std::optional discard_until; + + std::array buffer{}; + while (true) { + // poll fd + struct pollfd pfd{}; + pfd.fd = fd; + pfd.events = POLLIN; + const int pollRes = poll(&pfd, 1, 100); + if (pollRes < 0 && errno != EINTR) { + inotify_rm_watch(fd, wd); + close(fd); + + throw std::runtime_error("Error polling inotify events:\n" + "- " + std::string(strerror(errno))); + } + + // read fd if there are events + const ssize_t len = pollRes == 0 ? 0 : read(fd, buffer.data(), buffer.size()); + if (len <= 0 && errno != EINTR && pollRes > 0) { + inotify_rm_watch(fd, wd); + close(fd); + + throw std::runtime_error("Error reading inotify events:\n" + "- " + std::string(strerror(errno))); + } + + size_t i{}; + while (std::cmp_less(i, len)) { + auto* event = reinterpret_cast(&buffer.at(i)); + i += sizeof(inotify_event) + event->len; + + // stall a bit, then mark as invalid + if (!discard_until.has_value()) + std::cerr << "lsfg-vk: Configuration file changed, invalidating config...\n"; + discard_until.emplace(std::chrono::steady_clock::now() + + std::chrono::milliseconds(500)); + } + + auto now = std::chrono::steady_clock::now(); + if (discard_until.has_value() && now > *discard_until) { + discard_until.reset(); + + // mark config as invalid + valid->store(false, std::memory_order_release); + + // and wait until it has been marked as valid again + while (!valid->load(std::memory_order_acquire)) + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + } + } +} + bool Config::loadAndWatchConfig(const std::string& file) { globalConf.valid = std::make_shared(true); @@ -45,58 +120,7 @@ bool Config::loadAndWatchConfig(const std::string& file) { // prepare config watcher std::thread([file = file, valid = globalConf.valid]() { try { - const int fd = inotify_init(); - if (fd < 0) - throw std::runtime_error("Failed to initialize inotify:\n" - "- " + std::string(strerror(errno))); - - const int wd = inotify_add_watch(fd, file.c_str(), IN_MODIFY | IN_CLOSE_WRITE); - if (wd < 0) { - close(fd); - - throw std::runtime_error("Failed to add inotify watch for " + file + ":\n" - "- " + std::string(strerror(errno))); - } - - // watch for changes - std::optional discard_until; - - std::array buffer{}; - while (true) { - const ssize_t len = read(fd, buffer.data(), buffer.size()); - if (len <= 0 && errno != EINTR) { - inotify_rm_watch(fd, wd); - close(fd); - - throw std::runtime_error("Error reading inotify event:\n" - "- " + std::string(strerror(errno))); - } - - size_t i{}; - while (std::cmp_less(i, len)) { - auto* event = reinterpret_cast(&buffer.at(i)); - i += sizeof(inotify_event) + event->len; - if (event->len <= 0) - continue; - - // stall a bit, then mark as invalid - std::cerr << "lsfg-vk: Configuration file changed, invalidating config...\n"; - discard_until.emplace(std::chrono::steady_clock::now() - + std::chrono::milliseconds(500)); - } - - auto now = std::chrono::steady_clock::now(); - if (discard_until.has_value() && now > *discard_until) { - discard_until.reset(); - - // mark config as invalid - valid->store(false, std::memory_order_release); - - // and wait until it has been marked as valid again - while (!valid->load(std::memory_order_acquire)) - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - } + thread(file, valid); } catch (const std::exception& e) { std::cerr << "lsfg-vk: Error in config watcher thread:\n"; std::cerr << "- " << e.what() << '\n'; From 1d9b89122f3e84aa901327e3006d31c7284d2c42 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 01:53:32 +0200 Subject: [PATCH 154/253] monitor directory instead of file --- src/config/config.cpp | 16 +++++++++++----- src/utils/utils.cpp | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/config/config.cpp b/src/config/config.cpp index eafc1f4..80d28a0 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -46,17 +46,19 @@ namespace { throw std::runtime_error("Failed to initialize inotify:\n" "- " + std::string(strerror(errno))); - const int wd = inotify_add_watch(fd, file.c_str(), + const std::string parent = std::filesystem::path(file).parent_path().string(); + const int wd = inotify_add_watch(fd, parent.c_str(), IN_MODIFY | IN_CLOSE_WRITE | IN_MOVE_SELF); if (wd < 0) { close(fd); - throw std::runtime_error("Failed to add inotify watch for " + file + ":\n" + throw std::runtime_error("Failed to add inotify watch for " + parent + ":\n" "- " + std::string(strerror(errno))); } // watch for changes std::optional discard_until; + const std::string filename = std::filesystem::path(file).filename().string(); std::array buffer{}; while (true) { @@ -87,10 +89,14 @@ namespace { while (std::cmp_less(i, len)) { auto* event = reinterpret_cast(&buffer.at(i)); i += sizeof(inotify_event) + event->len; + if (event->len <= 0 || event->mask & IN_IGNORED) + continue; + + std::string name(reinterpret_cast(event->name)); + if (name != filename) + continue; // stall a bit, then mark as invalid - if (!discard_until.has_value()) - std::cerr << "lsfg-vk: Configuration file changed, invalidating config...\n"; discard_until.emplace(std::chrono::steady_clock::now() + std::chrono::milliseconds(500)); } @@ -131,6 +137,7 @@ bool Config::loadAndWatchConfig(const std::string& file) { } bool Config::updateConfig(const std::string& file) { + globalConf.valid->store(true, std::memory_order_relaxed); if (!std::filesystem::exists(file)) return false; @@ -190,7 +197,6 @@ bool Config::updateConfig(const std::string& file) { } // store configurations - global.valid->store(true, std::memory_order_release); globalConf = global; gameConfs = std::move(games); return true; diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 855d5a9..f58532e 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -229,6 +229,6 @@ std::string Utils::getConfigFile() { return{configFile}; const char* homePath = std::getenv("HOME"); if (homePath && *homePath != '\0') - return std::string(homePath) + "/.config/lsfg-vk.toml"; - return "/etc/lsfg-vk.toml"; + return std::string(homePath) + "/.config/lsfg-vk/conf.toml"; + return "/etc/lsfg-vk/conf.toml"; } From ce26c8a9e63828122d9646a7558a7621392e6bd3 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 01:54:03 +0200 Subject: [PATCH 155/253] properly recreate swapchain fixes #81 --- src/context.cpp | 26 +++++++++++++++----------- src/hooks.cpp | 10 +++++++++- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/context.cpp b/src/context.cpp index ee203bd..481b7b4 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -26,27 +26,31 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, : swapchain(swapchain), swapchainImages(swapchainImages), extent(extent) { // get updated configuration - const auto& conf = Config::activeConf; + auto& conf = Config::activeConf; if (!conf.valid->load(std::memory_order_relaxed)) { - std::cerr << "lsfg-vk: Configuration is no longer valid, rereading...\n"; - - const bool wasPerformance = conf.performance; + std::cerr << "lsfg-vk: Rereading configuration, as it is no longer valid.\n"; // reread configuration + const std::string file = Utils::getConfigFile(); + const std::string name = Utils::getProcessName(); try { - const std::string file = Utils::getConfigFile(); Config::updateConfig(file); - const std::string name = Utils::getProcessName(); - Config::activeConf = Config::getConfig(name); + conf = Config::getConfig(name); } catch (const std::exception& e) { std::cerr << "lsfg-vk: Failed to update configuration, continuing using old:\n"; std::cerr << "- " << e.what() << '\n'; } - if (wasPerformance && !Config::activeConf.performance) - LSFG_3_1P::finalize(); - if (!wasPerformance && Config::activeConf.performance) - LSFG_3_1::finalize(); + LSFG_3_1P::finalize(); + LSFG_3_1::finalize(); + + // print config + std::cerr << "lsfg-vk: Reloaded configuration for " << name << ":\n"; + if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n'; + std::cerr << " Multiplier: " << conf.multiplier << '\n'; + std::cerr << " Flow Scale: " << conf.flowScale << '\n'; + std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; + std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; } // we could take the format from the swapchain, // but honestly this is safer. diff --git a/src/hooks.cpp b/src/hooks.cpp index 4615aed..4963c82 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -1,18 +1,20 @@ #include "hooks.hpp" #include "common/exception.hpp" +#include "config/config.hpp" #include "utils/utils.hpp" #include "context.hpp" #include "layer.hpp" -#include #include #include #include #include #include +#include #include #include +#include #include #include @@ -248,6 +250,12 @@ namespace { std::vector semaphores(pPresentInfo->waitSemaphoreCount); std::copy_n(pPresentInfo->pWaitSemaphores, semaphores.size(), semaphores.data()); + // ensure config is valid + auto& conf = Config::activeConf; + if (!conf.valid->load(std::memory_order_relaxed)) + return VK_ERROR_OUT_OF_DATE_KHR; + + // present the swapchain res = swapchain.present(deviceInfo, pPresentInfo->pNext, queue, semaphores, *pPresentInfo->pImageIndices); From d6abc006a931702db178519371e2209ccad2735d Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 02:40:24 +0200 Subject: [PATCH 156/253] improve logging in main application for users fixes #47 --- include/utils/benchmark.hpp | 7 +++- include/utils/log.hpp | 74 --------------------------------- src/config/config.cpp | 2 +- src/main.cpp | 80 +++++++++++++++++++++--------------- src/utils/benchmark.cpp | 81 +++++++++++++++++-------------------- src/utils/log.cpp | 50 ----------------------- src/utils/utils.cpp | 10 ++--- 7 files changed, 96 insertions(+), 208 deletions(-) delete mode 100644 include/utils/log.hpp delete mode 100644 src/utils/log.cpp diff --git a/include/utils/benchmark.hpp b/include/utils/benchmark.hpp index f940872..fc7f481 100644 --- a/include/utils/benchmark.hpp +++ b/include/utils/benchmark.hpp @@ -1,10 +1,15 @@ #pragma once +#include + namespace Benchmark { /// /// Run the benchmark. /// - void run(); + /// @param width The width of the benchmark. + /// @param height The height of the benchmark. + /// + [[noreturn]] void run(uint32_t width, uint32_t height); } diff --git a/include/utils/log.hpp b/include/utils/log.hpp deleted file mode 100644 index 6576aa5..0000000 --- a/include/utils/log.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace Log { - - namespace Internal { - extern bool isSetup; - - extern std::set debugModules; - extern bool debugAllModules; - - extern std::ofstream logFile; - extern std::mutex logMutex; - - void setup(); - } - - template - void log(std::string_view color, std::string_view module, - std::format_string fmt, Args&&... args) { - Internal::setup(); - - const std::string prefix = std::format("lsfg-vk({}): ", module); - const std::string message = std::format(fmt, std::forward(args)...); - - const std::lock_guard lock(Internal::logMutex); - std::cerr << color << prefix << message << "\033[0m" << '\n'; - if (Internal::logFile.is_open()) { - Internal::logFile << prefix << message << '\n'; - Internal::logFile.flush(); - } - } - - const std::string_view WHITE = "\033[1;37m"; - const std::string_view YELLOW = "\033[1;33m"; - const std::string_view RED = "\033[1;31m"; - - template - void info(std::string_view module, std::format_string fmt, Args&&... args) { - log(WHITE, module, fmt, std::forward(args)...); - } - - template - void warn(std::string_view module, std::format_string fmt, Args&&... args) { - log(YELLOW, module, fmt, std::forward(args)...); - } - - template - void error(std::string_view module, std::format_string fmt, Args&&... args) { - log(RED, module, fmt, std::forward(args)...); - } - - const std::string_view GRAY = "\033[1;90m"; - -#ifdef LSFG_NO_DEBUG -template -void debug(std::string_view, std::format_string, Args&&...) {} -#else - template - void debug(std::string_view module, std::format_string fmt, Args&&... args) { - Internal::setup(); - if (Internal::debugAllModules || Internal::debugModules.contains(std::string(module))) - log(GRAY, module, fmt, std::forward(args)...); - } -#endif - -} diff --git a/src/config/config.cpp b/src/config/config.cpp index 80d28a0..ee4175b 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -92,7 +92,7 @@ namespace { if (event->len <= 0 || event->mask & IN_IGNORED) continue; - std::string name(reinterpret_cast(event->name)); + const std::string name(reinterpret_cast(event->name)); if (name != filename) continue; diff --git a/src/main.cpp b/src/main.cpp index 7b26d73..b6edace 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,44 +1,19 @@ #include "config/config.hpp" #include "extract/extract.hpp" +#include "utils/benchmark.hpp" #include "utils/utils.hpp" -#include #include +#include #include +#include +#include #include +#include namespace { - /// Check if the library is preloaded or Vulkan loaded. - bool isPreload() { - const char* preload = std::getenv("LD_PRELOAD"); - if (!preload || *preload == '\0') - return false; - const std::string preload_str(preload); - return preload_str.find("liblsfg-vk.so") != std::string::npos; - } - - /// Check if a benchmark is requested. - bool requestedBenchmark() { - const char* benchmark = std::getenv("LSFG_BENCHMARK"); - if (!benchmark || *benchmark == '\0') - return false; - const std::string benchmark_str(benchmark); - return benchmark_str == "1"; - } - __attribute__((constructor)) void lsfgvk_init() { - if (isPreload()) { - if (requestedBenchmark()) { - // TODO: Call benchmark function. - exit(0); - } - - // TODO: health check, maybe? - std::cerr << "lsfg-vk: This library is not meant to be preloaded, unless you are running a benchmark.\n"; - exit(1); - } - - // read configuration (might block) + // read configuration const std::string file = Utils::getConfigFile(); try { Config::loadAndWatchConfig(file); @@ -59,7 +34,7 @@ namespace { // exit silently if not enabled auto& conf = Config::activeConf; - if (!conf.enable) + if (!conf.enable && name != "benchmark") return; // print config @@ -79,5 +54,46 @@ namespace { exit(0); } std::cerr << "lsfg-vk: Shaders extracted successfully.\n"; + + // run benchmark if requested + const char* benchmark_flag = std::getenv("LSFG_BENCHMARK"); + if (!benchmark_flag) + return; + + const std::string resolution(benchmark_flag); + uint32_t width{}; + uint32_t height{}; + try { + const size_t x = resolution.find('x'); + if (x == std::string::npos) + throw std::runtime_error("Unable to find 'x' in benchmark string"); + + const std::string width_str = resolution.substr(0, x); + const std::string height_str = resolution.substr(x + 1); + if (width_str.empty() || height_str.empty()) + throw std::runtime_error("Invalid resolution"); + + const int32_t w = std::stoi(width_str); + const int32_t h = std::stoi(height_str); + if (w < 0 || h < 0) + throw std::runtime_error("Resolution cannot be negative"); + + width = static_cast(w); + height = static_cast(h); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred while trying to parse the resolution, exiting:\n"; + std::cerr << "- " << e.what() << '\n'; + } + + std::thread benchmark([width, height]() { + try { + Benchmark::run(width, height); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred during the benchmark:\n"; + std::cerr << "- " << e.what() << '\n'; + } + }); + benchmark.detach(); + conf.enable = false; } } diff --git a/src/utils/benchmark.cpp b/src/utils/benchmark.cpp index eb86702..0f0d0fd 100644 --- a/src/utils/benchmark.cpp +++ b/src/utils/benchmark.cpp @@ -1,63 +1,47 @@ #include "utils/benchmark.hpp" +#include "config/config.hpp" #include "extract/extract.hpp" #include "extract/trans.hpp" -#include "utils/log.hpp" #include #include #include +#include -#include -#include +#include #include +#include +#include +#include +#include #include #include using namespace Benchmark; -void Benchmark::run() { - // fetch benchmark parameters - const char* lsfgFlowScale = std::getenv("LSFG_FLOW_SCALE"); - const char* lsfgHdr = std::getenv("LSFG_HDR"); - const char* lsfgMultiplier = std::getenv("LSFG_MULTIPLIER"); - const char* lsfgExtentWidth = std::getenv("LSFG_EXTENT_WIDTH"); - const char* lsfgExtentHeight = std::getenv("LSFG_EXTENT_HEIGHT"); - const char* lsfgPerfMode = std::getenv("LSFG_PERF_MODE"); - - const float flowScale = lsfgFlowScale - ? std::stof(lsfgFlowScale) : 1.0F; - const bool isHdr = lsfgHdr - ? *lsfgHdr == '1' : false; - const uint64_t multiplier = lsfgMultiplier - ? std::stoull(std::string(lsfgMultiplier)) : 2; - const uint32_t width = lsfgExtentWidth - ? static_cast(std::stoul(lsfgExtentWidth)) : 1920; - const uint32_t height = lsfgExtentHeight - ? static_cast(std::stoul(lsfgExtentHeight)) : 1080; - const bool perfMode = lsfgPerfMode - ? *lsfgPerfMode == '1' : false; +void Benchmark::run(uint32_t width, uint32_t height) { + const auto& conf = Config::activeConf; auto* lsfgInitialize = LSFG_3_1::initialize; auto* lsfgCreateContext = LSFG_3_1::createContext; auto* lsfgPresentContext = LSFG_3_1::presentContext; - if (perfMode) { + if (conf.performance) { lsfgInitialize = LSFG_3_1P::initialize; lsfgCreateContext = LSFG_3_1P::createContext; lsfgPresentContext = LSFG_3_1P::presentContext; } - Log::info("bench", "Running {}x benchmark with {}x{} extent and flow scale of {} {} HDR", - multiplier, width, height, flowScale, isHdr ? "with" : "without"); - // create the benchmark context const char* lsfgDeviceUUID = std::getenv("LSFG_DEVICE_UUID"); const uint64_t deviceUUID = lsfgDeviceUUID ? std::stoull(std::string(lsfgDeviceUUID), nullptr, 16) : 0x1463ABAC; + setenv("DISABLE_LSFG", "1", 1); // NOLINT + Extract::extractShaders(); lsfgInitialize( deviceUUID, // some magic number if not given - isHdr, 1.0F / flowScale, multiplier - 1, + conf.hdr, 1.0F / conf.flowScale, conf.multiplier - 1, [](const std::string& name) -> std::vector { auto dxbc = Extract::getShader(name); auto spirv = Extract::translateShader(dxbc); @@ -66,21 +50,24 @@ void Benchmark::run() { ); const int32_t ctx = lsfgCreateContext(-1, -1, {}, { .width = width, .height = height }, - isHdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM + conf.hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM ); - Log::info("bench", "Benchmark context created, ready to run"); + unsetenv("DISABLE_LSFG"); // NOLINT // run the benchmark (run 8*n + 1 so the fences are waited on) const auto now = std::chrono::high_resolution_clock::now(); - const uint64_t iterations = (8 * 500) + 1; - for (uint64_t count = 0; count < iterations; count++) { + const uint64_t iterations = 8 * 500UL; + + std::cerr << "lsfg-vk: Benchmark started, running " << iterations << " iterations...\n"; + for (uint64_t count = 0; count < iterations + 1; count++) { lsfgPresentContext(ctx, -1, {}); - if (count % 500 == 0) - Log::info("bench", "{:.2f}% done ({}/{})", - static_cast(count) / static_cast(iterations) * 100.0F, - count + 1, iterations); + if (count % 50 == 0 && count > 0) + std::cerr << "lsfg-vk: " + << std::setprecision(2) << std::fixed + << static_cast(count) / static_cast(iterations) * 100.0F + << "% done (" << count + 1 << "/" << iterations << ")\r"; } const auto then = std::chrono::high_resolution_clock::now(); @@ -89,15 +76,21 @@ void Benchmark::run() { const auto perIteration = static_cast(ms) / static_cast(iterations); - const uint64_t totalGen = (multiplier - 1) * iterations; + const uint64_t totalGen = (conf.multiplier - 1) * iterations; const auto genFps = static_cast(totalGen) / (static_cast(ms) / 1000.0F); - const uint64_t totalFrames = iterations * multiplier; + const uint64_t totalFrames = iterations * conf.multiplier; const auto totalFps = static_cast(totalFrames) / (static_cast(ms) / 1000.0F); - Log::info("bench", "Benchmark completed in {} ms", ms); - Log::info("bench", "Time per iteration: {:.2f} ms", perIteration); - Log::info("bench", "Generation FPS: {:.2f}", genFps); - Log::info("bench", "Final FPS: {:.2f}", totalFps); - Log::info("bench", "Benchmark finished, exiting"); + std::cerr << "lsfg-vk: Benchmark completed in " << ms << " ms\n"; + std::cerr << " Time taken per real frame: " + << std::setprecision(2) << std::fixed << perIteration << " ms\n"; + std::cerr << " Generated " << totalGen << " frames in total at " + << std::setprecision(2) << std::fixed << genFps << " FPS\n"; + std::cerr << " Total of " << totalFrames << " frames presented at " + << std::setprecision(2) << std::fixed << totalFps << " FPS\n"; + + // sleep for a second, then exit + std::this_thread::sleep_for(std::chrono::seconds(1)); + _exit(0); } diff --git a/src/utils/log.cpp b/src/utils/log.cpp deleted file mode 100644 index 225d9f4..0000000 --- a/src/utils/log.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "utils/log.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace Log; - -bool Internal::isSetup{}; - -std::set Internal::debugModules; -bool Internal::debugAllModules{}; - -std::ofstream Internal::logFile; -std::mutex Internal::logMutex; - -void Internal::setup() { - if (isSetup) return; - isSetup = true; - - // open log file - const char* env_log_file = std::getenv("LSFG_LOG_FILE"); - if (env_log_file) { - std::ostringstream filename; - filename << getpid() << "_" << env_log_file; - logFile.open(filename.str(), std::ios::app); - } - - // parse debug modules - const char* env_log_debug = std::getenv("LSFG_LOG_DEBUG"); - if (!env_log_debug) - return; - const std::string debugModulesStr(env_log_debug); - - std::stringstream ss(debugModulesStr); - std::string item; - while (std::getline(ss, item, ',')) { - if (item == "all") { - debugAllModules = true; - return; - } - debugModules.insert(item); - } - } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index f58532e..16b86d9 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -1,5 +1,4 @@ #include "utils/utils.hpp" -#include "utils/log.hpp" #include "common/exception.hpp" #include "layer.hpp" @@ -82,12 +81,8 @@ std::vector Utils::addExtensions(const char* const* extensions, siz [e](const char* extName) { return std::string(extName) == std::string(e); }); - if (it == ext.end()) { - Log::debug("hooks-init", "Adding extension: {}", e); + if (it == ext.end()) ext.push_back(e); - } else { - Log::debug("hooks-init", "Extension {} already present", e); - } } return ext; @@ -214,6 +209,9 @@ void Utils::resetLimitN(const std::string& id) noexcept { /// Get the process name std::string Utils::getProcessName() { + const char* benchmark_flag = std::getenv("LSFG_BENCHMARK"); + if (benchmark_flag) + return "benchmark"; std::array exe{}; const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1); if (exe_len <= 0) From d283a146bc8e988c646ac758466b28011253396f Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 18:22:54 +0200 Subject: [PATCH 157/253] re-fix the mesa bug dumb puppy reintroduces old mesa bug --- src/layer.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/layer.cpp b/src/layer.cpp index d19dabb..9c2694b 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -101,15 +101,13 @@ namespace { if (!success) throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, "Failed to get instance function pointer for vkCreateInstance"); - success &= initInstanceFunc(*pInstance, - "vkCreateDevice", &next_vkCreateDevice); - if (!success) - throw LSFG::vulkan_error(VK_ERROR_INITIALIZATION_FAILED, - "Failed to get instance function pointer for vkCreateDevice"); // NOLINTEND | skip initialization if the layer is disabled - if (!Config::activeConf.enable) - return next_vkCreateInstance(pCreateInfo, pAllocator, pInstance); + if (!Config::activeConf.enable) { + auto res = next_vkCreateInstance(pCreateInfo, pAllocator, pInstance); + initInstanceFunc(*pInstance, "vkCreateDevice", &next_vkCreateDevice); + return res; + } // create instance try { @@ -126,6 +124,8 @@ namespace { success = true; success &= initInstanceFunc(*pInstance, "vkDestroyInstance", &next_vkDestroyInstance); + success &= initInstanceFunc(*pInstance, + "vkCreateDevice", &next_vkCreateDevice); // workaround mesa bug success &= initInstanceFunc(*pInstance, "vkGetPhysicalDeviceQueueFamilyProperties", &next_vkGetPhysicalDeviceQueueFamilyProperties); success &= initInstanceFunc(*pInstance, From 2af8717aa1308487fcb38529d4a749857af13c12 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 18:24:49 +0200 Subject: [PATCH 158/253] properly use dll path --- src/extract/extract.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index a8bfff7..ea9b7f8 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -1,4 +1,5 @@ #include "extract/extract.hpp" +#include "config/config.hpp" #include @@ -93,9 +94,9 @@ namespace { std::string getDllPath() { // overriden path - const char* dllPath = getenv("LSFG_DLL_PATH"); - if (dllPath && *dllPath != '\0') - return{dllPath}; + std::string dllPath = Config::activeConf.dll; + if (!dllPath.empty()) + return dllPath; // home based paths const char* home = getenv("HOME"); const std::string homeStr = home ? home : ""; From bb976ea86376004d076e6864e00bdd66dca02a82 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 18:54:59 +0200 Subject: [PATCH 159/253] default config --- CMakeLists.txt | 6 ++++++ conf.toml | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 conf.toml diff --git a/CMakeLists.txt b/CMakeLists.txt index 38cbcd3..c5b7e6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,3 +88,9 @@ 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) + +set(conf_file "$ENV{HOME}/.config/lsfg-vk/conf.toml") +if(NOT EXISTS "${conf_file}") + install(FILES "${CMAKE_SOURCE_DIR}/conf.toml" + DESTINATION ~/.config/lsfg-vk) +endif() diff --git a/conf.toml b/conf.toml new file mode 100644 index 0000000..8768984 --- /dev/null +++ b/conf.toml @@ -0,0 +1,49 @@ +[global] +# enable/disable lsfg on every game +# enable = true + +# specify where Lossless.dll is stored +# dll = "/games/Lossless Scaling/Lossless.dll" + +# change the fps multiplier +# multiplier = 2 + +# change the flow scale (lower = faster) +# flow_scale = 1.0 + +# toggle performance mode (2x-8x performance increase) +# performance_mode = false + +# enable hdr mode (doesn't support scrgb) +# hdr_mode = false + +# example entry for a game +# [[game]] +# exe = "Game.exe" +# +# enable = true +# dll = "/games/Lossless Scaling/Lossless.dll" +# multiplier = 2 +# flow_scale = 1.0 +# performance_mode = false +# hdr_mode = false + +[[game]] # configure benchmark +exe = "benchmark" +enable = true + +multiplier = 4 +performance_mode = false + +[[game]] # override GenshinImpact.exe +exe = "GenshinImpact.exe" +enable = true + +multiplier = 3 + +[[game]] # override vkcube +exe = "vkcube" +enable = true + +multiplier = 4 +performance_mode = true From f62e658b115a427b7419a1da5e6d79cc1f23e07c Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 23:05:42 +0200 Subject: [PATCH 160/253] check only for partial path --- src/config/config.cpp | 5 ++++- src/utils/utils.cpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/config/config.cpp b/src/config/config.cpp index ee4175b..454fba3 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -207,7 +208,9 @@ Configuration Config::getConfig(std::string_view name) { return globalConf; const auto& games = *gameConfs; - auto it = games.find(std::string(name)); + auto it = std::ranges::find_if(games, [&name](const auto& pair) { + return name.ends_with(pair.first); + }); if (it != games.end()) return it->second; diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 16b86d9..f5205ee 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -217,7 +217,7 @@ std::string Utils::getProcessName() { if (exe_len <= 0) return "Unknown Process"; exe.at(static_cast(exe_len)) = '\0'; - return{basename(exe.data())}; + return{exe.data()}; } /// Get the config file From f60f2cf3133bb1e30b67f7de0066c53d9e585a08 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 23:05:51 +0200 Subject: [PATCH 161/253] speed-up compilation a bit --- .clang-tidy | 10 ++++++++++ framegen/.clang-tidy | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/.clang-tidy b/.clang-tidy index 298590b..b4010e0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -25,3 +25,13 @@ Checks: - "-cppcoreguidelines-pro-type-reinterpret-cast" # allows reinterpret_cast - "-cppcoreguidelines-avoid-non-const-global-variables" - "-cppcoreguidelines-pro-type-union-access" +# disable slow and pointless checks +- "-modernize-use-std-numbers" +- "-modernize-type-traits" +- "-cppcoreguidelines-owning-memory" +- "-cppcoreguidelines-macro-to-enum" +- "-readability-container-contains" +- "-bugprone-reserved-identifier" +- "-bugprone-stringview-nullptr" +- "-bugprone-standalone-empty" +- "-misc-unused-using-decls" diff --git a/framegen/.clang-tidy b/framegen/.clang-tidy index cf513e9..b4010e0 100644 --- a/framegen/.clang-tidy +++ b/framegen/.clang-tidy @@ -14,6 +14,7 @@ Checks: - "-readability-implicit-bool-conversion" - "-readability-magic-numbers" - "-readability-math-missing-parentheses" +- "-readability-named-parameter" - "-bugprone-easily-swappable-parameters" # configure modernization - "modernize-*" @@ -24,3 +25,13 @@ Checks: - "-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" From 0401f724520e56c82685d47ae7875d0395fbea0c Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Thu, 17 Jul 2025 23:15:52 +0200 Subject: [PATCH 162/253] allow hot-reloading disable/enable if enabled at start --- src/hooks.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hooks.cpp b/src/hooks.cpp index 4963c82..88e9427 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -247,15 +247,19 @@ namespace { // NOLINTEND | present the next frame VkResult res{}; // might return VK_SUBOPTIMAL_KHR try { - std::vector semaphores(pPresentInfo->waitSemaphoreCount); - std::copy_n(pPresentInfo->pWaitSemaphores, semaphores.size(), semaphores.data()); - // ensure config is valid auto& conf = Config::activeConf; if (!conf.valid->load(std::memory_order_relaxed)) return VK_ERROR_OUT_OF_DATE_KHR; + // skip if disabled + if (!conf.enable) + return Layer::ovkQueuePresentKHR(queue, pPresentInfo); + // present the swapchain + std::vector semaphores(pPresentInfo->waitSemaphoreCount); + std::copy_n(pPresentInfo->pWaitSemaphores, semaphores.size(), semaphores.data()); + res = swapchain.present(deviceInfo, pPresentInfo->pNext, queue, semaphores, *pPresentInfo->pImageIndices); From a0d6368f4d983dfb62ef852e5cd5e9de0c05e359 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 00:20:58 +0200 Subject: [PATCH 163/253] allow custom environment variables --- include/config/config.hpp | 2 ++ src/config/config.cpp | 58 +++++++++++++++++++++++++++++++++++++++ src/main.cpp | 2 ++ 3 files changed, 62 insertions(+) diff --git a/include/config/config.hpp b/include/config/config.hpp index bc77cc7..c46ff7c 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -14,6 +14,8 @@ namespace Config { bool enable{false}; /// Path to Lossless.dll. std::string dll; + /// Additional environment variables to set. + std::vector> env; /// The frame generation muliplier size_t multiplier{2}; diff --git a/src/config/config.cpp b/src/config/config.cpp index 454fba3..4ac6c2c 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -137,6 +138,61 @@ bool Config::loadAndWatchConfig(const std::string& file) { return true; } +namespace { + /// Turn a string into a VkPresentModeKHR enum value. + VkPresentModeKHR into_present(const std::string& mode, VkPresentModeKHR defaultMode) { + if (mode.empty()) + return defaultMode; + if (mode == "fifo" || mode == "vsync") + return VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR; + if (mode == "mailbox") + return VkPresentModeKHR::VK_PRESENT_MODE_MAILBOX_KHR; + if (mode == "immediate") + return VkPresentModeKHR::VK_PRESENT_MODE_IMMEDIATE_KHR; + return VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR; + } + + /// Parse environment variables from a string. + std::vector> parse_env(const std::string& envs) { + std::vector> vars{}; + const std::string env_str = envs + ' '; + + std::string current{}; + bool escape{false}; + for (const char c : env_str) { + // toggle escape mode + if (c == '\'') { + escape = !escape; + continue; + } + + // parse variable + if (c == ' ' && !escape) { + if (current.empty()) + continue; + + auto eq_pos = current.find('='); + if (eq_pos == std::string::npos) + throw std::runtime_error("Invalid environment variable: " + current); + + std::string key = current.substr(0, eq_pos); + std::string value = current.substr(eq_pos + 1); + if (key.empty() || value.empty()) + throw std::runtime_error("Invalid environment variable: " + current); + + vars.emplace_back(std::move(key), std::move(value)); + + current.clear(); + continue; + } + + current += c; + } + + return vars; + } +} + bool Config::updateConfig(const std::string& file) { globalConf.valid->store(true, std::memory_order_relaxed); if (!std::filesystem::exists(file)) @@ -156,6 +212,7 @@ bool Config::updateConfig(const std::string& file) { const Configuration global{ .enable = toml::find_or(globalTable, "enable", false), .dll = toml::find_or(globalTable, "dll", std::string()), + .env = parse_env(toml::find_or(globalTable, "env", std::string())), .multiplier = toml::find_or(globalTable, "multiplier", size_t(2)), .flowScale = toml::find_or(globalTable, "flow_scale", 1.0F), .performance = toml::find_or(globalTable, "performance_mode", false), @@ -182,6 +239,7 @@ bool Config::updateConfig(const std::string& file) { Configuration game{ .enable = toml::find_or(gameTable, "enable", global.enable), .dll = toml::find_or(gameTable, "dll", global.dll), + .env = parse_env(toml::find_or(gameTable, "env", std::string())), .multiplier = toml::find_or(gameTable, "multiplier", global.multiplier), .flowScale = toml::find_or(gameTable, "flow_scale", global.flowScale), .performance = toml::find_or(gameTable, "performance_mode", global.performance), diff --git a/src/main.cpp b/src/main.cpp index b6edace..8c4118a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,6 +40,8 @@ namespace { // print config std::cerr << "lsfg-vk: Loaded configuration for " << name << ":\n"; if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n'; + for (const auto& [key, value] : conf.env) + std::cerr << " Environment: " << key << "=" << value << '\n'; std::cerr << " Multiplier: " << conf.multiplier << '\n'; std::cerr << " Flow Scale: " << conf.flowScale << '\n'; std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; From ab867ff2720266b0957e1a9c622e0f5f8bb14f97 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 00:21:59 +0200 Subject: [PATCH 164/253] allow hot-reload via multiplier=1 --- src/config/config.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/config/config.cpp b/src/config/config.cpp index 4ac6c2c..f551bb3 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -220,9 +220,14 @@ bool Config::updateConfig(const std::string& file) { .valid = globalConf.valid // use the same validity flag }; + if (global.multiplier == 1) { // jank alarm + global.enable = false; + global.multiplier = 2; + } + // validate global configuration - if (global.multiplier < 2) - throw std::runtime_error("Multiplier cannot be less than 2"); + if (global.multiplier < 1) + throw std::runtime_error("Multiplier cannot be less than 1"); if (global.flowScale < 0.25F || global.flowScale > 1.0F) throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); @@ -247,9 +252,14 @@ bool Config::updateConfig(const std::string& file) { .valid = global.valid // only need a single validity flag }; + if (game.multiplier == 1) { + game.enable = false; + game.multiplier = 2; + } + // validate the configuration - if (game.multiplier < 2) - throw std::runtime_error("Multiplier cannot be less than 2"); + if (game.multiplier < 1) + throw std::runtime_error("Multiplier cannot be less than 1"); if (game.flowScale < 0.25F || game.flowScale > 1.0F) throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); games[exe] = std::move(game); From 8b29b952fd8fd1bbc3e02b3a5e9804f23af38131 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 00:22:10 +0200 Subject: [PATCH 165/253] add experimental flags for present mode and fps limit --- include/config/config.hpp | 12 ++++++++++-- src/config/config.cpp | 11 ++++++++++- src/context.cpp | 1 + src/hooks.cpp | 25 +++++++++++++++++++++---- src/main.cpp | 9 +++++++++ 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/include/config/config.hpp b/include/config/config.hpp index c46ff7c..2ffa67a 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -1,10 +1,13 @@ #pragma once -#include +#include + +#include #include +#include +#include #include #include -#include namespace Config { @@ -26,6 +29,11 @@ namespace Config { /// Whether HDR is enabled bool hdr{false}; + /// Experimental flag for overriding the synchronization method. + VkPresentModeKHR e_present; + /// Experimental flag for limiting the framerate of DXVK games. + uint32_t e_fps_limit; + /// Atomic property to check if the configuration is valid or outdated. std::shared_ptr valid; }; diff --git a/src/config/config.cpp b/src/config/config.cpp index f551bb3..589850f 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -1,6 +1,7 @@ #include "config/config.hpp" #include "common/exception.hpp" +#include #include #include #include @@ -209,7 +210,7 @@ bool Config::updateConfig(const std::string& file) { // parse global configuration const toml::value globalTable = toml::find_or_default(toml, "global"); - const Configuration global{ + Configuration global{ .enable = toml::find_or(globalTable, "enable", false), .dll = toml::find_or(globalTable, "dll", std::string()), .env = parse_env(toml::find_or(globalTable, "env", std::string())), @@ -217,6 +218,10 @@ bool Config::updateConfig(const std::string& file) { .flowScale = toml::find_or(globalTable, "flow_scale", 1.0F), .performance = toml::find_or(globalTable, "performance_mode", false), .hdr = toml::find_or(globalTable, "hdr_mode", false), + .e_present = into_present( + toml::find_or(globalTable, "experimental_present_mode", ""), + VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR), + .e_fps_limit = toml::find_or(globalTable, "experimental_fps_limit", 0U), .valid = globalConf.valid // use the same validity flag }; @@ -249,6 +254,10 @@ bool Config::updateConfig(const std::string& file) { .flowScale = toml::find_or(gameTable, "flow_scale", global.flowScale), .performance = toml::find_or(gameTable, "performance_mode", global.performance), .hdr = toml::find_or(gameTable, "hdr_mode", global.hdr), + .e_present = into_present( + toml::find_or(gameTable, "experimental_present_mode", ""), + global.e_present), + .e_fps_limit = toml::find_or(gameTable, "experimental_fps_limit", global.e_fps_limit), .valid = global.valid // only need a single validity flag }; diff --git a/src/context.cpp b/src/context.cpp index 481b7b4..080e6e8 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -51,6 +51,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, std::cerr << " Flow Scale: " << conf.flowScale << '\n'; std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; + if (conf.e_present != 2) std::cerr << " ! Present Mode: " << conf.e_present << '\n'; } // we could take the format from the swapchain, // but honestly this is safer. diff --git a/src/hooks.cpp b/src/hooks.cpp index 88e9427..c663d40 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -107,6 +107,7 @@ namespace { std::unordered_map swapchains; std::unordered_map swapchainToDeviceTable; + std::unordered_map swapchainToPresent; /// /// Adjust swapchain creation parameters and create a swapchain context. @@ -148,8 +149,8 @@ namespace { createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - // enforce vsync - createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; + // enforce present mode + createInfo.presentMode = Config::activeConf.e_present; // retire potential old swapchain if (pCreateInfo->oldSwapchain) { @@ -163,6 +164,8 @@ namespace { return res; // can't be caused by lsfg-vk (yet) try { + swapchainToPresent.emplace(*pSwapchain, createInfo.presentMode); + // get all swapchain images uint32_t imageCount{}; res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr); @@ -228,7 +231,16 @@ namespace { } auto& swapchain = it3->second; - // enforce vsync | NOLINTBEGIN + // find present mode + auto it4 = swapchainToPresent.find(*pPresentInfo->pSwapchains); + if (it4 == swapchainToPresent.end()) { + Utils::logLimitN("swapMap", 5, + "Swapchain present mode not found in map"); + return Layer::ovkQueuePresentKHR(queue, pPresentInfo); + } + auto& present = it4->second; + + // enforce present mode | NOLINTBEGIN #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" const VkSwapchainPresentModeInfoEXT* presentModeInfo = @@ -237,7 +249,7 @@ namespace { if (presentModeInfo->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT) { for (size_t i = 0; i < presentModeInfo->swapchainCount; i++) const_cast(presentModeInfo->pPresentModes)[i] = - VK_PRESENT_MODE_FIFO_KHR; + present; } presentModeInfo = reinterpret_cast(presentModeInfo->pNext); @@ -252,6 +264,10 @@ namespace { if (!conf.valid->load(std::memory_order_relaxed)) return VK_ERROR_OUT_OF_DATE_KHR; + // ensure present mode is still valid + if (present != conf.e_present) + return VK_ERROR_OUT_OF_DATE_KHR; + // skip if disabled if (!conf.enable) return Layer::ovkQueuePresentKHR(queue, pPresentInfo); @@ -280,6 +296,7 @@ namespace { const VkAllocationCallbacks* pAllocator) noexcept { swapchains.erase(swapchain); swapchainToDeviceTable.erase(swapchain); + swapchainToPresent.erase(swapchain); Layer::ovkDestroySwapchainKHR(device, swapchain, pAllocator); } } diff --git a/src/main.cpp b/src/main.cpp index 8c4118a..6d96f95 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,6 +46,15 @@ namespace { std::cerr << " Flow Scale: " << conf.flowScale << '\n'; std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; + if (conf.e_present != 2) std::cerr << " ! Present Mode: " << conf.e_present << '\n'; + if (conf.e_fps_limit > 0) std::cerr << " ! FPS Limit: " << conf.e_fps_limit << '\n'; + + // update environment variables + unsetenv("MESA_VK_WSI_PRESENT_MODE"); // NOLINT + for (const auto& [key, value] : conf.env) + setenv(key.c_str(), value.c_str(), 1); // NOLINT + if (conf.e_fps_limit > 0) + setenv("DXVK_FRAME_RATE", std::to_string(conf.e_fps_limit).c_str(), 1); // NOLINT // load shaders try { From a457ef5aff21e5462f7122e56c84bb600c5cbeeb Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 02:34:47 +0200 Subject: [PATCH 166/253] store config in executable --- include/config/config.hpp | 6 ++--- conf.toml => include/config/default_conf.hpp | 6 +++++ src/config/config.cpp | 25 +++++++++++--------- 3 files changed, 22 insertions(+), 15 deletions(-) rename conf.toml => include/config/default_conf.hpp (92%) diff --git a/include/config/config.hpp b/include/config/config.hpp index 2ffa67a..4aaeef9 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -45,22 +45,20 @@ namespace Config { /// Load the config file and create a file watcher. /// /// @param file The path to the configuration file. - /// @return Whether a configuration exists or not. /// /// @throws std::runtime_error if an error occurs while loading the configuration file. /// - bool loadAndWatchConfig(const std::string& file); + void loadAndWatchConfig(const std::string& file); /// /// Reread the configuration file while preserving the old configuration /// in case of an error. /// /// @param file The path to the configuration file. - /// @return Whether a configuration exists or not. /// /// @throws std::runtime_error if an error occurs while loading the configuration file. /// - bool updateConfig(const std::string& file); + void updateConfig(const std::string& file); /// /// Get the configuration for a game. diff --git a/conf.toml b/include/config/default_conf.hpp similarity index 92% rename from conf.toml rename to include/config/default_conf.hpp index 8768984..624b7ba 100644 --- a/conf.toml +++ b/include/config/default_conf.hpp @@ -1,3 +1,8 @@ +#pragma once + +#include + +const std::string DEFAULT_CONFIG = R"( [global] # enable/disable lsfg on every game # enable = true @@ -47,3 +52,4 @@ enable = true multiplier = 4 performance_mode = true +)"; diff --git a/src/config/config.cpp b/src/config/config.cpp index 589850f..11b1efe 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -1,6 +1,8 @@ #include "config/config.hpp" #include "common/exception.hpp" +#include "config/default_conf.hpp" + #include #include #include @@ -19,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -119,12 +122,9 @@ namespace { } } -bool Config::loadAndWatchConfig(const std::string& file) { +void Config::loadAndWatchConfig(const std::string& file) { globalConf.valid = std::make_shared(true); - - auto res = updateConfig(file); - if (!res) - return false; + updateConfig(file); // prepare config watcher std::thread([file = file, valid = globalConf.valid]() { @@ -135,8 +135,6 @@ bool Config::loadAndWatchConfig(const std::string& file) { std::cerr << "- " << e.what() << '\n'; } }).detach(); - - return true; } namespace { @@ -194,10 +192,16 @@ namespace { } } -bool Config::updateConfig(const std::string& file) { +void Config::updateConfig(const std::string& file) { globalConf.valid->store(true, std::memory_order_relaxed); - if (!std::filesystem::exists(file)) - return false; + if (!std::filesystem::exists(file)) { + std::cerr << "lsfg-vk: Placing default configuration file at " << file << '\n'; + std::ofstream out(file); + if (!out.is_open()) + throw std::runtime_error("Unable to create configuration file at " + file); + out << DEFAULT_CONFIG; + out.close(); + } // parse config file std::optional parsed; @@ -277,7 +281,6 @@ bool Config::updateConfig(const std::string& file) { // store configurations globalConf = global; gameConfs = std::move(games); - return true; } Configuration Config::getConfig(std::string_view name) { From b5ca50d3d84738c2f4094602a962ebe6bd6a9496 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 13:59:04 +0200 Subject: [PATCH 167/253] add error gui --- .gitmodules | 3 ++ CMakeLists.txt | 9 +++-- include/utils/gui.hpp | 14 +++++++ src/main.cpp | 9 +++-- src/utils/gui.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++ thirdparty/raylib | 1 + 6 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 include/utils/gui.hpp create mode 100644 src/utils/gui.cpp create mode 160000 thirdparty/raylib diff --git a/.gitmodules b/.gitmodules index d5ff308..331e7c8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "thirdparty/toml11"] path = thirdparty/toml11 url = https://github.com/ToruNiina/toml11 +[submodule "thirdparty/raylib"] + path = thirdparty/raylib + url = https://github.com/raysan5/raylib diff --git a/CMakeLists.txt b/CMakeLists.txt index c5b7e6e..42c59c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,14 +12,16 @@ endif() # subprojects add_compile_options(-fPIC -Wno-deprecated-declarations - -Wno-unused-template) + -Wno-unused-template + -Wno-tautological-compare + -Wno-undef) 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/raylib EXCLUDE_FROM_ALL) add_subdirectory(framegen) - # main project project(lsfg-vk VERSION 0.0.1 @@ -43,7 +45,8 @@ set_target_properties(lsfg-vk PROPERTIES target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk PRIVATE - lsfg-vk-framegen pe-parse dxbc toml11 vulkan) + pe-parse dxbc toml11 raylib + lsfg-vk-framegen vulkan) get_target_property(TOML11_INCLUDE_DIRS toml11 INTERFACE_INCLUDE_DIRECTORIES) target_include_directories(lsfg-vk SYSTEM PRIVATE ${TOML11_INCLUDE_DIRS}) diff --git a/include/utils/gui.hpp b/include/utils/gui.hpp new file mode 100644 index 0000000..37295f3 --- /dev/null +++ b/include/utils/gui.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace Utils { + + /// + /// Display error gui and exit + /// + /// @param message The error message to display in the GUI. + /// + [[noreturn]] void showErrorGui(const std::string& message); + +} diff --git a/src/main.cpp b/src/main.cpp index 6d96f95..bb2a0ad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include "config/config.hpp" #include "extract/extract.hpp" #include "utils/benchmark.hpp" +#include "utils/gui.hpp" #include "utils/utils.hpp" #include @@ -20,7 +21,7 @@ namespace { } catch (const std::exception& e) { std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, exiting:\n"; std::cerr << "- " << e.what() << '\n'; - exit(0); + Utils::showErrorGui(e.what()); } const std::string name = Utils::getProcessName(); @@ -29,7 +30,7 @@ namespace { } catch (const std::exception& e) { std::cerr << "lsfg-vk: The configuration for " << name << " is invalid, exiting:\n"; std::cerr << e.what() << '\n'; - exit(0); + Utils::showErrorGui(e.what()); } // exit silently if not enabled @@ -62,7 +63,7 @@ namespace { } catch (const std::exception& e) { std::cerr << "lsfg-vk: An error occurred while trying to extract the shaders, exiting:\n"; std::cerr << "- " << e.what() << '\n'; - exit(0); + Utils::showErrorGui(e.what()); } std::cerr << "lsfg-vk: Shaders extracted successfully.\n"; @@ -94,6 +95,7 @@ namespace { } catch (const std::exception& e) { std::cerr << "lsfg-vk: An error occurred while trying to parse the resolution, exiting:\n"; std::cerr << "- " << e.what() << '\n'; + exit(EXIT_FAILURE); } std::thread benchmark([width, height]() { @@ -102,6 +104,7 @@ namespace { } catch (const std::exception& e) { std::cerr << "lsfg-vk: An error occurred during the benchmark:\n"; std::cerr << "- " << e.what() << '\n'; + exit(EXIT_FAILURE); } }); benchmark.detach(); diff --git a/src/utils/gui.cpp b/src/utils/gui.cpp new file mode 100644 index 0000000..0f0dac5 --- /dev/null +++ b/src/utils/gui.cpp @@ -0,0 +1,86 @@ +#include "utils/gui.hpp" + +#include + +#include +#include +#include + +using namespace Utils; + +namespace { + + void DrawCenteredText(const std::string& message, int y, Color color) { + const int textWidth = MeasureText(message.c_str(), 20); + DrawText(message.c_str(), (GetScreenWidth() - textWidth) / 2, y, 20, color); + } + + int DrawTextBox(std::string message, int x, int y, int width, bool draw) { + while (!message.empty()) { + std::string current = message; + + // take one line at a time + const size_t pos = current.find('\n'); + if (pos != std::string::npos) + current = current.substr(0, pos); + + // if too long, remove word or character + while (MeasureText(message.c_str(), 20) > width) { + const size_t pos = current.find_last_of(' '); + if (pos == std::string::npos) + current = current.substr(0, current.size() - 1); + else + current = current.substr(0, pos); + } + + // remove the current line from the message text + message = message.substr(current.size()); + while (message.starts_with(' ')) + message = message.substr(1); // remove leading space + while (message.starts_with('\n')) + message = message.substr(1); // remove leading newline + + // draw the current line + if (draw) DrawText(current.c_str(), x, y, 20, Color { 198, 173, 173, 255 }); + y += 25; // move down for the next line + } + return y; // return height of the text box + } +} + +void Utils::showErrorGui(const std::string& message) { + const int height = DrawTextBox(message, 10, 60, 780, false); + InitWindow(800, height + 80, "lsfg-vk - Error"); + + SetTargetFPS(24); // cinema frame rate lol + + while (!WindowShouldClose()) { + BeginDrawing(); + + ClearBackground(Color{ 58, 23, 32, 255 }); + DrawCenteredText("lsfg-vk encountered an error", 10, Color{ 225, 115, 115, 255 }); + DrawLine(10, 35, 790, 35, Color{ 218, 87, 87, 255 }); + DrawTextBox(message, 10, 60, 780, true); + + const bool hover = GetMouseY() > height + 30 + && GetMouseY() < height + 70 + && GetMouseX() > 10 + && GetMouseX() < 790; + if (hover) { + DrawRectangleLines(10, height + 30, 780, 40, Color{ 250, 170, 151, 255 }); + DrawCenteredText("Press Escape or click here to close", height + 40, Color{ 253, 180, 170, 255 }); + } else { + DrawRectangleLines(10, height + 30, 780, 40, Color{ 218, 87, 87, 255 }); + DrawCenteredText("Press Escape or click here to close", height + 40, Color{ 225, 115, 115, 255 }); + } + + EndDrawing(); + + if (hover && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + break; + } + + CloseWindow(); + + exit(EXIT_FAILURE); +} diff --git a/thirdparty/raylib b/thirdparty/raylib new file mode 160000 index 0000000..78a0699 --- /dev/null +++ b/thirdparty/raylib @@ -0,0 +1 @@ +Subproject commit 78a06990c7c55ace00a8e6c5afe983457304e645 From 34da212a7412f78aed12ef02c9148932f7f2c177 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 14:04:21 +0200 Subject: [PATCH 168/253] check all shaders are loaded early --- src/extract/extract.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index ea9b7f8..108336a 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -125,6 +125,11 @@ void Extract::extractShaders() { throw std::runtime_error("Unable to read Lossless.dll, is it installed?"); peparse::IterRsrc(dll, on_resource, nullptr); peparse::DestructParsedPE(dll); + + // ensure all shaders are present + for (const auto& [name, idx] : nameIdxTable) + if (shaders().find(idx) == shaders().end()) + throw std::runtime_error("Shader not found: " + name + ".\nIs Lossless Scaling up to date?"); } std::vector Extract::getShader(const std::string& name) { From df37e9356ae8147f85b3dd7e7be46dd72c6df90b Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 14:36:45 +0200 Subject: [PATCH 169/253] allow process picking via comm --- include/config/config.hpp | 3 +-- include/utils/utils.hpp | 2 +- src/context.cpp | 4 ++-- src/extract/extract.cpp | 2 +- src/main.cpp | 8 ++++---- src/utils/gui.cpp | 2 ++ src/utils/utils.cpp | 21 +++++++++++++++++---- 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/include/config/config.hpp b/include/config/config.hpp index 4aaeef9..f614e7c 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -2,7 +2,6 @@ #include -#include #include #include #include @@ -68,6 +67,6 @@ namespace Config { /// /// @throws std::runtime_error if the configuration is invalid. /// - Configuration getConfig(std::string_view name); + Configuration getConfig(const std::pair& name); } diff --git a/include/utils/utils.hpp b/include/utils/utils.hpp index 90185de..0dc8605 100644 --- a/include/utils/utils.hpp +++ b/include/utils/utils.hpp @@ -90,7 +90,7 @@ namespace Utils { /// /// @return The name of the process. /// - std::string getProcessName(); + std::pair getProcessName(); /// /// Get the configuration file path. diff --git a/src/context.cpp b/src/context.cpp index 080e6e8..ca16ff1 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -32,7 +32,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, // reread configuration const std::string file = Utils::getConfigFile(); - const std::string name = Utils::getProcessName(); + const auto name = Utils::getProcessName(); try { Config::updateConfig(file); conf = Config::getConfig(name); @@ -45,7 +45,7 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, LSFG_3_1::finalize(); // print config - std::cerr << "lsfg-vk: Reloaded configuration for " << name << ":\n"; + std::cerr << "lsfg-vk: Reloaded configuration for " << name.second << ":\n"; if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n'; std::cerr << " Multiplier: " << conf.multiplier << '\n'; std::cerr << " Flow Scale: " << conf.flowScale << '\n'; diff --git a/src/extract/extract.cpp b/src/extract/extract.cpp index 108336a..35d1635 100644 --- a/src/extract/extract.cpp +++ b/src/extract/extract.cpp @@ -129,7 +129,7 @@ void Extract::extractShaders() { // ensure all shaders are present for (const auto& [name, idx] : nameIdxTable) if (shaders().find(idx) == shaders().end()) - throw std::runtime_error("Shader not found: " + name + ".\nIs Lossless Scaling up to date?"); + throw std::runtime_error("Shader not found: " + name + ".\n- Is Lossless Scaling up to date?"); } std::vector Extract::getShader(const std::string& name) { diff --git a/src/main.cpp b/src/main.cpp index bb2a0ad..de51b22 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,22 +24,22 @@ namespace { Utils::showErrorGui(e.what()); } - const std::string name = Utils::getProcessName(); + const auto name = Utils::getProcessName(); try { Config::activeConf = Config::getConfig(name); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: The configuration for " << name << " is invalid, exiting:\n"; + std::cerr << "lsfg-vk: The configuration for " << name.second << " is invalid, exiting:\n"; std::cerr << e.what() << '\n'; Utils::showErrorGui(e.what()); } // exit silently if not enabled auto& conf = Config::activeConf; - if (!conf.enable && name != "benchmark") + if (!conf.enable && name.second != "benchmark") return; // print config - std::cerr << "lsfg-vk: Loaded configuration for " << name << ":\n"; + std::cerr << "lsfg-vk: Loaded configuration for " << name.second << ":\n"; if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n'; for (const auto& [key, value] : conf.env) std::cerr << " Environment: " << key << "=" << value << '\n'; diff --git a/src/utils/gui.cpp b/src/utils/gui.cpp index 0f0dac5..a09c7cf 100644 --- a/src/utils/gui.cpp +++ b/src/utils/gui.cpp @@ -49,6 +49,8 @@ namespace { } void Utils::showErrorGui(const std::string& message) { + SetTraceLogLevel(LOG_WARNING); + const int height = DrawTextBox(message, 10, 60, 780, false); InitWindow(800, height + 80, "lsfg-vk - Error"); diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index f5205ee..33889ea 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -208,16 +209,28 @@ void Utils::resetLimitN(const std::string& id) noexcept { } /// Get the process name -std::string Utils::getProcessName() { +std::pair Utils::getProcessName() { const char* benchmark_flag = std::getenv("LSFG_BENCHMARK"); if (benchmark_flag) - return "benchmark"; + return { "benchmark", "benchmark" }; std::array exe{}; + const ssize_t exe_len = readlink("/proc/self/exe", exe.data(), exe.size() - 1); if (exe_len <= 0) - return "Unknown Process"; + return { "Unknown Process", "unknown" }; exe.at(static_cast(exe_len)) = '\0'; - return{exe.data()}; + + std::ifstream comm_file("/proc/self/comm"); + if (!comm_file.is_open()) + return { std::string(exe.data()), "unknown" }; + std::array comm{}; + comm_file.read(comm.data(), 256); + comm.at(static_cast(comm_file.gcount())) = '\0'; + std::string comm_str(comm.data()); + if (comm_str.back() == '\n') + comm_str.pop_back(); + + return{ std::string(exe.data()), comm_str }; } /// Get the config file From 8140e1ca13f66e56d228cc34f3377626c0d98fb8 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 14:36:52 +0200 Subject: [PATCH 170/253] remove global config --- src/config/config.cpp | 38 ++++++++------------------------------ 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/config/config.cpp b/src/config/config.cpp index 11b1efe..2bf31f5 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -214,29 +213,14 @@ void Config::updateConfig(const std::string& file) { // parse global configuration const toml::value globalTable = toml::find_or_default(toml, "global"); - Configuration global{ - .enable = toml::find_or(globalTable, "enable", false), - .dll = toml::find_or(globalTable, "dll", std::string()), - .env = parse_env(toml::find_or(globalTable, "env", std::string())), - .multiplier = toml::find_or(globalTable, "multiplier", size_t(2)), - .flowScale = toml::find_or(globalTable, "flow_scale", 1.0F), - .performance = toml::find_or(globalTable, "performance_mode", false), - .hdr = toml::find_or(globalTable, "hdr_mode", false), - .e_present = into_present( - toml::find_or(globalTable, "experimental_present_mode", ""), - VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR), - .e_fps_limit = toml::find_or(globalTable, "experimental_fps_limit", 0U), + const Configuration global{ + .dll = toml::find_or(globalTable, "dll", std::string()), .valid = globalConf.valid // use the same validity flag }; - if (global.multiplier == 1) { // jank alarm - global.enable = false; - global.multiplier = 2; - } - // validate global configuration - if (global.multiplier < 1) - throw std::runtime_error("Multiplier cannot be less than 1"); + if (global.multiplier < 2) + throw std::runtime_error("Global Multiplier cannot be less than 2"); if (global.flowScale < 0.25F || global.flowScale > 1.0F) throw std::runtime_error("Flow scale must be between 0.25 and 1.0"); @@ -251,8 +235,7 @@ void Config::updateConfig(const std::string& file) { const std::string exe = toml::find(gameTable, "exe"); Configuration game{ - .enable = toml::find_or(gameTable, "enable", global.enable), - .dll = toml::find_or(gameTable, "dll", global.dll), + .enable = true, .env = parse_env(toml::find_or(gameTable, "env", std::string())), .multiplier = toml::find_or(gameTable, "multiplier", global.multiplier), .flowScale = toml::find_or(gameTable, "flow_scale", global.flowScale), @@ -265,11 +248,6 @@ void Config::updateConfig(const std::string& file) { .valid = global.valid // only need a single validity flag }; - if (game.multiplier == 1) { - game.enable = false; - game.multiplier = 2; - } - // validate the configuration if (game.multiplier < 1) throw std::runtime_error("Multiplier cannot be less than 1"); @@ -283,13 +261,13 @@ void Config::updateConfig(const std::string& file) { gameConfs = std::move(games); } -Configuration Config::getConfig(std::string_view name) { - if (name.empty() || !gameConfs.has_value()) +Configuration Config::getConfig(const std::pair& name) { + if (!gameConfs.has_value()) return globalConf; const auto& games = *gameConfs; auto it = std::ranges::find_if(games, [&name](const auto& pair) { - return name.ends_with(pair.first); + return name.first.ends_with(pair.first) || (name.second == pair.first); }); if (it != games.end()) return it->second; From 06fc427bdd9c2b47e235f5d5f14123d60521fcbb Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 14:41:36 +0200 Subject: [PATCH 171/253] fix using broken globals --- src/config/config.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/config/config.cpp b/src/config/config.cpp index 2bf31f5..c8ec68a 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -138,9 +138,7 @@ void Config::loadAndWatchConfig(const std::string& file) { namespace { /// Turn a string into a VkPresentModeKHR enum value. - VkPresentModeKHR into_present(const std::string& mode, VkPresentModeKHR defaultMode) { - if (mode.empty()) - return defaultMode; + VkPresentModeKHR into_present(const std::string& mode) { if (mode == "fifo" || mode == "vsync") return VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR; if (mode == "mailbox") @@ -237,14 +235,12 @@ void Config::updateConfig(const std::string& file) { Configuration game{ .enable = true, .env = parse_env(toml::find_or(gameTable, "env", std::string())), - .multiplier = toml::find_or(gameTable, "multiplier", global.multiplier), - .flowScale = toml::find_or(gameTable, "flow_scale", global.flowScale), - .performance = toml::find_or(gameTable, "performance_mode", global.performance), - .hdr = toml::find_or(gameTable, "hdr_mode", global.hdr), - .e_present = into_present( - toml::find_or(gameTable, "experimental_present_mode", ""), - global.e_present), - .e_fps_limit = toml::find_or(gameTable, "experimental_fps_limit", global.e_fps_limit), + .multiplier = toml::find_or(gameTable, "multiplier", 2U), + .flowScale = toml::find_or(gameTable, "flow_scale", 1.0F), + .performance = toml::find_or(gameTable, "performance_mode", false), + .hdr = toml::find_or(gameTable, "hdr_mode", false), + .e_present = into_present(toml::find_or(gameTable, "experimental_present_mode", "")), + .e_fps_limit = toml::find_or(gameTable, "experimental_fps_limit", 0U), .valid = global.valid // only need a single validity flag }; From 45736c15ee990c0e0ed8bade73ef7123a0574637 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 15:08:22 +0200 Subject: [PATCH 172/253] fix freeze when swapchain maintenance is enabled --- src/hooks.cpp | 8 ++++++-- src/utils/utils.cpp | 2 -- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/hooks.cpp b/src/hooks.cpp index c663d40..e50c886 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -261,12 +261,16 @@ namespace { try { // ensure config is valid auto& conf = Config::activeConf; - if (!conf.valid->load(std::memory_order_relaxed)) + if (!conf.valid->load(std::memory_order_relaxed)) { + Layer::ovkQueuePresentKHR(queue, pPresentInfo); return VK_ERROR_OUT_OF_DATE_KHR; + } // ensure present mode is still valid - if (present != conf.e_present) + if (present != conf.e_present) { + Layer::ovkQueuePresentKHR(queue, pPresentInfo); return VK_ERROR_OUT_OF_DATE_KHR; + } // skip if disabled if (!conf.enable) diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 33889ea..458c0a0 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -208,7 +208,6 @@ void Utils::resetLimitN(const std::string& id) noexcept { logCounts().erase(id); } -/// Get the process name std::pair Utils::getProcessName() { const char* benchmark_flag = std::getenv("LSFG_BENCHMARK"); if (benchmark_flag) @@ -233,7 +232,6 @@ std::pair Utils::getProcessName() { return{ std::string(exe.data()), comm_str }; } -/// Get the config file std::string Utils::getConfigFile() { const char* configFile = std::getenv("LSFG_CONFIG"); if (configFile && *configFile != '\0') From 54ec01a3a5086679029f7c2daa8b7d58fb28eba7 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 15:14:14 +0200 Subject: [PATCH 173/253] skip setup entirely on 1x multiplier --- include/config/default_conf.hpp | 55 ++++++++++++--------------------- src/context.cpp | 2 ++ src/hooks.cpp | 2 +- 3 files changed, 22 insertions(+), 37 deletions(-) diff --git a/include/config/default_conf.hpp b/include/config/default_conf.hpp index 624b7ba..8d5f0ac 100644 --- a/include/config/default_conf.hpp +++ b/include/config/default_conf.hpp @@ -4,52 +4,35 @@ const std::string DEFAULT_CONFIG = R"( [global] -# enable/disable lsfg on every game -# enable = true +# override the location of Lossless Scaling +# dll = "/games/Lossless Scaling" -# specify where Lossless.dll is stored -# dll = "/games/Lossless Scaling/Lossless.dll" - -# change the fps multiplier -# multiplier = 2 - -# change the flow scale (lower = faster) -# flow_scale = 1.0 - -# toggle performance mode (2x-8x performance increase) -# performance_mode = false - -# enable hdr mode (doesn't support scrgb) -# hdr_mode = false - -# example entry for a game -# [[game]] +# [[game]] # example entry # exe = "Game.exe" +# env = "SteamDeck=0" # -# enable = true -# dll = "/games/Lossless Scaling/Lossless.dll" -# multiplier = 2 -# flow_scale = 1.0 -# performance_mode = false +# multiplier = 3 +# flow_scale = 0.7 +# performance_mode = true # hdr_mode = false +# +# experimental_present_mode = fifo +# experimental_fps_limit = 48 -[[game]] # configure benchmark +[[game]] # default vkcube entry +exe = "vkcube" + +multiplier = 4 +performance_mode = true + +[[game]] # default benchmark entry exe = "benchmark" -enable = true multiplier = 4 performance_mode = false -[[game]] # override GenshinImpact.exe -exe = "GenshinImpact.exe" -enable = true +[[game]] # override Genshin Impact +exe = "Genshin" multiplier = 3 - -[[game]] # override vkcube -exe = "vkcube" -enable = true - -multiplier = 4 -performance_mode = true )"; diff --git a/src/context.cpp b/src/context.cpp index ca16ff1..0f747dc 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -52,6 +52,8 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; if (conf.e_present != 2) std::cerr << " ! Present Mode: " << conf.e_present << '\n'; + + if (conf.multiplier <= 1) return; } // we could take the format from the swapchain, // but honestly this is safer. diff --git a/src/hooks.cpp b/src/hooks.cpp index e50c886..72bf702 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -273,7 +273,7 @@ namespace { } // skip if disabled - if (!conf.enable) + if (conf.multiplier <= 1) return Layer::ovkQueuePresentKHR(queue, pPresentInfo); // present the swapchain From ecd8b4c2176e9541b7d4be0f949b059baf7d6c2e Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 15:18:05 +0200 Subject: [PATCH 174/253] write latest file to tmp --- src/main.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index de51b22..82ef2a1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,7 +4,10 @@ #include "utils/gui.hpp" #include "utils/utils.hpp" +#include + #include +#include #include #include #include @@ -57,6 +60,22 @@ namespace { if (conf.e_fps_limit > 0) setenv("DXVK_FRAME_RATE", std::to_string(conf.e_fps_limit).c_str(), 1); // NOLINT + // write latest file + try { + std::ofstream latest("/tmp/lsfg-vk_last", std::ios::trunc); + if (!latest.is_open()) + throw std::runtime_error("Failed to open /tmp/lsfg-vk_last for writing"); + latest << "exe: " << name.first << '\n'; + latest << "comm: " << name.second << '\n'; + latest << "pid: " << getpid() << '\n'; + if (!latest.good()) + throw std::runtime_error("Failed to write to /tmp/lsfg-vk_last"); + } catch (const std::exception& e) { + std::cerr << "lsfg-vk: An error occurred while trying to write the latest file, exiting:\n"; + std::cerr << "- " << e.what() << '\n'; + Utils::showErrorGui(e.what()); + } + // load shaders try { Extract::extractShaders(); From a7306ed64ae96a0665b060db63b25f1dbb27ce32 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 15:30:08 +0200 Subject: [PATCH 175/253] add config version --- include/config/default_conf.hpp | 2 +- src/config/config.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/config/default_conf.hpp b/include/config/default_conf.hpp index 8d5f0ac..4649e9a 100644 --- a/include/config/default_conf.hpp +++ b/include/config/default_conf.hpp @@ -2,7 +2,7 @@ #include -const std::string DEFAULT_CONFIG = R"( +const std::string DEFAULT_CONFIG = R"(version = 1 [global] # override the location of Lossless Scaling # dll = "/games/Lossless Scaling" diff --git a/src/config/config.cpp b/src/config/config.cpp index c8ec68a..91472bb 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -204,6 +204,10 @@ void Config::updateConfig(const std::string& file) { std::optional parsed; try { parsed.emplace(toml::parse(file)); + if (!parsed->contains("version")) + throw std::runtime_error("Configuration file is missing 'version' field"); + if (parsed->at("version").as_integer() != 1) + throw std::runtime_error("Configuration file version is not supported, expected 1"); } catch (const std::exception& e) { throw LSFG::rethrowable_error("Unable to parse configuration file", e); } From d2062c98ad12938e95158cbf836ba89f8b3692ab Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 16:23:09 +0200 Subject: [PATCH 176/253] update workflows --- .github/workflows/build-arch.yml | 4 ++-- .github/workflows/build-debian.yml | 3 ++- .github/workflows/build-fedora.yml | 4 ++-- .github/workflows/build-ubuntu.yml | 7 +++++-- CMakeLists.txt | 6 ------ 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-arch.yml b/.github/workflows/build-arch.yml index 4c92d1e..78e7eed 100644 --- a/.github/workflows/build-arch.yml +++ b/.github/workflows/build-arch.yml @@ -3,7 +3,6 @@ on: push: branches: ["develop"] - jobs: build-arch: runs-on: ubuntu-latest @@ -21,7 +20,8 @@ jobs: base-devel git \ clang llvm \ cmake ninja \ - spirv-headers vulkan-headers vulkan-icd-loader + spirv-headers vulkan-headers vulkan-icd-loader \ + xorg - name: Checkout repository uses: actions/checkout@v4 with: diff --git a/.github/workflows/build-debian.yml b/.github/workflows/build-debian.yml index ff36867..a85bd2a 100644 --- a/.github/workflows/build-debian.yml +++ b/.github/workflows/build-debian.yml @@ -23,7 +23,8 @@ jobs: clang clang-tools llvm \ git tar \ cmake ninja-build \ - spirv-headers libvulkan-dev + spirv-headers libvulkan-dev \ + xorg-dev xutils-dev - name: Checkout repository uses: actions/checkout@v4 with: diff --git a/.github/workflows/build-fedora.yml b/.github/workflows/build-fedora.yml index a021f18..ad9df25 100644 --- a/.github/workflows/build-fedora.yml +++ b/.github/workflows/build-fedora.yml @@ -3,7 +3,6 @@ on: push: branches: ["develop"] - jobs: build-fedora: runs-on: ubuntu-latest @@ -23,7 +22,8 @@ jobs: git \ clang llvm \ cmake ninja-build \ - spirv-headers-devel vulkan-headers vulkan-loader-devel + spirv-headers-devel vulkan-headers vulkan-loader-devel \ + libX11-devel libXrandr-devel libXinerama-devel libXcursor-devel libXi-devel mesa-libGL-devel - name: Checkout repository uses: actions/checkout@v4 with: diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index b62b4d7..a3250fe 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -3,11 +3,13 @@ on: push: branches: ["develop"] - jobs: build-ubuntu: runs-on: ubuntu-latest steps: + - name: Prepare permissions for apt cache + run: | + sudo chmod a+rwx -R /var/cache/apt/archives - name: Prepare cache for apt packages uses: actions/cache@v4 with: @@ -22,7 +24,8 @@ jobs: clang clang-tools llvm \ git tar \ cmake ninja-build \ - spirv-headers libvulkan-dev + spirv-headers libvulkan-dev \ + xorg-dev xutils-dev - name: Checkout SPIR-V uses: actions/checkout@v4 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 42c59c7..c210607 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,9 +91,3 @@ 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) - -set(conf_file "$ENV{HOME}/.config/lsfg-vk/conf.toml") -if(NOT EXISTS "${conf_file}") - install(FILES "${CMAKE_SOURCE_DIR}/conf.toml" - DESTINATION ~/.config/lsfg-vk) -endif() From 1dcb496cc0a32990c9637165adcffd9d5b055d60 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 16:35:32 +0200 Subject: [PATCH 177/253] add process env var for overriding child processes --- src/utils/utils.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 458c0a0..713480b 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -209,6 +209,10 @@ void Utils::resetLimitN(const std::string& id) noexcept { } std::pair Utils::getProcessName() { + const char* process_name = std::getenv("LSFG_PROCESS"); + if (process_name && *process_name != '\0') + return { process_name, process_name }; + const char* benchmark_flag = std::getenv("LSFG_BENCHMARK"); if (benchmark_flag) return { "benchmark", "benchmark" }; From 420fa191d33d3f598c2c2b2238336fde6bd196ce Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 16:47:23 +0200 Subject: [PATCH 178/253] remove unused definition --- CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c210607..be921ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,10 +5,6 @@ set(CMAKE_CXX_COMPILER clang++) set(CMAKE_SKIP_RPATH ON) -if(CMAKE_BUILD_TYPE STREQUAL "Release") - add_definitions("-DLSFG_NO_DEBUG") -endif() - # subprojects add_compile_options(-fPIC -Wno-deprecated-declarations From 218d82f52ca29fcf87f1da7023f4aa2302e79bf0 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 17:17:15 +0200 Subject: [PATCH 179/253] fix config related bugs --- src/config/config.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/config/config.cpp b/src/config/config.cpp index 91472bb..12f5c7c 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -193,6 +193,11 @@ void Config::updateConfig(const std::string& file) { globalConf.valid->store(true, std::memory_order_relaxed); if (!std::filesystem::exists(file)) { std::cerr << "lsfg-vk: Placing default configuration file at " << file << '\n'; + const auto parent = std::filesystem::path(file).parent_path(); + if (!std::filesystem::exists(parent)) + if (!std::filesystem::create_directories(parent)) + throw std::runtime_error("Unable to create configuration directory at " + parent.string()); + std::ofstream out(file); if (!out.is_open()) throw std::runtime_error("Unable to create configuration file at " + file); @@ -238,6 +243,7 @@ void Config::updateConfig(const std::string& file) { const std::string exe = toml::find(gameTable, "exe"); Configuration game{ .enable = true, + .dll = global.dll, .env = parse_env(toml::find_or(gameTable, "env", std::string())), .multiplier = toml::find_or(gameTable, "multiplier", 2U), .flowScale = toml::find_or(gameTable, "flow_scale", 1.0F), From f261cf40ba253e8b8bf725f780fcdafc0f1b2eb2 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 17:52:03 +0200 Subject: [PATCH 180/253] attempt to fix ubuntu workflow cache --- .github/workflows/build-ubuntu.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index a3250fe..6b8c467 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -58,3 +58,6 @@ jobs: path: | build-release/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json build-release/lib/liblsfg-vk.so + - name: Prepare permissions for apt cache + run: | + sudo chmod a+rwx -R /var/cache/apt/archives From a431803fc96ffc5e8937a38856ca648d509ed0b2 Mon Sep 17 00:00:00 2001 From: Samuel <36420837+Samueru-sama@users.noreply.github.com> Date: Fri, 18 Jul 2025 11:55:53 -0400 Subject: [PATCH 181/253] check `XDG_CONFIG_HOME` for config file --- src/utils/utils.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 713480b..a83e80d 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -240,6 +240,9 @@ std::string Utils::getConfigFile() { const char* configFile = std::getenv("LSFG_CONFIG"); if (configFile && *configFile != '\0') return{configFile}; + const char* xdgPath = std::getenv("XDG_CONFIG_HOME"); + if (xdgPath && *xdgPath != '\0') + return std::string(xdgPath) + "/lsfg-vk/conf.toml"; const char* homePath = std::getenv("HOME"); if (homePath && *homePath != '\0') return std::string(homePath) + "/.config/lsfg-vk/conf.toml"; From 5f033eca0d2d16ea519ef74f0fdc422a71eff56e Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 20:30:08 +0200 Subject: [PATCH 182/253] readd legacy environment variables --- src/config/config.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/config/config.cpp b/src/config/config.cpp index 12f5c7c..13f0e22 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -268,6 +270,37 @@ void Config::updateConfig(const std::string& file) { } Configuration Config::getConfig(const std::pair& name) { + // process legacy environment variables + if (std::getenv("LSFG_LEGACY")) { + Configuration conf{ + .enable = true, + .multiplier = 2, + .flowScale = 1.0F, + .e_present = VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR, + .valid = std::make_shared(true) + }; + + const char* dll = std::getenv("LSFG_DLL_PATH"); + if (dll) conf.dll = std::string(dll); + const char* multiplier = std::getenv("LSFG_MULTIPLIER"); + if (multiplier) conf.multiplier = std::stoul(multiplier); + const char* flow_scale = std::getenv("LSFG_FLOW_SCALE"); + if (flow_scale) conf.flowScale = std::stof(flow_scale); + const char* performance = std::getenv("LSFG_PERFORMANCE_MODE"); + if (performance) conf.performance = std::string(performance) == "1"; + const char* hdr = std::getenv("LSFG_HDR_MODE"); + if (hdr) conf.hdr = std::string(hdr) == "1"; + const char* e_present = std::getenv("LSFG_EXPERIMENTAL_PRESENT_MODE"); + if (e_present) conf.e_present = into_present(std::string(e_present)); + const char* e_fps_limit = std::getenv("LSFG_EXPERIMENTAL_FPS_LIMIT"); + if (e_fps_limit) conf.e_fps_limit = static_cast(std::stoul(e_fps_limit)); + const char* envs = std::getenv("LSFG_ENV"); + if (envs) conf.env = parse_env(std::string(envs)); + + return conf; + } + + // process new configuration system if (!gameConfs.has_value()) return globalConf; From 53b4438a2a567c26d739c856329c1a4d13aa1968 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 20:46:01 +0200 Subject: [PATCH 183/253] remove clang requirement --- .github/workflows/build-arch.yml | 2 +- .github/workflows/build-debian.yml | 2 +- .github/workflows/build-fedora.yml | 2 +- .github/workflows/build-ubuntu.yml | 2 +- CMakeLists.txt | 3 --- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-arch.yml b/.github/workflows/build-arch.yml index 78e7eed..73f1a10 100644 --- a/.github/workflows/build-arch.yml +++ b/.github/workflows/build-arch.yml @@ -28,7 +28,7 @@ jobs: submodules: true - name: Configure with CMake and Ninja run: | - cmake -B build -G Ninja \ + CC=clang CXX=clang++ cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=./build-release - name: Build with Ninja diff --git a/.github/workflows/build-debian.yml b/.github/workflows/build-debian.yml index a85bd2a..5bed5dd 100644 --- a/.github/workflows/build-debian.yml +++ b/.github/workflows/build-debian.yml @@ -31,7 +31,7 @@ jobs: submodules: true - name: Configure with CMake and Ninja run: | - cmake -B build -G Ninja \ + CC=clang CXX=clang++ cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=./build-release - name: Build with Ninja diff --git a/.github/workflows/build-fedora.yml b/.github/workflows/build-fedora.yml index ad9df25..299996c 100644 --- a/.github/workflows/build-fedora.yml +++ b/.github/workflows/build-fedora.yml @@ -30,7 +30,7 @@ jobs: submodules: true - name: Configure with CMake and Ninja run: | - cmake -B build -G Ninja \ + CC=clang CXX=clang++ cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=./build-release - name: Build with Ninja diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 6b8c467..e5c941e 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -42,7 +42,7 @@ jobs: submodules: true - name: Configure with CMake and Ninja run: | - cmake -B build -G Ninja \ + CC=clang CXX=clang++ cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=./build-release - name: Build with Ninja diff --git a/CMakeLists.txt b/CMakeLists.txt index be921ee..80be538 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,5 @@ cmake_minimum_required(VERSION 3.28) -set(CMAKE_C_COMPILER clang) -set(CMAKE_CXX_COMPILER clang++) - set(CMAKE_SKIP_RPATH ON) # subprojects From 15d9864d0b9da30c2096c78ba4381eb5cb3b03e8 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Fri, 18 Jul 2025 20:51:24 +0200 Subject: [PATCH 184/253] remove clang requirement (v2) --- framegen/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/framegen/CMakeLists.txt b/framegen/CMakeLists.txt index 58bcea2..26e6177 100644 --- a/framegen/CMakeLists.txt +++ b/framegen/CMakeLists.txt @@ -1,8 +1,5 @@ cmake_minimum_required(VERSION 3.28) -set(CMAKE_C_COMPILER clang) -set(CMAKE_CXX_COMPILER clang++) - project(lsfg-vk-framegen DESCRIPTION "Lossless Scaling Frame Generation Backend" LANGUAGES CXX) From 977d8618c48e20537e9068e25e7c8ff3ed4223a1 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 19 Jul 2025 09:34:55 +0200 Subject: [PATCH 185/253] fix default configuration --- include/config/default_conf.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/config/default_conf.hpp b/include/config/default_conf.hpp index 4649e9a..2ae5456 100644 --- a/include/config/default_conf.hpp +++ b/include/config/default_conf.hpp @@ -16,7 +16,7 @@ const std::string DEFAULT_CONFIG = R"(version = 1 # performance_mode = true # hdr_mode = false # -# experimental_present_mode = fifo +# experimental_present_mode = "fifo" # experimental_fps_limit = 48 [[game]] # default vkcube entry From 41f81fb832e617bc55ec99f501f36f2c3df34c09 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 19 Jul 2025 09:35:06 +0200 Subject: [PATCH 186/253] exit silently on error --- src/main.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 82ef2a1..2d3afcf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,29 +17,31 @@ namespace { __attribute__((constructor)) void lsfgvk_init() { + std::cerr << std::unitbuf; + // read configuration const std::string file = Utils::getConfigFile(); try { Config::loadAndWatchConfig(file); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, exiting:\n"; + std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, IGNORING:\n"; std::cerr << "- " << e.what() << '\n'; - Utils::showErrorGui(e.what()); + return; // default configuration will unload } const auto name = Utils::getProcessName(); try { Config::activeConf = Config::getConfig(name); } catch (const std::exception& e) { - std::cerr << "lsfg-vk: The configuration for " << name.second << " is invalid, exiting:\n"; + std::cerr << "lsfg-vk: The configuration for " << name.second << " is invalid, IGNORING:\n"; std::cerr << e.what() << '\n'; - Utils::showErrorGui(e.what()); + return; // default configuration will unload } // exit silently if not enabled auto& conf = Config::activeConf; if (!conf.enable && name.second != "benchmark") - return; + return; // default configuration will unload // print config std::cerr << "lsfg-vk: Loaded configuration for " << name.second << ":\n"; From 8b63447d4167a603c60d2b3c48f13163d39c5569 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 19 Jul 2025 12:32:00 +0200 Subject: [PATCH 187/253] workaround weird issue --- src/config/config.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/config/config.cpp b/src/config/config.cpp index 13f0e22..0041afd 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -128,6 +128,9 @@ void Config::loadAndWatchConfig(const std::string& file) { updateConfig(file); // prepare config watcher + if (std::getenv("LSFG_NO_HOT_RELOAD")) + return; + std::thread([file = file, valid = globalConf.valid]() { try { thread(file, valid); From c1413a3ed92a635746e0ab6d28a0ce80209677f4 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 19 Jul 2025 13:08:11 +0200 Subject: [PATCH 188/253] do not multithread --- include/config/config.hpp | 19 +++--- src/config/config.cpp | 120 ++------------------------------------ src/context.cpp | 15 +++-- src/hooks.cpp | 8 ++- src/main.cpp | 2 +- 5 files changed, 30 insertions(+), 134 deletions(-) diff --git a/include/config/config.hpp b/include/config/config.hpp index f614e7c..92057b2 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -2,6 +2,8 @@ #include +#include +#include #include #include #include @@ -33,24 +35,17 @@ namespace Config { /// Experimental flag for limiting the framerate of DXVK games. uint32_t e_fps_limit; - /// Atomic property to check if the configuration is valid or outdated. - std::shared_ptr valid; + /// Path to the configuration file. + std::filesystem::path config_file; + /// File timestamp of the configuration file + std::chrono::time_point timestamp; }; /// Active configuration. Must be set in main.cpp. extern Configuration activeConf; /// - /// Load the config file and create a file watcher. - /// - /// @param file The path to the configuration file. - /// - /// @throws std::runtime_error if an error occurs while loading the configuration file. - /// - void loadAndWatchConfig(const std::string& file); - - /// - /// Reread the configuration file while preserving the old configuration + /// Read the configuration file while preserving the previous configuration /// in case of an error. /// /// @param file The path to the configuration file. diff --git a/src/config/config.cpp b/src/config/config.cpp index 0041afd..659b71a 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -4,14 +4,9 @@ #include "config/default_conf.hpp" #include -#include -#include -#include #include #include #include -#include -#include #include #include @@ -21,19 +16,11 @@ #include #include #include -#include #include #include #include -#include #include -#include -#include -#include -#include -#include #include -#include using namespace Config; @@ -44,103 +31,6 @@ namespace { Configuration Config::activeConf{}; -namespace { - [[noreturn]] void thread( - const std::string& file, - const std::shared_ptr& valid) { - const int fd = inotify_init(); - if (fd < 0) - throw std::runtime_error("Failed to initialize inotify:\n" - "- " + std::string(strerror(errno))); - - const std::string parent = std::filesystem::path(file).parent_path().string(); - const int wd = inotify_add_watch(fd, parent.c_str(), - IN_MODIFY | IN_CLOSE_WRITE | IN_MOVE_SELF); - if (wd < 0) { - close(fd); - - throw std::runtime_error("Failed to add inotify watch for " + parent + ":\n" - "- " + std::string(strerror(errno))); - } - - // watch for changes - std::optional discard_until; - const std::string filename = std::filesystem::path(file).filename().string(); - - std::array buffer{}; - while (true) { - // poll fd - struct pollfd pfd{}; - pfd.fd = fd; - pfd.events = POLLIN; - const int pollRes = poll(&pfd, 1, 100); - if (pollRes < 0 && errno != EINTR) { - inotify_rm_watch(fd, wd); - close(fd); - - throw std::runtime_error("Error polling inotify events:\n" - "- " + std::string(strerror(errno))); - } - - // read fd if there are events - const ssize_t len = pollRes == 0 ? 0 : read(fd, buffer.data(), buffer.size()); - if (len <= 0 && errno != EINTR && pollRes > 0) { - inotify_rm_watch(fd, wd); - close(fd); - - throw std::runtime_error("Error reading inotify events:\n" - "- " + std::string(strerror(errno))); - } - - size_t i{}; - while (std::cmp_less(i, len)) { - auto* event = reinterpret_cast(&buffer.at(i)); - i += sizeof(inotify_event) + event->len; - if (event->len <= 0 || event->mask & IN_IGNORED) - continue; - - const std::string name(reinterpret_cast(event->name)); - if (name != filename) - continue; - - // stall a bit, then mark as invalid - discard_until.emplace(std::chrono::steady_clock::now() - + std::chrono::milliseconds(500)); - } - - auto now = std::chrono::steady_clock::now(); - if (discard_until.has_value() && now > *discard_until) { - discard_until.reset(); - - // mark config as invalid - valid->store(false, std::memory_order_release); - - // and wait until it has been marked as valid again - while (!valid->load(std::memory_order_acquire)) - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - } - } -} - -void Config::loadAndWatchConfig(const std::string& file) { - globalConf.valid = std::make_shared(true); - updateConfig(file); - - // prepare config watcher - if (std::getenv("LSFG_NO_HOT_RELOAD")) - return; - - std::thread([file = file, valid = globalConf.valid]() { - try { - thread(file, valid); - } catch (const std::exception& e) { - std::cerr << "lsfg-vk: Error in config watcher thread:\n"; - std::cerr << "- " << e.what() << '\n'; - } - }).detach(); -} - namespace { /// Turn a string into a VkPresentModeKHR enum value. VkPresentModeKHR into_present(const std::string& mode) { @@ -195,7 +85,6 @@ namespace { } void Config::updateConfig(const std::string& file) { - globalConf.valid->store(true, std::memory_order_relaxed); if (!std::filesystem::exists(file)) { std::cerr << "lsfg-vk: Placing default configuration file at " << file << '\n'; const auto parent = std::filesystem::path(file).parent_path(); @@ -227,7 +116,8 @@ void Config::updateConfig(const std::string& file) { const toml::value globalTable = toml::find_or_default(toml, "global"); const Configuration global{ .dll = toml::find_or(globalTable, "dll", std::string()), - .valid = globalConf.valid // use the same validity flag + .config_file = file, + .timestamp = std::filesystem::last_write_time(file) }; // validate global configuration @@ -256,7 +146,8 @@ void Config::updateConfig(const std::string& file) { .hdr = toml::find_or(gameTable, "hdr_mode", false), .e_present = into_present(toml::find_or(gameTable, "experimental_present_mode", "")), .e_fps_limit = toml::find_or(gameTable, "experimental_fps_limit", 0U), - .valid = global.valid // only need a single validity flag + .config_file = file, + .timestamp = global.timestamp }; // validate the configuration @@ -279,8 +170,7 @@ Configuration Config::getConfig(const std::pair& name) .enable = true, .multiplier = 2, .flowScale = 1.0F, - .e_present = VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR, - .valid = std::make_shared(true) + .e_present = VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR }; const char* dll = std::getenv("LSFG_DLL_PATH"); diff --git a/src/context.cpp b/src/context.cpp index 0f747dc..e00804a 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -11,14 +11,16 @@ #include #include +#include #include #include #include #include -#include -#include -#include #include +#include +#include +#include +#include #include LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, @@ -27,8 +29,13 @@ LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain, extent(extent) { // get updated configuration auto& conf = Config::activeConf; - if (!conf.valid->load(std::memory_order_relaxed)) { + if (!conf.config_file.empty() + && ( + !std::filesystem::exists(conf.config_file) + || conf.timestamp != std::filesystem::last_write_time(conf.config_file) + )) { std::cerr << "lsfg-vk: Rereading configuration, as it is no longer valid.\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // reread configuration const std::string file = Utils::getConfigFile(); diff --git a/src/hooks.cpp b/src/hooks.cpp index 72bf702..a4f7aed 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -8,13 +8,13 @@ #include #include +#include #include #include #include #include #include #include -#include #include #include @@ -261,7 +261,11 @@ namespace { try { // ensure config is valid auto& conf = Config::activeConf; - if (!conf.valid->load(std::memory_order_relaxed)) { + if (!conf.config_file.empty() + && ( + !std::filesystem::exists(conf.config_file) + || conf.timestamp != std::filesystem::last_write_time(conf.config_file) + )) { Layer::ovkQueuePresentKHR(queue, pPresentInfo); return VK_ERROR_OUT_OF_DATE_KHR; } diff --git a/src/main.cpp b/src/main.cpp index 2d3afcf..0e1543c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,7 @@ namespace { // read configuration const std::string file = Utils::getConfigFile(); try { - Config::loadAndWatchConfig(file); + Config::updateConfig(file); } catch (const std::exception& e) { std::cerr << "lsfg-vk: An error occured while trying to parse the configuration, IGNORING:\n"; std::cerr << "- " << e.what() << '\n'; From 13c30c1afe137d4d8bd4193a8ddfa707e277e43e Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 19 Jul 2025 13:23:53 +0200 Subject: [PATCH 189/253] don't return error on swapchain context --- src/hooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks.cpp b/src/hooks.cpp index a4f7aed..bd1368e 100644 --- a/src/hooks.cpp +++ b/src/hooks.cpp @@ -194,7 +194,7 @@ namespace { Utils::logLimitN("swapCtxCreate", 5, "An error occurred while creating the swapchain wrapper:\n" "- " + std::string(e.what())); - return VK_ERROR_INITIALIZATION_FAILED; + return VK_SUCCESS; // swapchain is still valid } return VK_SUCCESS; } From 37e0ffa624b13ae72aeda7db3f39bd8571048c1c Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 19 Jul 2025 14:37:05 +0200 Subject: [PATCH 190/253] add native wayland backend --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 80be538..f51d6a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,10 @@ add_compile_options(-fPIC -Wno-tautological-compare -Wno-undef) +set(BUILD_EXAMPLES OFF) +set(GLFW_BUILD_WAYLAND ON) +set(GLFW_BUILD_X11 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) From 6a86107abab417269df200c49f11ddad287136db Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 19 Jul 2025 14:41:43 +0200 Subject: [PATCH 191/253] remove broken environment variables --- include/config/config.hpp | 7 ----- include/config/default_conf.hpp | 4 +-- src/config/config.cpp | 48 --------------------------------- src/main.cpp | 9 +------ 4 files changed, 2 insertions(+), 66 deletions(-) diff --git a/include/config/config.hpp b/include/config/config.hpp index 92057b2..eef37da 100644 --- a/include/config/config.hpp +++ b/include/config/config.hpp @@ -5,9 +5,6 @@ #include #include #include -#include -#include -#include #include namespace Config { @@ -18,8 +15,6 @@ namespace Config { bool enable{false}; /// Path to Lossless.dll. std::string dll; - /// Additional environment variables to set. - std::vector> env; /// The frame generation muliplier size_t multiplier{2}; @@ -32,8 +27,6 @@ namespace Config { /// Experimental flag for overriding the synchronization method. VkPresentModeKHR e_present; - /// Experimental flag for limiting the framerate of DXVK games. - uint32_t e_fps_limit; /// Path to the configuration file. std::filesystem::path config_file; diff --git a/include/config/default_conf.hpp b/include/config/default_conf.hpp index 2ae5456..d6facc6 100644 --- a/include/config/default_conf.hpp +++ b/include/config/default_conf.hpp @@ -5,11 +5,10 @@ const std::string DEFAULT_CONFIG = R"(version = 1 [global] # override the location of Lossless Scaling -# dll = "/games/Lossless Scaling" +# dll = "/games/Lossless Scaling/Lossless.dll" # [[game]] # example entry # exe = "Game.exe" -# env = "SteamDeck=0" # # multiplier = 3 # flow_scale = 0.7 @@ -17,7 +16,6 @@ const std::string DEFAULT_CONFIG = R"(version = 1 # hdr_mode = false # # experimental_present_mode = "fifo" -# experimental_fps_limit = 48 [[game]] # default vkcube entry exe = "vkcube" diff --git a/src/config/config.cpp b/src/config/config.cpp index 659b71a..ea98a81 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -16,10 +16,8 @@ #include #include #include -#include #include #include -#include #include using namespace Config; @@ -42,46 +40,6 @@ namespace { return VkPresentModeKHR::VK_PRESENT_MODE_IMMEDIATE_KHR; return VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR; } - - /// Parse environment variables from a string. - std::vector> parse_env(const std::string& envs) { - std::vector> vars{}; - const std::string env_str = envs + ' '; - - std::string current{}; - bool escape{false}; - for (const char c : env_str) { - // toggle escape mode - if (c == '\'') { - escape = !escape; - continue; - } - - // parse variable - if (c == ' ' && !escape) { - if (current.empty()) - continue; - - auto eq_pos = current.find('='); - if (eq_pos == std::string::npos) - throw std::runtime_error("Invalid environment variable: " + current); - - std::string key = current.substr(0, eq_pos); - std::string value = current.substr(eq_pos + 1); - if (key.empty() || value.empty()) - throw std::runtime_error("Invalid environment variable: " + current); - - vars.emplace_back(std::move(key), std::move(value)); - - current.clear(); - continue; - } - - current += c; - } - - return vars; - } } void Config::updateConfig(const std::string& file) { @@ -139,13 +97,11 @@ void Config::updateConfig(const std::string& file) { Configuration game{ .enable = true, .dll = global.dll, - .env = parse_env(toml::find_or(gameTable, "env", std::string())), .multiplier = toml::find_or(gameTable, "multiplier", 2U), .flowScale = toml::find_or(gameTable, "flow_scale", 1.0F), .performance = toml::find_or(gameTable, "performance_mode", false), .hdr = toml::find_or(gameTable, "hdr_mode", false), .e_present = into_present(toml::find_or(gameTable, "experimental_present_mode", "")), - .e_fps_limit = toml::find_or(gameTable, "experimental_fps_limit", 0U), .config_file = file, .timestamp = global.timestamp }; @@ -185,10 +141,6 @@ Configuration Config::getConfig(const std::pair& name) if (hdr) conf.hdr = std::string(hdr) == "1"; const char* e_present = std::getenv("LSFG_EXPERIMENTAL_PRESENT_MODE"); if (e_present) conf.e_present = into_present(std::string(e_present)); - const char* e_fps_limit = std::getenv("LSFG_EXPERIMENTAL_FPS_LIMIT"); - if (e_fps_limit) conf.e_fps_limit = static_cast(std::stoul(e_fps_limit)); - const char* envs = std::getenv("LSFG_ENV"); - if (envs) conf.env = parse_env(std::string(envs)); return conf; } diff --git a/src/main.cpp b/src/main.cpp index 0e1543c..ec7916e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,21 +46,14 @@ namespace { // print config std::cerr << "lsfg-vk: Loaded configuration for " << name.second << ":\n"; if (!conf.dll.empty()) std::cerr << " Using DLL from: " << conf.dll << '\n'; - for (const auto& [key, value] : conf.env) - std::cerr << " Environment: " << key << "=" << value << '\n'; std::cerr << " Multiplier: " << conf.multiplier << '\n'; std::cerr << " Flow Scale: " << conf.flowScale << '\n'; std::cerr << " Performance Mode: " << (conf.performance ? "Enabled" : "Disabled") << '\n'; std::cerr << " HDR Mode: " << (conf.hdr ? "Enabled" : "Disabled") << '\n'; if (conf.e_present != 2) std::cerr << " ! Present Mode: " << conf.e_present << '\n'; - if (conf.e_fps_limit > 0) std::cerr << " ! FPS Limit: " << conf.e_fps_limit << '\n'; - // update environment variables + // remove mesa var in favor of config unsetenv("MESA_VK_WSI_PRESENT_MODE"); // NOLINT - for (const auto& [key, value] : conf.env) - setenv(key.c_str(), value.c_str(), 1); // NOLINT - if (conf.e_fps_limit > 0) - setenv("DXVK_FRAME_RATE", std::to_string(conf.e_fps_limit).c_str(), 1); // NOLINT // write latest file try { From dd5190aa680a7543143e724a100bd5d6e9898dd7 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 19 Jul 2025 15:59:04 +0200 Subject: [PATCH 192/253] remove broken workflows (and fix them) --- .github/workflows/build-arch.yml | 2 +- .github/workflows/build-debian.yml | 5 +++-- .github/workflows/build-fedora.yml | 3 ++- .github/workflows/build-ubuntu.yml | 30 ++++++++++-------------------- 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build-arch.yml b/.github/workflows/build-arch.yml index 73f1a10..069e1d6 100644 --- a/.github/workflows/build-arch.yml +++ b/.github/workflows/build-arch.yml @@ -21,7 +21,7 @@ jobs: clang llvm \ cmake ninja \ spirv-headers vulkan-headers vulkan-icd-loader \ - xorg + xorg wayland wayland-protocols mesa-utils - name: Checkout repository uses: actions/checkout@v4 with: diff --git a/.github/workflows/build-debian.yml b/.github/workflows/build-debian.yml index 5bed5dd..3e662d4 100644 --- a/.github/workflows/build-debian.yml +++ b/.github/workflows/build-debian.yml @@ -21,10 +21,11 @@ jobs: apt-get install -y \ build-essential \ clang clang-tools llvm \ - git tar \ + git \ cmake ninja-build \ spirv-headers libvulkan-dev \ - xorg-dev xutils-dev + xorg-dev xutils-dev \ + libwayland-dev libxkbcommon-dev - name: Checkout repository uses: actions/checkout@v4 with: diff --git a/.github/workflows/build-fedora.yml b/.github/workflows/build-fedora.yml index 299996c..029ae21 100644 --- a/.github/workflows/build-fedora.yml +++ b/.github/workflows/build-fedora.yml @@ -23,7 +23,8 @@ jobs: clang llvm \ cmake ninja-build \ spirv-headers-devel vulkan-headers vulkan-loader-devel \ - libX11-devel libXrandr-devel libXinerama-devel libXcursor-devel libXi-devel mesa-libGL-devel + libX11-devel libXrandr-devel libXinerama-devel libXcursor-devel libXi-devel mesa-libGL-devel \ + wayland-devel libxkbcommon-devel - name: Checkout repository uses: actions/checkout@v4 with: diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index e5c941e..d02bd54 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -7,25 +7,18 @@ jobs: build-ubuntu: runs-on: ubuntu-latest steps: - - name: Prepare permissions for apt cache - run: | - sudo chmod a+rwx -R /var/cache/apt/archives - - name: Prepare cache for apt packages - uses: actions/cache@v4 - with: - path: /var/cache/apt/archives - key: ubuntu-apt-cache - name: Install build dependencies - run: | - export DEBIAN_FRONTEND=noninteractive - sudo apt-get update - sudo apt-get install -y \ - build-essential \ - clang clang-tools llvm \ - git tar \ - cmake ninja-build \ - spirv-headers libvulkan-dev \ + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: build-essential + clang clang-tools llvm + git + cmake ninja-build + spirv-headers libvulkan-dev xorg-dev xutils-dev + libwayland-dev libxkbcommon-x11-dev + version: 1.0 + execute_install_scripts: true - name: Checkout SPIR-V uses: actions/checkout@v4 with: @@ -58,6 +51,3 @@ jobs: path: | build-release/share/vulkan/implicit_layer.d/VkLayer_LS_frame_generation.json build-release/lib/liblsfg-vk.so - - name: Prepare permissions for apt cache - run: | - sudo chmod a+rwx -R /var/cache/apt/archives From 1f8b0248071d0db8ebc1104c8a70e46951546056 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 20 Jul 2025 15:51:10 +0200 Subject: [PATCH 193/253] docs: update readme fixes #108 --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 94f0c06..bddfc79 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@ # lsfg-vk This project brings [Lossless Scaling's Frame Generation](https://store.steampowered.com/app/993090/Lossless_Scaling/) to Linux! +>[!NOTE] +> The latest few commits replace the traditional environment system with a config file. Read [this wiki page](https://github.com/PancakeTAS/lsfg-vk/wiki/Configuring-lsfg%E2%80%90vk) for more information. + >[!CAUTION] > **This is a work-in-progress**. While frame generation has worked in quite a few games, compatibility in many games as well as frame pacing issues still need to be fixed. -You can install lsfg-vk by running this in your command line: +You can install lsfg-vk by running this in your command line. Always be careful when copy pasting commands from the internet: ```bash curl -sSf https://pancake.gay/lsfg-vk.sh | sh ``` -Note: This script is extremely primitive and will not work on all distros. It has been written for Arch Linux and its derivations Please see the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki) for more information and join the [Discord](https://discord.gg/losslessscaling) for help (In order to see the linux channels, verify your Steam account.) >[!WARNING] -> Please do not open GitHub issues for anything other than feature requests. Due to the nature of this project, it is much easier to deal with issues through Discord, than GitHub. Use the #linux-reports channel for game compatibility. +> **Please do not open GitHub** issues for anything other than feature requests. Due to the nature of this project, it is much easier to deal with issues through Discord, than GitHub. Use the #linux-reports channel for game compatibility. From 3acde02cc2aa4568522a36b124b462d67dc3b65b Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 20 Jul 2025 17:39:05 +0200 Subject: [PATCH 194/253] update submodule --- thirdparty/dxbc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/dxbc b/thirdparty/dxbc index 04ca5e9..8026542 160000 --- a/thirdparty/dxbc +++ b/thirdparty/dxbc @@ -1 +1 @@ -Subproject commit 04ca5e9ae5fef6c0c65ea72bbaa7375327f11454 +Subproject commit 8026542c3b7ad5b269795dbe63c602708503df0a From a045f4c9b39893caafe7ff4f2cc7c4c1a9eeb835 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 20 Jul 2025 18:17:23 +0200 Subject: [PATCH 195/253] switch to SPIR-V submodule --- .github/workflows/build-arch.yml | 2 +- .github/workflows/build-debian.yml | 2 +- .github/workflows/build-fedora.yml | 2 +- .github/workflows/build-ubuntu.yml | 12 +----------- thirdparty/dxbc | 2 +- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-arch.yml b/.github/workflows/build-arch.yml index 069e1d6..e4c2da6 100644 --- a/.github/workflows/build-arch.yml +++ b/.github/workflows/build-arch.yml @@ -20,7 +20,7 @@ jobs: base-devel git \ clang llvm \ cmake ninja \ - spirv-headers vulkan-headers vulkan-icd-loader \ + vulkan-headers vulkan-icd-loader \ xorg wayland wayland-protocols mesa-utils - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/build-debian.yml b/.github/workflows/build-debian.yml index 3e662d4..ba59570 100644 --- a/.github/workflows/build-debian.yml +++ b/.github/workflows/build-debian.yml @@ -23,7 +23,7 @@ jobs: clang clang-tools llvm \ git \ cmake ninja-build \ - spirv-headers libvulkan-dev \ + libvulkan-dev \ xorg-dev xutils-dev \ libwayland-dev libxkbcommon-dev - name: Checkout repository diff --git a/.github/workflows/build-fedora.yml b/.github/workflows/build-fedora.yml index 029ae21..599f0b4 100644 --- a/.github/workflows/build-fedora.yml +++ b/.github/workflows/build-fedora.yml @@ -22,7 +22,7 @@ jobs: git \ clang llvm \ cmake ninja-build \ - spirv-headers-devel vulkan-headers vulkan-loader-devel \ + vulkan-headers vulkan-loader-devel \ libX11-devel libXrandr-devel libXinerama-devel libXcursor-devel libXi-devel mesa-libGL-devel \ wayland-devel libxkbcommon-devel - name: Checkout repository diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index d02bd54..92eaa05 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -14,21 +14,11 @@ jobs: clang clang-tools llvm git cmake ninja-build - spirv-headers libvulkan-dev + libvulkan-dev xorg-dev xutils-dev libwayland-dev libxkbcommon-x11-dev version: 1.0 execute_install_scripts: true - - name: Checkout SPIR-V - uses: actions/checkout@v4 - with: - repository: 'KhronosGroup/SPIRV-Headers' - ref: 'vulkan-sdk-1.4.313.0' - path: 'spirv-headers' - - name: Build SPIR-V headers - run: | - cmake -S spirv-headers -B spirv-headers/build - sudo cmake --build spirv-headers/build --target install - name: Checkout repository uses: actions/checkout@v4 with: diff --git a/thirdparty/dxbc b/thirdparty/dxbc index 8026542..80e316f 160000 --- a/thirdparty/dxbc +++ b/thirdparty/dxbc @@ -1 +1 @@ -Subproject commit 8026542c3b7ad5b269795dbe63c602708503df0a +Subproject commit 80e316fd13d7e8938d99a08f1f405a0679c3ccfa From b2205f3ee1538864db04dff9953b7a1b1826c54d Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 20 Jul 2025 18:20:23 +0200 Subject: [PATCH 196/253] recursive submodules --- .github/workflows/build-arch.yml | 2 +- .github/workflows/build-debian.yml | 2 +- .github/workflows/build-fedora.yml | 2 +- .github/workflows/build-ubuntu.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-arch.yml b/.github/workflows/build-arch.yml index e4c2da6..58a2a42 100644 --- a/.github/workflows/build-arch.yml +++ b/.github/workflows/build-arch.yml @@ -25,7 +25,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - submodules: true + submodules: 'recursive' - name: Configure with CMake and Ninja run: | CC=clang CXX=clang++ cmake -B build -G Ninja \ diff --git a/.github/workflows/build-debian.yml b/.github/workflows/build-debian.yml index ba59570..a16a316 100644 --- a/.github/workflows/build-debian.yml +++ b/.github/workflows/build-debian.yml @@ -29,7 +29,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - submodules: true + submodules: 'recursive' - name: Configure with CMake and Ninja run: | CC=clang CXX=clang++ cmake -B build -G Ninja \ diff --git a/.github/workflows/build-fedora.yml b/.github/workflows/build-fedora.yml index 599f0b4..2ad5b74 100644 --- a/.github/workflows/build-fedora.yml +++ b/.github/workflows/build-fedora.yml @@ -28,7 +28,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - submodules: true + submodules: 'recursive' - name: Configure with CMake and Ninja run: | CC=clang CXX=clang++ cmake -B build -G Ninja \ diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 92eaa05..f7c2a4a 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - submodules: true + submodules: 'recursive' - name: Configure with CMake and Ninja run: | CC=clang CXX=clang++ cmake -B build -G Ninja \ From 60e2335f860bf23358602378082d12ef8a4ae2ab Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sun, 20 Jul 2025 21:45:39 +0200 Subject: [PATCH 197/253] force clang/clang++ --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f51d6a3..046541d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,8 @@ cmake_minimum_required(VERSION 3.28) +set(CMAKE_CXX_COMPILER clang++) # gcc release build crashes +set(CMAKE_C_COMPILER clang) # feel free to fix :3 + set(CMAKE_SKIP_RPATH ON) # subprojects @@ -42,7 +45,7 @@ set_target_properties(lsfg-vk PROPERTIES target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk PRIVATE - pe-parse dxbc toml11 raylib + pe-parse dxbc toml11 raylib SPIRV-Headers lsfg-vk-framegen vulkan) get_target_property(TOML11_INCLUDE_DIRS toml11 INTERFACE_INCLUDE_DIRECTORIES) From e67fcd3dd832c9d177ad2be780e5dd0e47810bdf Mon Sep 17 00:00:00 2001 From: Twig6943 <119701717+Twig6943@users.noreply.github.com> Date: Sun, 20 Jul 2025 23:24:39 +0300 Subject: [PATCH 198/253] custom flatpak extension (many squashed commits) --------- Co-authored-by: PancakeTAS --- .github/workflows/update-flatpak-23.08.yml | 20 ++++++++++++++ .github/workflows/update-flatpak-24.08.yml | 20 ++++++++++++++ flatpak/VkLayer_LS_frame_generation.patch | 13 +++++++++ ...ktop.Platform.VulkanLayer.lsfgvk_23.08.yml | 27 +++++++++++++++++++ ...ktop.Platform.VulkanLayer.lsfgvk_24.08.yml | 27 +++++++++++++++++++ 5 files changed, 107 insertions(+) create mode 100644 .github/workflows/update-flatpak-23.08.yml create mode 100644 .github/workflows/update-flatpak-24.08.yml create mode 100644 flatpak/VkLayer_LS_frame_generation.patch create mode 100644 flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_23.08.yml create mode 100644 flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_24.08.yml diff --git a/.github/workflows/update-flatpak-23.08.yml b/.github/workflows/update-flatpak-23.08.yml new file mode 100644 index 0000000..8cc0cbe --- /dev/null +++ b/.github/workflows/update-flatpak-23.08.yml @@ -0,0 +1,20 @@ +name: Update lsfg-vk Flatpak (23.08) +on: + push: + branches: ["develop", "flatpak"] + +jobs: + update-flatpak: + runs-on: ubuntu-latest + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-23.08 + options: --privileged + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build Flatpak extension + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: "org.freedesktop.Platform.VulkanLayer.lsfg_vk" + manifest-path: "flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_23.08.yml" + verbose: true diff --git a/.github/workflows/update-flatpak-24.08.yml b/.github/workflows/update-flatpak-24.08.yml new file mode 100644 index 0000000..81aa0f3 --- /dev/null +++ b/.github/workflows/update-flatpak-24.08.yml @@ -0,0 +1,20 @@ +name: Update lsfg-vk Flatpak (24.08) +on: + push: + branches: ["develop", "flatpak"] + +jobs: + update-flatpak: + runs-on: ubuntu-latest + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-24.08 + options: --privileged + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Build Flatpak extension + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: "org.freedesktop.Platform.VulkanLayer.lsfg_vk" + manifest-path: "flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_24.08.yml" + verbose: true diff --git a/flatpak/VkLayer_LS_frame_generation.patch b/flatpak/VkLayer_LS_frame_generation.patch new file mode 100644 index 0000000..b50ed75 --- /dev/null +++ b/flatpak/VkLayer_LS_frame_generation.patch @@ -0,0 +1,13 @@ +diff --git a/VkLayer_LS_frame_generation.json b/VkLayer_LS_frame_generation.json +index ece2a5f..774a027 100644 +--- a/VkLayer_LS_frame_generation.json ++++ b/VkLayer_LS_frame_generation.json +@@ -4,7 +4,7 @@ + "name": "VK_LAYER_LS_frame_generation", + "type": "GLOBAL", + "api_version": "1.4.313", +- "library_path": "../../../lib/liblsfg-vk.so", ++ "library_path": "/usr/lib/extensions/vulkan/lsfgvk/lib/liblsfg-vk.so", + "implementation_version": "1", + "description": "Lossless Scaling frame generation layer", + "functions": { diff --git a/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_23.08.yml b/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_23.08.yml new file mode 100644 index 0000000..72c1041 --- /dev/null +++ b/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_23.08.yml @@ -0,0 +1,27 @@ +id: org.freedesktop.Platform.VulkanLayer.lsfgvk +branch: '23.08' +runtime: org.freedesktop.Platform +runtime-version: '23.08' +sdk: org.freedesktop.Sdk +build-extension: true + +sdk-extensions: + - org.freedesktop.Sdk.Extension.llvm18 + +build-options: + prefix: /usr/lib/extensions/vulkan/lsfgvk + +modules: + - name: lsfg-vk + buildsystem: cmake-ninja + build-options: + append-path: /usr/lib/sdk/llvm18/bin + prepend-ld-library-path: /usr/lib/sdk/llvm18/lib + config-opts: + - -DCMAKE_BUILD_TYPE=Release + sources: + - type: git + url: https://github.com/PancakeTAS/lsfg-vk.git + branch: develop + - type: patch + path: VkLayer_LS_frame_generation.patch diff --git a/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_24.08.yml b/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_24.08.yml new file mode 100644 index 0000000..bf34b02 --- /dev/null +++ b/flatpak/org.freedesktop.Platform.VulkanLayer.lsfgvk_24.08.yml @@ -0,0 +1,27 @@ +id: org.freedesktop.Platform.VulkanLayer.lsfgvk +branch: '24.08' +runtime: org.freedesktop.Platform +runtime-version: '24.08' +sdk: org.freedesktop.Sdk +build-extension: true + +sdk-extensions: + - org.freedesktop.Sdk.Extension.llvm20 + +build-options: + prefix: /usr/lib/extensions/vulkan/lsfgvk + +modules: + - name: lsfg-vk + buildsystem: cmake-ninja + build-options: + append-path: /usr/lib/sdk/llvm20/bin + prepend-ld-library-path: /usr/lib/sdk/llvm20/lib + config-opts: + - -DCMAKE_BUILD_TYPE=Release + sources: + - type: git + url: https://github.com/PancakeTAS/lsfg-vk.git + branch: develop + - type: patch + path: VkLayer_LS_frame_generation.patch From ad180b793e03ee05215c9f46962e0389b1898ef0 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Tue, 22 Jul 2025 13:41:01 +0200 Subject: [PATCH 199/253] upload ui --- .gitignore | 3 + README.md | 2 + ui/Cargo.lock | 952 ++++++++++++++++++++ ui/Cargo.toml | 14 + ui/resources/com.cali666.lsfg-vk-ui.desktop | 13 + ui/resources/icons/lsfg-vk.png | Bin 0 -> 91746 bytes ui/resources/ui.ui | 347 +++++++ ui/src/app_state.rs | 293 ++++++ ui/src/config.rs | 141 +++ ui/src/main.rs | 497 ++++++++++ ui/src/settings_window.rs | 155 ++++ ui/src/utils.rs | 59 ++ 12 files changed, 2476 insertions(+) create mode 100644 ui/Cargo.lock create mode 100644 ui/Cargo.toml create mode 100644 ui/resources/com.cali666.lsfg-vk-ui.desktop create mode 100644 ui/resources/icons/lsfg-vk.png create mode 100644 ui/resources/ui.ui create mode 100644 ui/src/app_state.rs create mode 100644 ui/src/config.rs create mode 100644 ui/src/main.rs create mode 100644 ui/src/settings_window.rs create mode 100644 ui/src/utils.rs diff --git a/.gitignore b/.gitignore index 43ab8ae..682effd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # cmake files /build +# cargo files +/ui/target + # ide/lsp files /.zed /.vscode diff --git a/README.md b/README.md index bddfc79..4fe55c1 100644 --- a/README.md +++ b/README.md @@ -14,5 +14,7 @@ curl -sSf https://pancake.gay/lsfg-vk.sh | sh Please see the [Wiki](https://github.com/PancakeTAS/lsfg-vk/wiki) for more information and join the [Discord](https://discord.gg/losslessscaling) for help (In order to see the linux channels, verify your Steam account.) +Thanks to @Caliel666 for writing the GTK-based gui for lsfg-vk! + >[!WARNING] > **Please do not open GitHub** issues for anything other than feature requests. Due to the nature of this project, it is much easier to deal with issues through Discord, than GitHub. Use the #linux-reports channel for game compatibility. diff --git a/ui/Cargo.lock b/ui/Cargo.lock new file mode 100644 index 0000000..fb3b967 --- /dev/null +++ b/ui/Cargo.lock @@ -0,0 +1,952 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk4" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edb019ad581f8ecf8ea8e4baa6df7c483a95b5a59be3140be6a9c3b0c632af6" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk4-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk4-sys" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbab43f332a3cf1df9974da690b5bb0e26720ed09a228178ce52175372dcfef0" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "graphene-rs" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2228cda1505613a7a956cca69076892cfbda84fc2b7a62b94a41a272c0c401" +dependencies = [ + "glib", + "graphene-sys", + "libc", +] + +[[package]] +name = "graphene-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4144cee8fc8788f2a9b73dc5f1d4e1189d1f95305c4cb7bd9c1af1cfa31f59" +dependencies = [ + "glib-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gsk4" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d958e351d2f210309b32d081c832d7de0aca0b077aa10d88336c6379bd01f7e" +dependencies = [ + "cairo-rs", + "gdk4", + "glib", + "graphene-rs", + "gsk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gsk4-sys" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bd9e3effea989f020e8f1ff3fa3b8c63ba93d43b899c11a118868853a56d55" +dependencies = [ + "cairo-sys-rs", + "gdk4-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk4" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb51aa3e9728575a053e1f43543cd9992ac2477e1b186ad824fd4adfb70842" +dependencies = [ + "cairo-rs", + "field-offset", + "futures-channel", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "graphene-rs", + "gsk4", + "gtk4-macros", + "gtk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gtk4-macros" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d57ec49cf9b657f69a05bca8027cff0a8dfd0c49e812be026fc7311f2163832f" +dependencies = [ + "anyhow", + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "gtk4-sys" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54d8c4aa23638ce9faa2caf7e2a27d4a1295af2155c8e8d28c4d4eeca7a65eb8" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "gsk4-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "libadwaita" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fe7e70c06507ed10a16cda707f358fbe60fe0dc237498f78c686ade92fd979c" +dependencies = [ + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "gtk4", + "libadwaita-sys", + "libc", + "pango", +] + +[[package]] +name = "libadwaita-sys" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e10aaa38de1d53374f90deeb4535209adc40cc5dba37f9704724169bceec69a" +dependencies = [ + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk4-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libredox" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "lsfg-vk-ui" +version = "0.1.3" +dependencies = [ + "directories", + "dirs", + "gtk4", + "libadwaita", + "serde", + "toml", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] diff --git a/ui/Cargo.toml b/ui/Cargo.toml new file mode 100644 index 0000000..3524dea --- /dev/null +++ b/ui/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "lsfg-vk-ui" +version = "0.1.3" +edition = "2021" +authors = ["Cali666"] +description = "Lossless Scaling Frame Generation Configuration Tool" + +[dependencies] +libadwaita = "0.5" +gtk = { version = "0.7", package = "gtk4" } +directories = "5.0" +serde = { version = "1.0", features = ["derive"] } +toml = "0.8" +dirs = "5.0" diff --git a/ui/resources/com.cali666.lsfg-vk-ui.desktop b/ui/resources/com.cali666.lsfg-vk-ui.desktop new file mode 100644 index 0000000..640a161 --- /dev/null +++ b/ui/resources/com.cali666.lsfg-vk-ui.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=LSFG-VK UI +Comment=Lossless Scaling Frame Generation Configuration Tool +Exec=lsfg-vk-ui %U +Icon=com.cali666.lsfg-vk-ui +Terminal=false +Categories=Game;Settings; +Keywords=lossless;scaling;frame;generation;gaming;graphics;configuration; +StartupNotify=true +StartupWMClass=com.cali666.lsfg-vk-ui +MimeType=application/x-lsfg-profile; diff --git a/ui/resources/icons/lsfg-vk.png b/ui/resources/icons/lsfg-vk.png new file mode 100644 index 0000000000000000000000000000000000000000..1c67ee2a50503f34b7bd6afbed09a3f0e4cb1f9d GIT binary patch literal 91746 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%0DRY{s%5jc~Z{Pg8 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
T$^`GHzq(O>E5~*5Ax;sS?4u~jFw@hXmOtor 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 + + + + Lossless Scaling Frame Generation + 800 + 600 + true + + + + + + + Lossless Scaling Frame Generation + + + + + + Settings + settings-icon-button + + + + + + + + horizontal + + + + never + automatic + + + vertical + 0 + sidebar-content + + + LSFG Profiles + 0.0 + 12 + 12 + 6 + title-1 + + + + + browse + navigation-sidebar + + + + + + Create New Profile + 12 + 12 + 12 + suggested-action + + + + + + + + + + vertical + true + true + + + center + + + + + true + true + + + + settings_page + Settings + + + never + + + vertical + 48 + 48 + 32 + 32 + 32 + + + + + Frame Generation + true + 8 + 8 + + + true + + + horizontal + 16 + center + 12 + 12 + 8 + 8 + + + Multiplier + start + true + 0 + + + + + + + + off + 2 + 3 + 4 + + + + 0 + + + + + + + + + true + + + horizontal + 16 + center + 12 + 12 + 8 + 8 + + + Flow Scale + start + true + 0 + + + + + 0.7 + number + + + + + + + + + true + + + horizontal + 16 + center + 12 + 12 + 8 + 8 + + + Performance Mode + start + true + 0 + + + + + true + compact + + + + + + + + + + + + + Misc + true + 8 + 8 + + + true + + + horizontal + 16 + center + 12 + 12 + 8 + 8 + + + HDR Mode + start + true + 0 + + + + + true + compact + + + + + + + + + true + + + horizontal + 16 + center + 12 + 12 + 8 + 8 + + + Experimental Present Mode + start + true + 0 + + + + + + + + vsync + mailbox + immediate + + + + 0 + + + + + + + + + + + + + + + + + + about_page + About + + + vertical + center + center + 24 + + + LSFG-VK UI + title-1 + + + + + Lossless Scaling Frame Generation Configuration Tool + title-4 + true + 50 + + + + + Made by Cali666 • 2025 + dim-label + 24 + + + + + For more information refer to the lsfg-vk wiki + https://github.com/PancakeTAS/lsfg-vk/wiki + 12 + + + + + + + + + + + + + + + + diff --git a/ui/src/app_state.rs b/ui/src/app_state.rs new file mode 100644 index 0000000..552da38 --- /dev/null +++ b/ui/src/app_state.rs @@ -0,0 +1,293 @@ +use gtk::prelude::*; +use gtk::{glib, MessageDialog}; +use libadwaita::ApplicationWindow; +use std::cell::RefCell; +use std::rc::Rc; + +use crate::config::{Config, save_config}; +use crate::utils::round_to_2_decimals; + +#[allow(dead_code)] +pub struct AppState { + pub config: Config, + pub selected_profile_index: Option, + // Store references to the UI widgets for easy access and updates + pub main_window: ApplicationWindow, + pub sidebar_list_box: gtk::ListBox, + pub multiplier_dropdown: gtk::DropDown, + pub flow_scale_entry: gtk::Entry, + pub performance_mode_switch: gtk::Switch, + pub hdr_mode_switch: gtk::Switch, + pub experimental_present_mode_dropdown: gtk::DropDown, + pub save_button: gtk::Button, + pub main_settings_box: gtk::Box, + pub main_stack: gtk::Stack, + // Store SignalHandlerIds to block/unblock signals + pub multiplier_dropdown_handler_id: Option, + pub flow_scale_entry_handler_id: Option, + pub performance_mode_switch_handler_id: Option, + pub hdr_mode_switch_handler_id: Option, + pub experimental_present_mode_dropdown_handler_id: Option, +} + +impl AppState { + // Saves the current configuration to the TOML file + pub fn save_current_config(&self) { + if let Err(e) = save_config(&self.config) { + eprintln!("Failed to save config: {}", e); + // In a real app, you'd show a user-friendly error dialog here + } + } + + // Updates the main window UI with data from the currently selected profile + pub fn update_main_window_from_profile(&self) { + if let Some(index) = self.selected_profile_index { + if let Some(profile) = self.config.game.get(index) { + // Temporarily block signals to prevent re-entrancy + let _guard_mult = self.multiplier_dropdown_handler_id.as_ref().map(|id| self.multiplier_dropdown.block_signal(id)); + let _guard_flow = self.flow_scale_entry_handler_id.as_ref().map(|id| self.flow_scale_entry.block_signal(id)); + let _guard_perf = self.performance_mode_switch_handler_id.as_ref().map(|id| self.performance_mode_switch.block_signal(id)); + let _guard_hdr = self.hdr_mode_switch_handler_id.as_ref().map(|id| self.hdr_mode_switch.block_signal(id)); + let _guard_exp = self.experimental_present_mode_dropdown_handler_id.as_ref().map(|id| self.experimental_present_mode_dropdown.block_signal(id)); + + // Update Multiplier Dropdown + let multiplier_str = match profile.multiplier { + 1 => "off".to_string(), + _ => profile.multiplier.to_string(), + }; + if let Some(pos) = self.multiplier_dropdown.model().and_then(|model| { + let list_model = model.downcast_ref::()?; + (0..list_model.n_items()).find(|&i| list_model.string(i).map_or(false, |s| s.as_str() == multiplier_str)) + }) { + self.multiplier_dropdown.set_selected(pos); + } + + // Update Flow Scale Entry (round to avoid floating point display issues) + let rounded_flow_scale = round_to_2_decimals(profile.flow_scale); + self.flow_scale_entry.set_text(&format!("{:.2}", rounded_flow_scale)); + + // Update Performance Mode Switch + self.performance_mode_switch.set_active(profile.performance_mode); + + // Update HDR Mode Switch + self.hdr_mode_switch.set_active(profile.hdr_mode); + + // Update Experimental Present Mode Dropdown + if let Some(pos) = self.experimental_present_mode_dropdown.model().and_then(|model| { + let list_model = model.downcast_ref::()?; + (0..list_model.n_items()).find(|&i| list_model.string(i).map_or(false, |s| s.as_str() == profile.experimental_present_mode)) + }) { + self.experimental_present_mode_dropdown.set_selected(pos); + } + // Signal handlers are unblocked automatically when _guard_X go out of scope + + // Switch to the settings page + self.main_stack.set_visible_child_name("settings_page"); + + } + } else { + // Clear or disable main window elements if no profile is selected + self.multiplier_dropdown.set_selected(0); // Default to 'off' or first item + self.flow_scale_entry.set_text(""); + self.performance_mode_switch.set_active(false); + self.hdr_mode_switch.set_active(false); + self.experimental_present_mode_dropdown.set_selected(0); // Default to first item + + // Switch to the about page + self.main_stack.set_visible_child_name("about_page"); + } + } + + // Populates sidebar with optional app_state for button handlers + pub fn populate_sidebar_with_handlers(&self, app_state: Option>>) { + // Clear existing rows + while let Some(child) = self.sidebar_list_box.first_child() { + self.sidebar_list_box.remove(&child); + } + + let mut row_to_select: Option = None; + + for (i, profile) in self.config.game.iter().enumerate() { + let row = gtk::ListBoxRow::new(); + + // Create a horizontal box to hold the profile name and buttons + let row_box = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .spacing(8) + .margin_start(12) + .margin_end(12) + .margin_top(8) + .margin_bottom(8) + .build(); + + // Profile name label + let label = gtk::Label::builder() + .label(&profile.exe) + .halign(gtk::Align::Start) + .hexpand(true) + .build(); + + // Edit button + let edit_button = gtk::Button::builder() + .label("🖊") + .css_classes(["flat", "circular"]) + .tooltip_text("Edit profile name") + .build(); + + // Remove button + let remove_button = gtk::Button::builder() + .label("𐄂") + .css_classes(["flat", "circular", "destructive-action"]) + .tooltip_text("Remove profile") + .build(); + + // Add all elements to the row box + row_box.append(&label); + row_box.append(&edit_button); + row_box.append(&remove_button); + + // Connect button handlers if app_state is available + if let Some(app_state_ref) = &app_state { + // Edit button handler + let app_state_clone = app_state_ref.clone(); + let profile_index = i; + edit_button.connect_clicked(move |_| { + let state = app_state_clone.borrow(); + let main_window = &state.main_window; + + // Create edit dialog + let dialog = MessageDialog::new( + Some(main_window), + gtk::DialogFlags::MODAL, + gtk::MessageType::Question, + gtk::ButtonsType::None, + "Edit profile name:", + ); + dialog.set_title(Some("Edit Profile")); + + let entry = gtk::Entry::builder() + .placeholder_text("Profile Name") + .text(&state.config.game[profile_index].exe) + .margin_top(12) + .margin_bottom(12) + .margin_start(12) + .margin_end(12) + .build(); + + dialog.content_area().append(&entry); + dialog.add_button("Cancel", gtk::ResponseType::Cancel); + dialog.add_button("Save", gtk::ResponseType::Other(1)); + dialog.set_default_response(gtk::ResponseType::Other(1)); + + // Allow pressing Enter in the entry to trigger the "Save" button + let dialog_clone = dialog.clone(); + entry.connect_activate(move |_| { + dialog_clone.response(gtk::ResponseType::Other(1)); + }); + + let app_state_clone_dialog = app_state_clone.clone(); + let entry_clone = entry.clone(); + dialog.connect_response(move |d, response| { + if response == gtk::ResponseType::Other(1) { + let new_name = entry_clone.text().to_string(); + if !new_name.is_empty() { + let mut state = app_state_clone_dialog.borrow_mut(); + + // Check if profile with this name already exists (excluding current) + if state.config.game.iter().enumerate().any(|(idx, p)| idx != profile_index && p.exe == new_name) { + let error_dialog = MessageDialog::new( + Some(d), + gtk::DialogFlags::MODAL, + gtk::MessageType::Error, + gtk::ButtonsType::Ok, + "A profile with this name already exists", + ); + error_dialog.set_title(Some("Error")); + error_dialog.connect_response(move |d, _| { d.close(); }); + error_dialog.present(); + return; + } + + // Update profile name + state.config.game[profile_index].exe = new_name; + state.save_current_config(); + state.populate_sidebar_with_handlers(Some(app_state_clone_dialog.clone())); + } + } + d.close(); + }); + dialog.present(); + }); + + // Remove button handler + let app_state_clone = app_state_ref.clone(); + let profile_index = i; + remove_button.connect_clicked(move |_| { + let state = app_state_clone.borrow(); + let main_window = &state.main_window; + let profile_name = &state.config.game[profile_index].exe; + + // Create confirmation dialog + let dialog = MessageDialog::new( + Some(main_window), + gtk::DialogFlags::MODAL, + gtk::MessageType::Warning, + gtk::ButtonsType::None, + &format!("Are you sure you want to remove the profile '{}'?", profile_name), + ); + dialog.set_title(Some("Remove Profile")); + dialog.add_button("Cancel", gtk::ResponseType::Cancel); + dialog.add_button("Remove", gtk::ResponseType::Other(1)); + dialog.set_default_response(gtk::ResponseType::Cancel); + + let app_state_clone_dialog = app_state_clone.clone(); + dialog.connect_response(move |d, response| { + if response == gtk::ResponseType::Other(1) { + let mut state = app_state_clone_dialog.borrow_mut(); + + // Remove the profile + state.config.game.remove(profile_index); + + // Update selected index if needed + if let Some(selected) = state.selected_profile_index { + if selected == profile_index { + // If we removed the selected profile, select the first available or none + state.selected_profile_index = if state.config.game.is_empty() { None } else { Some(0) }; + } else if selected > profile_index { + // Adjust index if we removed a profile before the selected one + state.selected_profile_index = Some(selected - 1); + } + } + + state.save_current_config(); + state.populate_sidebar_with_handlers(Some(app_state_clone_dialog.clone())); + drop(state); + + // Update main window + app_state_clone_dialog.borrow().update_main_window_from_profile(); + } + d.close(); + }); + dialog.present(); + }); + } + + row.set_child(Some(&row_box)); + self.sidebar_list_box.append(&row); + + // Mark the row to be selected later + if self.selected_profile_index == Some(i) { + row_to_select = Some(row.clone()); // Clone the row to store it + } + } + + // Perform selection in a separate idle callback + if let Some(row) = row_to_select { + let list_box_clone = self.sidebar_list_box.clone(); + glib::idle_add_local(move || { + list_box_clone.select_row(Some(&row)); + glib::ControlFlow::Break + }); + } + } +} diff --git a/ui/src/config.rs b/ui/src/config.rs new file mode 100644 index 0000000..7fdde70 --- /dev/null +++ b/ui/src/config.rs @@ -0,0 +1,141 @@ +use serde::{Deserialize, Serialize}; +use std::{fs, io}; +use std::path::PathBuf; +use toml; +use dirs; + +use crate::utils::round_to_2_decimals; // Import from utils module + +// --- Configuration Data Structures --- + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct Config { + pub version: u32, // Made public to be accessible from main.rs + #[serde(flatten)] // Flatten this struct into the parent, controlling order + pub ordered_global: OrderedGlobalConfig, + #[serde(default)] + pub game: Vec, +} + +// Helper struct to control the serialization order of global config +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct OrderedGlobalConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] // Only serialize if Some + pub global: Option, +} + +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct GlobalConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] // Only serialize if Some + pub dll: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(default)] +pub struct GameProfile { + pub exe: String, + pub multiplier: u32, + #[serde(serialize_with = "serialize_flow_scale", deserialize_with = "deserialize_flow_scale")] + pub flow_scale: f32, + pub performance_mode: bool, + pub hdr_mode: bool, + pub experimental_present_mode: String, +} + +// Default values for a new game profile +impl Default for GameProfile { + fn default() -> Self { + GameProfile { + exe: String::new(), + multiplier: 1, // Default to "off" (1) + flow_scale: round_to_2_decimals(0.7), + performance_mode: true, + hdr_mode: false, + experimental_present_mode: "vsync".to_string(), + } + } +} + +// Custom serde functions to ensure flow_scale is always rounded +fn serialize_flow_scale(value: &f32, serializer: S) -> Result +where + S: serde::Serializer, +{ + // Force to 2 decimal places and serialize as a precise decimal + let rounded = round_to_2_decimals(*value); + let formatted = format!("{:.2}", rounded); + let precise_value: f64 = formatted.parse().unwrap_or(*value as f64); + serializer.serialize_f64(precise_value) +} + +fn deserialize_flow_scale<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + use serde::Deserialize; + let value = f64::deserialize(deserializer)?; + Ok(round_to_2_decimals(value as f32)) +} + +// --- Configuration File Handling Functions --- + +pub fn get_config_path() -> Result { + let config_dir = dirs::config_dir() + .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Could not find config directory"))? + .join("lsfg-vk"); + + fs::create_dir_all(&config_dir)?; // Ensure directory exists + println!("Config directory: {:?}", config_dir); + Ok(config_dir.join("conf.toml")) +} + + +pub fn load_config() -> Result { + let config_path = get_config_path()?; + println!("Attempting to load config from: {:?}", config_path); + if config_path.exists() { + let contents = fs::read_to_string(&config_path)?; + println!("Successfully read config contents ({} bytes).", contents.len()); + // Load configuration with default values when the format is invalid + let mut config: Config = toml::from_str(&contents).unwrap_or_else(|_| Config::default()); + + // Old way to load config + // let mut config: Config = toml::from_str(&contents).map_err(|e| { + // io::Error::new( + // io::ErrorKind::InvalidData, + // format!("Failed to parse TOML: {}", e), + // ) + // })?; + + + // Clean up any floating point precision issues in existing configs + let mut needs_save = false; + for profile in &mut config.game { + let original = profile.flow_scale; + profile.flow_scale = round_to_2_decimals(profile.flow_scale); + if (original - profile.flow_scale).abs() > f32::EPSILON { + needs_save = true; + } + } + + // Save the cleaned config if we made changes + if needs_save { + let _ = save_config(&config); + } + + Ok(config) + } else { + println!("Config file not found at {:?}, creating default.", config_path); + Ok(Config { version: 1, ordered_global: OrderedGlobalConfig { global: None }, game: Vec::new() }) + } +} + +pub fn save_config(config: &Config) -> Result<(), io::Error> { + let config_path = get_config_path()?; + println!("Attempting to save config to: {:?}", config_path); + let toml_string = toml::to_string_pretty(config) + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to serialize TOML: {}", e)))?; + fs::write(&config_path, toml_string)?; + println!("Successfully saved config."); + Ok(()) +} diff --git a/ui/src/main.rs b/ui/src/main.rs new file mode 100644 index 0000000..f3d066e --- /dev/null +++ b/ui/src/main.rs @@ -0,0 +1,497 @@ +use gtk::prelude::*; +use gtk::{glib, CssProvider, Builder, Label}; +use libadwaita::ApplicationWindow; +use libadwaita::prelude::AdwApplicationWindowExt; +use std::cell::RefCell; +use std::rc::Rc; + +// Import modules +mod config; +mod app_state; +mod utils; +mod settings_window; + +use config::load_config; +use app_state::AppState; +use utils::round_to_2_decimals; +use config::OrderedGlobalConfig; + +fn main() -> glib::ExitCode { + let application = libadwaita::Application::builder() + .application_id("com.cali666.lsfg-vk-ui") + .build(); + + // Set the desktop file name for proper GNOME integration + glib::set_application_name("LSFG-VK UI"); + glib::set_prgname(Some("lsfg-vk-ui")); + + application.connect_startup(move |_app| { + // Load CSS for sidebar background + let provider = CssProvider::new(); + provider.load_from_data(&format!( + ".settings-icon-button {{ + font-size: 1.4rem; + }} + + .sidebar {{ + background-color: @theme_bg_color; + }} + + .sidebar-content {{ + background-color: shade(@theme_bg_color, {}); + color: @theme_fg_color; + padding: 12px; + }}\n + .linked-button-box {{ + margin-top: 12px; + margin-bottom: 12px; + }}", + 0.95 + )); + gtk::style_context_add_provider_for_display( + >k::gdk::Display::default().expect("Could not connect to a display."), + &provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); + + // Set up icon theme for the application icon + if let Some(display) = gtk::gdk::Display::default() { + let icon_theme = gtk::IconTheme::for_display(&display); + icon_theme.add_resource_path("/com/cali666/lsfg-vk-ui/icons"); + } + }); + + application.connect_activate(move |app| { + // Load initial configuration + let initial_config = load_config().unwrap_or_else(|e| { + eprintln!("Error loading config: {}", e); + // Corrected Config initialization + config::Config { version: 1, ordered_global: OrderedGlobalConfig { global: None }, game: Vec::new() } + }); + + // Load UI from .ui file + let ui_bytes = include_bytes!("../resources/ui.ui"); + let builder = Builder::from_string(std::str::from_utf8(ui_bytes).unwrap()); + + // Get main window and other widgets + let main_window: ApplicationWindow = builder + .object("main_window") + .expect("Could not get main_window from builder"); + main_window.set_application(Some(app)); + + let settings_button: gtk::Button = builder + .object("settings_button") + .expect("Could not get settings_button from builder"); + + // Set application icon for proper dock integration + main_window.set_icon_name(Some("com.cali666.lsfg-vk-ui")); + + let sidebar_list_box: gtk::ListBox = builder + .object("sidebar_list_box") + .expect("Could not get sidebar_list_box from builder"); + let create_profile_button: gtk::Button = builder + .object("create_profile_button") + .expect("Could not get create_profile_button from builder"); + + let multiplier_dropdown: gtk::DropDown = builder + .object("multiplier_dropdown") + .expect("Could not get multiplier_dropdown from builder"); + let flow_scale_entry: gtk::Entry = builder + .object("flow_scale_entry") + .expect("Could not get flow_scale_entry from builder"); + let performance_mode_switch: gtk::Switch = builder + .object("performance_mode_switch") + .expect("Could not get performance_mode_switch from builder"); + let hdr_mode_switch: gtk::Switch = builder + .object("hdr_mode_switch") + .expect("Could not get hdr_mode_switch from builder"); + let experimental_present_mode_dropdown: gtk::DropDown = builder + .object("experimental_present_mode_dropdown") + .expect("Could not get experimental_present_mode_dropdown from builder"); + + let main_stack: gtk::Stack = builder + .object("main_stack") + .expect("Could not get main_stack from builder. Ensure it has id='main_stack' in ui.ui."); + let main_stack_switcher: gtk::StackSwitcher = builder + .object("main_stack_switcher") + .expect("Could not get main_stack_switcher from builder. Ensure it has id='main_stack_switcher' in ui.ui."); + + main_stack_switcher.set_stack(Some(&main_stack)); + + let main_settings_box: gtk::Box = builder + .object("main_box") + .expect("Could not get main_box from builder"); + + let save_button = gtk::Button::builder() + .label("Save Changes") + .halign(gtk::Align::End) + .margin_end(12) + .margin_bottom(12) + .build(); + + main_settings_box.append(&save_button); + + // Initialize application state (with None for handler IDs initially) + let app_state = Rc::new(RefCell::new(AppState { + config: initial_config, + selected_profile_index: None, + main_window: main_window.clone(), + sidebar_list_box: sidebar_list_box.clone(), + multiplier_dropdown: multiplier_dropdown.clone(), + flow_scale_entry: flow_scale_entry.clone(), + performance_mode_switch: performance_mode_switch.clone(), + hdr_mode_switch: hdr_mode_switch.clone(), + experimental_present_mode_dropdown: experimental_present_mode_dropdown.clone(), + save_button: save_button.clone(), + main_settings_box: main_settings_box.clone(), + main_stack: main_stack.clone(), + multiplier_dropdown_handler_id: None, + flow_scale_entry_handler_id: None, + performance_mode_switch_handler_id: None, + hdr_mode_switch_handler_id: None, + experimental_present_mode_dropdown_handler_id: None, + })); + + // --- Connect Signals --- + + // Connect settings button + let main_window_clone = main_window.clone(); + let app_state_clone_for_settings = app_state.clone(); // Clone for settings window + settings_button.connect_clicked(move |_| { + let settings_win = settings_window::create_settings_window(&main_window_clone, app_state_clone_for_settings.clone()); + settings_win.present(); + }); + + let app_state_clone = app_state.clone(); + sidebar_list_box.connect_row_activated(move |_list_box, row| { + let index = row.index() as usize; + let mut state = app_state_clone.borrow_mut(); + state.selected_profile_index = Some(index); + drop(state); + + let app_state_for_idle = app_state_clone.clone(); + glib::idle_add_local(move || { + app_state_for_idle.borrow().update_main_window_from_profile(); + glib::ControlFlow::Break + }); + }); + + let app_state_clone = app_state.clone(); + create_profile_button.connect_clicked(move |_| { + let dialog = gtk::MessageDialog::new( + Some(&app_state_clone.borrow().main_window), + gtk::DialogFlags::MODAL, + gtk::MessageType::Question, + gtk::ButtonsType::None, + "", + ); + dialog.set_title(Some("New Profile")); + dialog.set_secondary_text(Some("Enter or browse Application Name")); + + let entry = gtk::Entry::builder() + .placeholder_text("Application Name") + .hexpand(true) + .build(); + + let pick_process_button = gtk::Button::builder() + .label("🖵") + .tooltip_text("Pick a running Vulkan process") + .css_classes(["flat", "square", "icon-button"]) + .build(); + + let entry_box = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .spacing(6) + .margin_top(12) + .margin_bottom(12) + .margin_start(12) + .margin_end(12) + .build(); + entry_box.append(&entry); + entry_box.append(&pick_process_button); + + dialog.content_area().append(&entry_box); + + dialog.add_button("Cancel", gtk::ResponseType::Cancel); + dialog.add_button("Create", gtk::ResponseType::Other(1)); + + dialog.set_default_response(gtk::ResponseType::Other(1)); + + // Allow pressing Enter in the entry to trigger the "Create" button + let dialog_clone = dialog.clone(); + entry.connect_activate(move |_| { + dialog_clone.response(gtk::ResponseType::Other(1)); + }); + + // --- Process Picker Button Logic --- + let entry_clone_for_picker = entry.clone(); + let main_window_clone_for_picker = app_state_clone.borrow().main_window.clone(); + + pick_process_button.connect_clicked(move |_| { + let process_picker_window = libadwaita::ApplicationWindow::builder() + .title("Select Process") + .transient_for(&main_window_clone_for_picker) + .modal(true) + .default_width(400) + .default_height(600) + .build(); + + let scrolled_window = gtk::ScrolledWindow::builder() + .hscrollbar_policy(gtk::PolicyType::Never) + .vscrollbar_policy(gtk::PolicyType::Automatic) + .hexpand(true) // Make the scrolled window expand horizontally + .vexpand(true) // Make the scrolled window expand vertically + .margin_top(12) + .margin_start(12) + .margin_end(12) + .build(); + + let process_list_box = gtk::ListBox::builder() + .selection_mode(gtk::SelectionMode::Single) + .build(); + scrolled_window.set_child(Some(&process_list_box)); + + let content_box = gtk::Box::builder() + .orientation(gtk::Orientation::Vertical) + .build(); + content_box.append(&scrolled_window); // Add scrolled window first to take up space + + let close_button = gtk::Button::builder() + .label("Close") + .halign(gtk::Align::End) + .margin_end(12) + .margin_bottom(12) + .build(); + content_box.append(&close_button); // Add close button at the bottom + + process_picker_window.set_content(Some(&content_box)); + + // Populate the list with processes + let processes = utils::get_vulkan_processes(); // Call the new function from utils.rs + for proc_name in processes { + let row = gtk::ListBoxRow::new(); + let label = gtk::Label::builder() + .label(&proc_name) + .halign(gtk::Align::Start) + .margin_start(12) + .margin_end(12) + .margin_top(8) + .margin_bottom(8) + .build(); + row.set_child(Some(&label)); + process_list_box.append(&row); + } + + // Connect selection handler + let entry_clone_for_select = entry_clone_for_picker.clone(); + let picker_window_clone = process_picker_window.clone(); + process_list_box.connect_row_activated(move |_list_box, row| { + if let Some(label_widget) = row.child().and_then(|c| c.downcast::().ok()) { + let process_name = label_widget.label().to_string(); + entry_clone_for_select.set_text(&process_name); + picker_window_clone.close(); + } + }); + + // Connect close button + let picker_window_clone_for_close = process_picker_window.clone(); + close_button.connect_clicked(move |_| { + picker_window_clone_for_close.close(); + }); + + process_picker_window.present(); + }); + // --- End Process Picker Button Logic --- + + let app_state_clone_dialog = app_state_clone.clone(); + let entry_clone = entry.clone(); + dialog.connect_response( + move |d: >k::MessageDialog, response: gtk::ResponseType| { + if response == gtk::ResponseType::Other(1) { + let game_name = entry_clone.text().to_string(); + if !game_name.is_empty() { + let mut state = app_state_clone_dialog.borrow_mut(); + + if state.config.game.iter().any(|p| p.exe == game_name) { + let error_dialog = gtk::MessageDialog::new( + Some(d), + gtk::DialogFlags::MODAL, + gtk::MessageType::Error, + gtk::ButtonsType::Ok, + "A profile with this name already exists", + ); + error_dialog.set_title(Some("Error")); + error_dialog.connect_response(move |d, _| { d.close(); }); + error_dialog.present(); + return; + } + + let new_profile = config::GameProfile { + exe: game_name, + ..Default::default() + }; + + state.config.game.push(new_profile); + state.selected_profile_index = Some(state.config.game.len() - 1); + + state.save_current_config(); + + state.populate_sidebar_with_handlers(Some(app_state_clone_dialog.clone())); + drop(state); + + let app_state_for_idle = app_state_clone_dialog.clone(); + glib::idle_add_local(move || { + app_state_for_idle.borrow().update_main_window_from_profile(); + glib::ControlFlow::Break + }); + } + } + d.close(); + } + ); + dialog.present(); + }); + + let app_state_clone_for_handler_mult = app_state.clone(); + let multiplier_handler_id = multiplier_dropdown.connect_selected_item_notify(move |dropdown| { + let mut state = app_state_clone_for_handler_mult.borrow_mut(); + + if let Some(index) = state.selected_profile_index { + if index < state.config.game.len() { + if let Some(profile) = state.config.game.get_mut(index) { + if let Some(item) = dropdown.selected_item() { + if let Some(string_obj) = item.downcast_ref::() { + let text = string_obj.string(); + profile.multiplier = match text.as_str() { + "off" => 1, + _ => text.parse().unwrap_or(1), + }; + } + } + } + } + } + }); + app_state.borrow_mut().multiplier_dropdown_handler_id = Some(multiplier_handler_id); + + let app_state_clone_for_handler_flow = app_state.clone(); + let flow_handler_id = flow_scale_entry.connect_changed(move |entry| { + let mut state = app_state_clone_for_handler_flow.borrow_mut(); + if let Some(index) = state.selected_profile_index { + if let Some(profile) = state.config.game.get_mut(index) { + if let Ok(value) = entry.text().parse::() { + profile.flow_scale = round_to_2_decimals(value); + } + } + } + }); + app_state.borrow_mut().flow_scale_entry_handler_id = Some(flow_handler_id); + + let app_state_clone_for_handler_perf = app_state.clone(); + let perf_handler_id = performance_mode_switch.connect_state_set(move |_sw, active| { + let mut state = app_state_clone_for_handler_perf.borrow_mut(); + if let Some(index) = state.selected_profile_index { + if let Some(profile) = state.config.game.get_mut(index) { + profile.performance_mode = active; + } + } + drop(state); + glib::Propagation::Proceed + }); + app_state.borrow_mut().performance_mode_switch_handler_id = Some(perf_handler_id); + + let app_state_clone_for_handler_hdr = app_state.clone(); + let hdr_handler_id = hdr_mode_switch.connect_state_set(move |_sw, active| { + let mut state = app_state_clone_for_handler_hdr.borrow_mut(); + if let Some(index) = state.selected_profile_index { + if let Some(profile) = state.config.game.get_mut(index) { + profile.hdr_mode = active; + } + } + drop(state); + glib::Propagation::Proceed + }); + app_state.borrow_mut().hdr_mode_switch_handler_id = Some(hdr_handler_id); + + let app_state_clone_for_handler_exp = app_state.clone(); + let exp_handler_id = experimental_present_mode_dropdown.connect_selected_item_notify(move |dropdown| { + let mut state = app_state_clone_for_handler_exp.borrow_mut(); + if let Some(index) = state.selected_profile_index { + if let Some(profile) = state.config.game.get_mut(index) { + let selected_text = dropdown.selected_item().and_then(|item| item.downcast_ref::().map(|s| s.string().to_string())); + if let Some(text) = selected_text { + profile.experimental_present_mode = text; + } + } + } + }); + app_state.borrow_mut().experimental_present_mode_dropdown_handler_id = Some(exp_handler_id); + + let app_state_clone_save = app_state.clone(); + save_button.connect_clicked(move |_| { + let state_ref = app_state_clone_save.borrow(); + if let Some(index) = state_ref.selected_profile_index { + let multiplier_str = state_ref.multiplier_dropdown.selected_item().and_then(|item| item.downcast_ref::().map(|s| s.string().to_string())); + let flow_scale_text = state_ref.flow_scale_entry.text().to_string(); + let performance_mode_active = state_ref.performance_mode_switch.is_active(); + let hdr_mode_active = state_ref.hdr_mode_switch.is_active(); + let exp_mode_str = state_ref.experimental_present_mode_dropdown.selected_item().and_then(|item| item.downcast_ref::().map(|s| s.string().to_string())); + + drop(state_ref); + + let mut state = app_state_clone_save.borrow_mut(); + if let Some(profile) = state.config.game.get_mut(index) { + if let Some(text) = multiplier_str { + profile.multiplier = if text == "off" { 1 } else { text.parse().unwrap_or(1) }; + } + + if let Ok(value) = flow_scale_text.parse::() { + profile.flow_scale = round_to_2_decimals(value); + } + + profile.performance_mode = performance_mode_active; + profile.hdr_mode = hdr_mode_active; + + if let Some(text) = exp_mode_str { + profile.experimental_present_mode = text; + } + + state.save_current_config(); + + let feedback_label = Label::new(Some("Saved!")); + feedback_label.set_halign(gtk::Align::End); + feedback_label.set_margin_end(12); + feedback_label.set_margin_bottom(12); + + let main_settings_box_clone = state.main_settings_box.clone(); + + main_settings_box_clone.append(&feedback_label); + + glib::timeout_add_local(std::time::Duration::new(2, 0), move || { + main_settings_box_clone.remove(&feedback_label); + glib::ControlFlow::Break + }); + } + } + }); + + let app_state_clone_initial = app_state.clone(); + glib::idle_add_local(move || { + let mut state = app_state_clone_initial.borrow_mut(); + if state.config.game.first().is_some() { + state.selected_profile_index = Some(0); + } + state.populate_sidebar_with_handlers(Some(app_state_clone_initial.clone())); + drop(state); + + if app_state_clone_initial.borrow().selected_profile_index.is_some() { + app_state_clone_initial.borrow().update_main_window_from_profile(); + } + glib::ControlFlow::Break + }); + + main_window.present(); + }); + + application.run() +} diff --git a/ui/src/settings_window.rs b/ui/src/settings_window.rs new file mode 100644 index 0000000..73417e7 --- /dev/null +++ b/ui/src/settings_window.rs @@ -0,0 +1,155 @@ +use gtk::prelude::*; +use gtk::{glib, Label, Switch, Entry, Box, Orientation}; +use libadwaita::prelude::*; +use libadwaita::{ApplicationWindow, PreferencesGroup, PreferencesPage, PreferencesWindow, ActionRow}; +use std::rc::Rc; +use std::cell::RefCell; + +use crate::app_state::AppState; + +pub fn create_settings_window(parent: &ApplicationWindow, app_state: Rc>) -> PreferencesWindow { + let settings_window = PreferencesWindow::builder() + .title("Settings") + .transient_for(parent) + .modal(true) + .search_enabled(false) + .default_width(450) // Set default width + .default_height(300) // Set default height + .build(); + + let page = PreferencesPage::builder() + .title("General") + .icon_name("preferences-system-symbolic") + .build(); + + let group = PreferencesGroup::builder() + .title("Global Settings") + .build(); + + // --- Custom DLL Toggle and Path (Programmatically created) --- + let custom_dll_switch = Switch::builder() + .halign(gtk::Align::End) + .valign(gtk::Align::Center) + .build(); + + let custom_dll_path_entry = Entry::builder() + .placeholder_text("/path/to/Lossless.dll") + .hexpand(true) + .build(); + + let custom_dll_row = ActionRow::builder() + .title("Custom Path to Lossless.dll") + .build(); + custom_dll_row.add_suffix(&custom_dll_switch); + custom_dll_row.set_activatable_widget(Some(&custom_dll_switch)); + + let custom_dll_box = Box::builder() + .orientation(Orientation::Vertical) + .spacing(6) + .margin_top(6) + .margin_bottom(6) + .build(); + custom_dll_box.append(&custom_dll_row); + custom_dll_box.append(&custom_dll_path_entry); + + group.add(&custom_dll_box); // Add the box directly to the group + + // Initial state setup for Custom DLL + let current_dll_path = app_state.borrow().config.ordered_global.global.as_ref() + .and_then(|g| g.dll.clone()); + + if let Some(path) = current_dll_path { + custom_dll_switch.set_active(true); + custom_dll_path_entry.set_text(&path); + custom_dll_path_entry.set_visible(true); + } else { + custom_dll_switch.set_active(false); + custom_dll_path_entry.set_visible(false); + } + + // Connect switch to show/hide entry and update config + let app_state_clone_switch = app_state.clone(); + let custom_dll_path_entry_clone = custom_dll_path_entry.clone(); + custom_dll_switch.connect_state_set(move |_sw, active| { + custom_dll_path_entry_clone.set_visible(active); + let mut state = app_state_clone_switch.borrow_mut(); + if active { + // If activating, ensure global config exists and set DLL path + let current_path = custom_dll_path_entry_clone.text().to_string(); + state.config.ordered_global.global.get_or_insert_with(Default::default).dll = Some(current_path); + } else { + // If deactivating, set DLL path to None + if let Some(global_config) = state.config.ordered_global.global.as_mut() { + global_config.dll = None; + } + } + glib::Propagation::Proceed + }); + + // Connect entry to update config + let app_state_clone_entry = app_state.clone(); + let custom_dll_switch_clone = custom_dll_switch.clone(); + custom_dll_path_entry.connect_changed(move |entry| { + let mut state = app_state_clone_entry.borrow_mut(); + if custom_dll_switch_clone.is_active() { + let path = entry.text().to_string(); + if !path.is_empty() { + state.config.ordered_global.global.get_or_insert_with(Default::default).dll = Some(path); + } else { + // If path is cleared, set dll to None + if let Some(global_config) = state.config.ordered_global.global.as_mut() { + global_config.dll = None; + } + } + } + }); + + // Save button for settings + let save_settings_button = gtk::Button::builder() + .label("Save Global Settings") + .halign(gtk::Align::End) + .margin_end(12) + .margin_bottom(12) + .margin_top(12) + .build(); + + // Create a box to hold the feedback label + let feedback_container_box = Box::builder() + .orientation(Orientation::Vertical) + .halign(gtk::Align::End) + .margin_end(12) + .margin_bottom(12) + .build(); + + group.add(&save_settings_button); // Add button first + group.add(&feedback_container_box); // Then add the container for feedback + + let app_state_clone_save = app_state.clone(); + let feedback_container_box_clone = feedback_container_box.clone(); // Clone for the closure + save_settings_button.connect_clicked(move |_| { + let state = app_state_clone_save.borrow_mut(); // Removed 'mut' + state.save_current_config(); + + let feedback_label = Label::new(Some("Saved!")); + feedback_label.set_halign(gtk::Align::End); + feedback_label.set_margin_end(12); + feedback_label.set_margin_bottom(12); + + // Append to the dedicated feedback container box + feedback_container_box_clone.append(&feedback_label); + + glib::timeout_add_local(std::time::Duration::new(2, 0), { + let feedback_label_clone = feedback_label.clone(); // Clone for the timeout + let feedback_container_box_clone_for_remove = feedback_container_box_clone.clone(); // Clone for the timeout + move || { + feedback_container_box_clone_for_remove.remove(&feedback_label_clone); + glib::ControlFlow::Break + } + }); + }); + + page.add(&group); + settings_window.add(&page); + + settings_window +} diff --git a/ui/src/utils.rs b/ui/src/utils.rs new file mode 100644 index 0000000..7fe2612 --- /dev/null +++ b/ui/src/utils.rs @@ -0,0 +1,59 @@ +use std::process::Command; +use std::io::{BufReader, BufRead}; + +pub fn round_to_2_decimals(value: f32) -> f32 { + // Use string formatting to get exactly 2 decimal places and then parse back + // This avoids floating point precision issues + format!("{:.2}", value).parse().unwrap_or(value) +} + +/// Executes a bash command to find running processes that use Vulkan +/// and are owned by the current user. +/// Returns a vector of process names. +pub fn get_vulkan_processes() -> Vec { + let mut processes = Vec::new(); + let command_str = r#" + for pid in /proc/[0-9]*; do + owner=$(stat -c %U "$pid" 2>/dev/null) + if [[ "$owner" == "$USER" ]]; then + if grep -qi 'vulkan' "$pid/maps" 2>/dev/null; then + procname=$(cat "$pid/comm" 2>/dev/null) + if [[ -n "$procname" ]]; then + printf "%s\n" "$procname" # Only print the process name + fi + fi + fi + done + "#; + + // Execute the bash command + let output = Command::new("bash") + .arg("-c") + .arg(command_str) + .output(); + + match output { + Ok(output) => { + if output.status.success() { + // Read stdout line by line + let reader = BufReader::new(output.stdout.as_slice()); + for line in reader.lines() { + if let Ok(proc_name) = line { + let trimmed_name = proc_name.trim().to_string(); + if !trimmed_name.is_empty() { + processes.push(trimmed_name); + } + } + } + } else { + // Print stderr if the command failed + eprintln!("Command failed with error: {}", String::from_utf8_lossy(&output.stderr)); + } + }, + Err(e) => { + // Print error if the command could not be executed + eprintln!("Failed to execute command: {}", e); + } + } + processes +} From c04a224c386a2c012d33aa77cdcefa0b80c0f6c5 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 23 Jul 2025 16:36:11 +0200 Subject: [PATCH 200/253] ui: redrafting the ui --- ui/rsc/resources.gresource.xml | 6 + ui/rsc/window.ui | 265 +++++++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 ui/rsc/resources.gresource.xml create mode 100644 ui/rsc/window.ui diff --git a/ui/rsc/resources.gresource.xml b/ui/rsc/resources.gresource.xml new file mode 100644 index 0000000..bd84eea --- /dev/null +++ b/ui/rsc/resources.gresource.xml @@ -0,0 +1,6 @@ + + + + window.ui + + diff --git a/ui/rsc/window.ui b/ui/rsc/window.ui new file mode 100644 index 0000000..1f7f3db --- /dev/null +++ b/ui/rsc/window.ui @@ -0,0 +1,265 @@ + + + + + From 246aefdbf2261ffc82e356d238c2eea9c8ae38ad Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 23 Jul 2025 18:11:13 +0200 Subject: [PATCH 201/253] extract prefs to templates --- ui/build.rs | 7 + ui/rsc/pref/dropdown.ui | 32 ++++ ui/rsc/pref/entry.ui | 34 ++++ ui/rsc/pref/switch.ui | 31 +++ ui/rsc/resources.gresource.xml | 3 + ui/rsc/window.ui | 334 +++++++++++---------------------- 6 files changed, 220 insertions(+), 221 deletions(-) create mode 100644 ui/build.rs create mode 100644 ui/rsc/pref/dropdown.ui create mode 100644 ui/rsc/pref/entry.ui create mode 100644 ui/rsc/pref/switch.ui diff --git a/ui/build.rs b/ui/build.rs new file mode 100644 index 0000000..71763a3 --- /dev/null +++ b/ui/build.rs @@ -0,0 +1,7 @@ +fn main() { + glib_build_tools::compile_resources( + &["rsc"], + "rsc/resources.gresource.xml", + "ui.gresource", + ); +} diff --git a/ui/rsc/pref/dropdown.ui b/ui/rsc/pref/dropdown.ui new file mode 100644 index 0000000..bce91aa --- /dev/null +++ b/ui/rsc/pref/dropdown.ui @@ -0,0 +1,32 @@ + + + + diff --git a/ui/rsc/pref/entry.ui b/ui/rsc/pref/entry.ui new file mode 100644 index 0000000..bff72e9 --- /dev/null +++ b/ui/rsc/pref/entry.ui @@ -0,0 +1,34 @@ + + + + diff --git a/ui/rsc/pref/switch.ui b/ui/rsc/pref/switch.ui new file mode 100644 index 0000000..e9ecbde --- /dev/null +++ b/ui/rsc/pref/switch.ui @@ -0,0 +1,31 @@ + + + + diff --git a/ui/rsc/resources.gresource.xml b/ui/rsc/resources.gresource.xml index bd84eea..d9e189c 100644 --- a/ui/rsc/resources.gresource.xml +++ b/ui/rsc/resources.gresource.xml @@ -2,5 +2,8 @@ window.ui + pref/dropdown.ui + pref/entry.ui + pref/switch.ui diff --git a/ui/rsc/window.ui b/ui/rsc/window.ui index 1f7f3db..2c4c35a 100644 --- a/ui/rsc/window.ui +++ b/ui/rsc/window.ui @@ -12,252 +12,144 @@ 300 - + + + + + + + + + Profiles + + + + + + + + + + + + vertical + 12 + 12 + 12 + 12 - - - - - - - Profiles - - - - - - - - - - - - vertical - 12 - 12 - 12 - 12 - - - Create New Profile - suggested-action - - - - + + Create New Profile + suggested-action + + + + - - - - - - - - - lsfg-vk Configuration Menu - - - - + + + + + + + + + lsfg-vk Configuration Menu + + + + - - - - never - - - vertical - 48 - 48 - 32 - 32 - 32 - - - - Frame Generation - - - - - - horizontal - 16 - center - 12 - 12 - 8 - 8 - - - Multiplier - start - true - - - - - 0 - - - - 2x - 3x - 4x - - - - - - - - - - - - - - - horizontal - 16 - center - 12 - 12 - 8 - 8 - - - Flow Scale - start - true - - - - - 0.7 - number - - - - - - - - - - - - horizontal - 16 - center - 12 - 12 - 8 - 8 - - - Performance Mode - start - true - - - - - true - compact - - - - - - + + + + never + + + vertical + 48 + 48 + 32 + 32 + 32 + + + + Frame Generation + + + + Multiplier + 0 + + + + 2x + 3x + 4x + + + + + + + Flow Scale + Enter a number between 0.25 and 1.00 + 0.7 + + + - - + + Performance Mode + false + + + + + + + Frame Generation - - - - horizontal - 16 - center - 12 - 12 - 8 - 8 - - - HDR Mode - start - true - - - - - false - compact - - - - + + HDR Mode + false - - - - horizontal - 16 - center - 12 - 12 - 8 - 8 - - - Experimental Present Mode - start - true + + Experimental Present Mode + 0 + + + + vsync/fifo + mailbox + immediate + - - - - 0 - - - - vsync/fifo - mailbox - immediate - - - - - - - + - - - - - - + + + + + From 850c8476c8d118b11407b1442e229007c85d5e28 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 23 Jul 2025 18:11:44 +0200 Subject: [PATCH 202/253] remove most of the ui major rewrite incoming :/ --- ui/Cargo.lock | 434 ++++++-------------- ui/Cargo.toml | 19 +- ui/resources/com.cali666.lsfg-vk-ui.desktop | 13 - ui/resources/icons/lsfg-vk.png | Bin 91746 -> 0 bytes ui/resources/ui.ui | 347 ---------------- ui/src/app_state.rs | 293 ------------- ui/src/config.rs | 141 ------- ui/src/settings_window.rs | 155 ------- ui/src/utils.rs | 59 --- 9 files changed, 124 insertions(+), 1337 deletions(-) delete mode 100644 ui/resources/com.cali666.lsfg-vk-ui.desktop delete mode 100644 ui/resources/icons/lsfg-vk.png delete mode 100644 ui/resources/ui.ui delete mode 100644 ui/src/app_state.rs delete mode 100644 ui/src/config.rs delete mode 100644 ui/src/settings_window.rs delete mode 100644 ui/src/utils.rs diff --git a/ui/Cargo.lock b/ui/Cargo.lock index fb3b967..537d499 100644 --- a/ui/Cargo.lock +++ b/ui/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "anyhow" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" - [[package]] name = "autocfg" version = "1.5.0" @@ -22,23 +16,21 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "cairo-rs" -version = "0.18.5" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +checksum = "f6466a563dea2e99f59f6ffbb749fd0bdf75764f5e6e93976b5e7bd73c4c9efb" dependencies = [ "bitflags", "cairo-sys-rs", "glib", "libc", - "once_cell", - "thiserror", ] [[package]] name = "cairo-sys-rs" -version = "0.18.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +checksum = "cab7e9f13c802625aad1ad2b4ae3989f4ce9339ff388f335a6f109f9338705e2" dependencies = [ "glib-sys", "libc", @@ -47,50 +39,14 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.8" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +checksum = "0d0390889d58f934f01cd49736275b4c2da15bcfc328c78ff2349907e6cabf22" dependencies = [ "smallvec", "target-lexicon", ] -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "directories" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -147,7 +103,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -172,22 +128,21 @@ dependencies = [ [[package]] name = "gdk-pixbuf" -version = "0.18.5" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +checksum = "688dc7eaf551dbac1f5b11d000d089c3db29feb25562455f47c1a2080cc60bda" dependencies = [ "gdk-pixbuf-sys", "gio", "glib", "libc", - "once_cell", ] [[package]] name = "gdk-pixbuf-sys" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +checksum = "5af1823d3d1cb72616873ba0a593bd440eb92da700fdfb047505a21ee3ec3e10" dependencies = [ "gio-sys", "glib-sys", @@ -198,9 +153,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.7.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edb019ad581f8ecf8ea8e4baa6df7c483a95b5a59be3140be6a9c3b0c632af6" +checksum = "0a67b064d2f35e649232455c7724f56f977555d2608c43300eabc530eaa4e359" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -213,9 +168,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.7.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbab43f332a3cf1df9974da690b5bb0e26720ed09a228178ce52175372dcfef0" +checksum = "2edbda0d879eb85317bdb49a3da591ed70a804a10776e358ef416be38c6db2c5" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -228,22 +183,11 @@ dependencies = [ "system-deps", ] -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "gio" -version = "0.18.4" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +checksum = "273d64c833fbbf7cd86c4cdced893c5d3f2f5d6aeb30fd0c30d172456ce8be2e" dependencies = [ "futures-channel", "futures-core", @@ -252,30 +196,28 @@ dependencies = [ "gio-sys", "glib", "libc", - "once_cell", "pin-project-lite", "smallvec", - "thiserror", ] [[package]] name = "gio-sys" -version = "0.18.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +checksum = "2c8130f5810a839d74afc3a929c34a700bf194972bb034f2ecfe639682dd13cc" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", - "winapi", + "windows-sys", ] [[package]] name = "glib" -version = "0.18.5" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +checksum = "690e8bcf8a819b5911d6ae79879226191d01253a4f602748072603defd5b9553" dependencies = [ "bitflags", "futures-channel", @@ -289,30 +231,36 @@ dependencies = [ "gobject-sys", "libc", "memchr", - "once_cell", "smallvec", - "thiserror", +] + +[[package]] +name = "glib-build-tools" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86aebe63bb050d4918cb1d629880cb35fcba7ccda6f6fc0ec1beffdaa1b9d5c3" +dependencies = [ + "gio", ] [[package]] name = "glib-macros" -version = "0.18.5" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +checksum = "e772291ebea14c28eb11bb75741f62f4a4894f25e60ce80100797b6b010ef0f9" dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", + "heck", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] name = "glib-sys" -version = "0.18.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +checksum = "4b2be4c74454fb4a6bd3328320737d0fa3d6939e2d570f5d846da00cb222f6a0" dependencies = [ "libc", "system-deps", @@ -320,9 +268,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +checksum = "ab318a786f9abd49d388013b9161fa0ef8218ea6118ee7111c95e62186f7d31f" dependencies = [ "glib-sys", "libc", @@ -331,9 +279,9 @@ dependencies = [ [[package]] name = "graphene-rs" -version = "0.18.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2228cda1505613a7a956cca69076892cfbda84fc2b7a62b94a41a272c0c401" +checksum = "0487f78e8a772ec89020458fbabadd1332bc1e3236ca1c63ef1d61afd4e5f2cc" dependencies = [ "glib", "graphene-sys", @@ -342,9 +290,9 @@ dependencies = [ [[package]] name = "graphene-sys" -version = "0.18.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4144cee8fc8788f2a9b73dc5f1d4e1189d1f95305c4cb7bd9c1af1cfa31f59" +checksum = "270cefb6b270fcb2ef9708c3a35c0e25c2e831dac28d75c4f87e5ad3540c9543" dependencies = [ "glib-sys", "libc", @@ -354,9 +302,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.7.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d958e351d2f210309b32d081c832d7de0aca0b077aa10d88336c6379bd01f7e" +checksum = "d5dbe33ceed6fc20def67c03d36e532f5a4a569ae437ae015a7146094f31e10c" dependencies = [ "cairo-rs", "gdk4", @@ -369,9 +317,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.7.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bd9e3effea989f020e8f1ff3fa3b8c63ba93d43b899c11a118868853a56d55" +checksum = "8d76011d55dd19fde16ffdedee08877ae6ec942818cfa7bc08a91259bc0b9fc9" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -385,9 +333,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.7.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb51aa3e9728575a053e1f43543cd9992ac2477e1b186ad824fd4adfb70842" +checksum = "938d68ad43080ad5ee710c30d467c1bc022ee5947856f593855691d726305b3e" dependencies = [ "cairo-rs", "field-offset", @@ -406,23 +354,21 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.7.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d57ec49cf9b657f69a05bca8027cff0a8dfd0c49e812be026fc7311f2163832f" +checksum = "0912d2068695633002b92c5966edc108b2e4f54b58c509d1eeddd4cbceb7315c" dependencies = [ - "anyhow", - "proc-macro-crate 1.3.1", - "proc-macro-error", + "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "gtk4-sys" -version = "0.7.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54d8c4aa23638ce9faa2caf7e2a27d4a1295af2155c8e8d28c4d4eeca7a65eb8" +checksum = "a923bdcf00e46723801162de24432cbce38a6810e0178a2d0b6dd4ecc26a1c74" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -443,12 +389,6 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -467,11 +407,10 @@ dependencies = [ [[package]] name = "libadwaita" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe7e70c06507ed10a16cda707f358fbe60fe0dc237498f78c686ade92fd979c" +checksum = "4df6715d1257bd8c093295b77a276ed129d73543b10304fec5829ced5d5b7c41" dependencies = [ - "gdk-pixbuf", "gdk4", "gio", "glib", @@ -483,9 +422,9 @@ dependencies = [ [[package]] name = "libadwaita-sys" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e10aaa38de1d53374f90deeb4535209adc40cc5dba37f9704724169bceec69a" +checksum = "fdf8950090cc180250cdb1ff859a39748feeda7a53a9f28ead3a17a14cc37ae2" dependencies = [ "gdk4-sys", "gio-sys", @@ -503,28 +442,6 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" -[[package]] -name = "libredox" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" -dependencies = [ - "bitflags", - "libc", -] - -[[package]] -name = "lsfg-vk-ui" -version = "0.1.3" -dependencies = [ - "directories", - "dirs", - "gtk4", - "libadwaita", - "serde", - "toml", -] - [[package]] name = "memchr" version = "2.7.5" @@ -540,36 +457,23 @@ dependencies = [ "autocfg", ] -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "pango" -version = "0.18.3" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +checksum = "2d4803f086c4f49163c31ac14db162112a22401c116435080e4be8678c507d61" dependencies = [ "gio", "glib", "libc", - "once_cell", "pango-sys", ] [[package]] name = "pango-sys" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +checksum = "66872b3cfd328ad6d1a4f89ebd5357119bd4c592a4ddbb8f6bc2386f8ce7b898" dependencies = [ "glib-sys", "gobject-sys", @@ -597,46 +501,11 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "toml_edit", ] [[package]] @@ -657,17 +526,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "rustc_version" version = "0.4.1" @@ -700,7 +558,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn", ] [[package]] @@ -724,17 +582,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.104" @@ -748,12 +595,12 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.2.2" +version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" dependencies = [ "cfg-expr", - "heck 0.5.0", + "heck", "pkg-config", "toml", "version-compare", @@ -761,67 +608,36 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "toml" -version = "0.8.2" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.2", + "toml_edit", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", @@ -830,6 +646,15 @@ dependencies = [ "winnow", ] +[[package]] +name = "ui" +version = "0.1.0" +dependencies = [ + "glib-build-tools", + "gtk4", + "libadwaita", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -842,58 +667,25 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -902,51 +694,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.5.40" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] diff --git a/ui/Cargo.toml b/ui/Cargo.toml index 3524dea..9684e1e 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -1,14 +1,11 @@ [package] -name = "lsfg-vk-ui" -version = "0.1.3" -edition = "2021" -authors = ["Cali666"] -description = "Lossless Scaling Frame Generation Configuration Tool" +name = "ui" +version = "0.1.0" +edition = "2024" [dependencies] -libadwaita = "0.5" -gtk = { version = "0.7", package = "gtk4" } -directories = "5.0" -serde = { version = "1.0", features = ["derive"] } -toml = "0.8" -dirs = "5.0" +gtk = { version = "0.10.0", package = "gtk4" } +adw = { version = "0.8.0", package = "libadwaita" } + +[build-dependencies] +glib-build-tools = "0.21.0" diff --git a/ui/resources/com.cali666.lsfg-vk-ui.desktop b/ui/resources/com.cali666.lsfg-vk-ui.desktop deleted file mode 100644 index 640a161..0000000 --- a/ui/resources/com.cali666.lsfg-vk-ui.desktop +++ /dev/null @@ -1,13 +0,0 @@ -[Desktop Entry] -Version=1.0 -Type=Application -Name=LSFG-VK UI -Comment=Lossless Scaling Frame Generation Configuration Tool -Exec=lsfg-vk-ui %U -Icon=com.cali666.lsfg-vk-ui -Terminal=false -Categories=Game;Settings; -Keywords=lossless;scaling;frame;generation;gaming;graphics;configuration; -StartupNotify=true -StartupWMClass=com.cali666.lsfg-vk-ui -MimeType=application/x-lsfg-profile; diff --git a/ui/resources/icons/lsfg-vk.png b/ui/resources/icons/lsfg-vk.png deleted file mode 100644 index 1c67ee2a50503f34b7bd6afbed09a3f0e4cb1f9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91746 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%0DRY{s%5jc~Z{Pg8 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
T$^`GHzq(O>E5~*5Ax;sS?4u~jFw@hXmOtor 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 - - - - Lossless Scaling Frame Generation - 800 - 600 - true - - - - - - - Lossless Scaling Frame Generation - - - - - - Settings - settings-icon-button - - - - - - - - horizontal - - - - never - automatic - - - vertical - 0 - sidebar-content - - - LSFG Profiles - 0.0 - 12 - 12 - 6 - title-1 - - - - - browse - navigation-sidebar - - - - - - Create New Profile - 12 - 12 - 12 - suggested-action - - - - - - - - - - vertical - true - true - - - center - - - - - true - true - - - - settings_page - Settings - - - never - - - vertical - 48 - 48 - 32 - 32 - 32 - - - - - Frame Generation - true - 8 - 8 - - - true - - - horizontal - 16 - center - 12 - 12 - 8 - 8 - - - Multiplier - start - true - 0 - - - - - - - - off - 2 - 3 - 4 - - - - 0 - - - - - - - - - true - - - horizontal - 16 - center - 12 - 12 - 8 - 8 - - - Flow Scale - start - true - 0 - - - - - 0.7 - number - - - - - - - - - true - - - horizontal - 16 - center - 12 - 12 - 8 - 8 - - - Performance Mode - start - true - 0 - - - - - true - compact - - - - - - - - - - - - - Misc - true - 8 - 8 - - - true - - - horizontal - 16 - center - 12 - 12 - 8 - 8 - - - HDR Mode - start - true - 0 - - - - - true - compact - - - - - - - - - true - - - horizontal - 16 - center - 12 - 12 - 8 - 8 - - - Experimental Present Mode - start - true - 0 - - - - - - - - vsync - mailbox - immediate - - - - 0 - - - - - - - - - - - - - - - - - - about_page - About - - - vertical - center - center - 24 - - - LSFG-VK UI - title-1 - - - - - Lossless Scaling Frame Generation Configuration Tool - title-4 - true - 50 - - - - - Made by Cali666 • 2025 - dim-label - 24 - - - - - For more information refer to the lsfg-vk wiki - https://github.com/PancakeTAS/lsfg-vk/wiki - 12 - - - - - - - - - - - - - - - - diff --git a/ui/src/app_state.rs b/ui/src/app_state.rs deleted file mode 100644 index 552da38..0000000 --- a/ui/src/app_state.rs +++ /dev/null @@ -1,293 +0,0 @@ -use gtk::prelude::*; -use gtk::{glib, MessageDialog}; -use libadwaita::ApplicationWindow; -use std::cell::RefCell; -use std::rc::Rc; - -use crate::config::{Config, save_config}; -use crate::utils::round_to_2_decimals; - -#[allow(dead_code)] -pub struct AppState { - pub config: Config, - pub selected_profile_index: Option, - // Store references to the UI widgets for easy access and updates - pub main_window: ApplicationWindow, - pub sidebar_list_box: gtk::ListBox, - pub multiplier_dropdown: gtk::DropDown, - pub flow_scale_entry: gtk::Entry, - pub performance_mode_switch: gtk::Switch, - pub hdr_mode_switch: gtk::Switch, - pub experimental_present_mode_dropdown: gtk::DropDown, - pub save_button: gtk::Button, - pub main_settings_box: gtk::Box, - pub main_stack: gtk::Stack, - // Store SignalHandlerIds to block/unblock signals - pub multiplier_dropdown_handler_id: Option, - pub flow_scale_entry_handler_id: Option, - pub performance_mode_switch_handler_id: Option, - pub hdr_mode_switch_handler_id: Option, - pub experimental_present_mode_dropdown_handler_id: Option, -} - -impl AppState { - // Saves the current configuration to the TOML file - pub fn save_current_config(&self) { - if let Err(e) = save_config(&self.config) { - eprintln!("Failed to save config: {}", e); - // In a real app, you'd show a user-friendly error dialog here - } - } - - // Updates the main window UI with data from the currently selected profile - pub fn update_main_window_from_profile(&self) { - if let Some(index) = self.selected_profile_index { - if let Some(profile) = self.config.game.get(index) { - // Temporarily block signals to prevent re-entrancy - let _guard_mult = self.multiplier_dropdown_handler_id.as_ref().map(|id| self.multiplier_dropdown.block_signal(id)); - let _guard_flow = self.flow_scale_entry_handler_id.as_ref().map(|id| self.flow_scale_entry.block_signal(id)); - let _guard_perf = self.performance_mode_switch_handler_id.as_ref().map(|id| self.performance_mode_switch.block_signal(id)); - let _guard_hdr = self.hdr_mode_switch_handler_id.as_ref().map(|id| self.hdr_mode_switch.block_signal(id)); - let _guard_exp = self.experimental_present_mode_dropdown_handler_id.as_ref().map(|id| self.experimental_present_mode_dropdown.block_signal(id)); - - // Update Multiplier Dropdown - let multiplier_str = match profile.multiplier { - 1 => "off".to_string(), - _ => profile.multiplier.to_string(), - }; - if let Some(pos) = self.multiplier_dropdown.model().and_then(|model| { - let list_model = model.downcast_ref::()?; - (0..list_model.n_items()).find(|&i| list_model.string(i).map_or(false, |s| s.as_str() == multiplier_str)) - }) { - self.multiplier_dropdown.set_selected(pos); - } - - // Update Flow Scale Entry (round to avoid floating point display issues) - let rounded_flow_scale = round_to_2_decimals(profile.flow_scale); - self.flow_scale_entry.set_text(&format!("{:.2}", rounded_flow_scale)); - - // Update Performance Mode Switch - self.performance_mode_switch.set_active(profile.performance_mode); - - // Update HDR Mode Switch - self.hdr_mode_switch.set_active(profile.hdr_mode); - - // Update Experimental Present Mode Dropdown - if let Some(pos) = self.experimental_present_mode_dropdown.model().and_then(|model| { - let list_model = model.downcast_ref::()?; - (0..list_model.n_items()).find(|&i| list_model.string(i).map_or(false, |s| s.as_str() == profile.experimental_present_mode)) - }) { - self.experimental_present_mode_dropdown.set_selected(pos); - } - // Signal handlers are unblocked automatically when _guard_X go out of scope - - // Switch to the settings page - self.main_stack.set_visible_child_name("settings_page"); - - } - } else { - // Clear or disable main window elements if no profile is selected - self.multiplier_dropdown.set_selected(0); // Default to 'off' or first item - self.flow_scale_entry.set_text(""); - self.performance_mode_switch.set_active(false); - self.hdr_mode_switch.set_active(false); - self.experimental_present_mode_dropdown.set_selected(0); // Default to first item - - // Switch to the about page - self.main_stack.set_visible_child_name("about_page"); - } - } - - // Populates sidebar with optional app_state for button handlers - pub fn populate_sidebar_with_handlers(&self, app_state: Option>>) { - // Clear existing rows - while let Some(child) = self.sidebar_list_box.first_child() { - self.sidebar_list_box.remove(&child); - } - - let mut row_to_select: Option = None; - - for (i, profile) in self.config.game.iter().enumerate() { - let row = gtk::ListBoxRow::new(); - - // Create a horizontal box to hold the profile name and buttons - let row_box = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) - .spacing(8) - .margin_start(12) - .margin_end(12) - .margin_top(8) - .margin_bottom(8) - .build(); - - // Profile name label - let label = gtk::Label::builder() - .label(&profile.exe) - .halign(gtk::Align::Start) - .hexpand(true) - .build(); - - // Edit button - let edit_button = gtk::Button::builder() - .label("🖊") - .css_classes(["flat", "circular"]) - .tooltip_text("Edit profile name") - .build(); - - // Remove button - let remove_button = gtk::Button::builder() - .label("𐄂") - .css_classes(["flat", "circular", "destructive-action"]) - .tooltip_text("Remove profile") - .build(); - - // Add all elements to the row box - row_box.append(&label); - row_box.append(&edit_button); - row_box.append(&remove_button); - - // Connect button handlers if app_state is available - if let Some(app_state_ref) = &app_state { - // Edit button handler - let app_state_clone = app_state_ref.clone(); - let profile_index = i; - edit_button.connect_clicked(move |_| { - let state = app_state_clone.borrow(); - let main_window = &state.main_window; - - // Create edit dialog - let dialog = MessageDialog::new( - Some(main_window), - gtk::DialogFlags::MODAL, - gtk::MessageType::Question, - gtk::ButtonsType::None, - "Edit profile name:", - ); - dialog.set_title(Some("Edit Profile")); - - let entry = gtk::Entry::builder() - .placeholder_text("Profile Name") - .text(&state.config.game[profile_index].exe) - .margin_top(12) - .margin_bottom(12) - .margin_start(12) - .margin_end(12) - .build(); - - dialog.content_area().append(&entry); - dialog.add_button("Cancel", gtk::ResponseType::Cancel); - dialog.add_button("Save", gtk::ResponseType::Other(1)); - dialog.set_default_response(gtk::ResponseType::Other(1)); - - // Allow pressing Enter in the entry to trigger the "Save" button - let dialog_clone = dialog.clone(); - entry.connect_activate(move |_| { - dialog_clone.response(gtk::ResponseType::Other(1)); - }); - - let app_state_clone_dialog = app_state_clone.clone(); - let entry_clone = entry.clone(); - dialog.connect_response(move |d, response| { - if response == gtk::ResponseType::Other(1) { - let new_name = entry_clone.text().to_string(); - if !new_name.is_empty() { - let mut state = app_state_clone_dialog.borrow_mut(); - - // Check if profile with this name already exists (excluding current) - if state.config.game.iter().enumerate().any(|(idx, p)| idx != profile_index && p.exe == new_name) { - let error_dialog = MessageDialog::new( - Some(d), - gtk::DialogFlags::MODAL, - gtk::MessageType::Error, - gtk::ButtonsType::Ok, - "A profile with this name already exists", - ); - error_dialog.set_title(Some("Error")); - error_dialog.connect_response(move |d, _| { d.close(); }); - error_dialog.present(); - return; - } - - // Update profile name - state.config.game[profile_index].exe = new_name; - state.save_current_config(); - state.populate_sidebar_with_handlers(Some(app_state_clone_dialog.clone())); - } - } - d.close(); - }); - dialog.present(); - }); - - // Remove button handler - let app_state_clone = app_state_ref.clone(); - let profile_index = i; - remove_button.connect_clicked(move |_| { - let state = app_state_clone.borrow(); - let main_window = &state.main_window; - let profile_name = &state.config.game[profile_index].exe; - - // Create confirmation dialog - let dialog = MessageDialog::new( - Some(main_window), - gtk::DialogFlags::MODAL, - gtk::MessageType::Warning, - gtk::ButtonsType::None, - &format!("Are you sure you want to remove the profile '{}'?", profile_name), - ); - dialog.set_title(Some("Remove Profile")); - dialog.add_button("Cancel", gtk::ResponseType::Cancel); - dialog.add_button("Remove", gtk::ResponseType::Other(1)); - dialog.set_default_response(gtk::ResponseType::Cancel); - - let app_state_clone_dialog = app_state_clone.clone(); - dialog.connect_response(move |d, response| { - if response == gtk::ResponseType::Other(1) { - let mut state = app_state_clone_dialog.borrow_mut(); - - // Remove the profile - state.config.game.remove(profile_index); - - // Update selected index if needed - if let Some(selected) = state.selected_profile_index { - if selected == profile_index { - // If we removed the selected profile, select the first available or none - state.selected_profile_index = if state.config.game.is_empty() { None } else { Some(0) }; - } else if selected > profile_index { - // Adjust index if we removed a profile before the selected one - state.selected_profile_index = Some(selected - 1); - } - } - - state.save_current_config(); - state.populate_sidebar_with_handlers(Some(app_state_clone_dialog.clone())); - drop(state); - - // Update main window - app_state_clone_dialog.borrow().update_main_window_from_profile(); - } - d.close(); - }); - dialog.present(); - }); - } - - row.set_child(Some(&row_box)); - self.sidebar_list_box.append(&row); - - // Mark the row to be selected later - if self.selected_profile_index == Some(i) { - row_to_select = Some(row.clone()); // Clone the row to store it - } - } - - // Perform selection in a separate idle callback - if let Some(row) = row_to_select { - let list_box_clone = self.sidebar_list_box.clone(); - glib::idle_add_local(move || { - list_box_clone.select_row(Some(&row)); - glib::ControlFlow::Break - }); - } - } -} diff --git a/ui/src/config.rs b/ui/src/config.rs deleted file mode 100644 index 7fdde70..0000000 --- a/ui/src/config.rs +++ /dev/null @@ -1,141 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::{fs, io}; -use std::path::PathBuf; -use toml; -use dirs; - -use crate::utils::round_to_2_decimals; // Import from utils module - -// --- Configuration Data Structures --- - -#[derive(Debug, Default, Serialize, Deserialize, Clone)] -pub struct Config { - pub version: u32, // Made public to be accessible from main.rs - #[serde(flatten)] // Flatten this struct into the parent, controlling order - pub ordered_global: OrderedGlobalConfig, - #[serde(default)] - pub game: Vec, -} - -// Helper struct to control the serialization order of global config -#[derive(Debug, Default, Serialize, Deserialize, Clone)] -pub struct OrderedGlobalConfig { - #[serde(default, skip_serializing_if = "Option::is_none")] // Only serialize if Some - pub global: Option, -} - -#[derive(Debug, Default, Serialize, Deserialize, Clone)] -pub struct GlobalConfig { - #[serde(default, skip_serializing_if = "Option::is_none")] // Only serialize if Some - pub dll: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(default)] -pub struct GameProfile { - pub exe: String, - pub multiplier: u32, - #[serde(serialize_with = "serialize_flow_scale", deserialize_with = "deserialize_flow_scale")] - pub flow_scale: f32, - pub performance_mode: bool, - pub hdr_mode: bool, - pub experimental_present_mode: String, -} - -// Default values for a new game profile -impl Default for GameProfile { - fn default() -> Self { - GameProfile { - exe: String::new(), - multiplier: 1, // Default to "off" (1) - flow_scale: round_to_2_decimals(0.7), - performance_mode: true, - hdr_mode: false, - experimental_present_mode: "vsync".to_string(), - } - } -} - -// Custom serde functions to ensure flow_scale is always rounded -fn serialize_flow_scale(value: &f32, serializer: S) -> Result -where - S: serde::Serializer, -{ - // Force to 2 decimal places and serialize as a precise decimal - let rounded = round_to_2_decimals(*value); - let formatted = format!("{:.2}", rounded); - let precise_value: f64 = formatted.parse().unwrap_or(*value as f64); - serializer.serialize_f64(precise_value) -} - -fn deserialize_flow_scale<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - use serde::Deserialize; - let value = f64::deserialize(deserializer)?; - Ok(round_to_2_decimals(value as f32)) -} - -// --- Configuration File Handling Functions --- - -pub fn get_config_path() -> Result { - let config_dir = dirs::config_dir() - .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Could not find config directory"))? - .join("lsfg-vk"); - - fs::create_dir_all(&config_dir)?; // Ensure directory exists - println!("Config directory: {:?}", config_dir); - Ok(config_dir.join("conf.toml")) -} - - -pub fn load_config() -> Result { - let config_path = get_config_path()?; - println!("Attempting to load config from: {:?}", config_path); - if config_path.exists() { - let contents = fs::read_to_string(&config_path)?; - println!("Successfully read config contents ({} bytes).", contents.len()); - // Load configuration with default values when the format is invalid - let mut config: Config = toml::from_str(&contents).unwrap_or_else(|_| Config::default()); - - // Old way to load config - // let mut config: Config = toml::from_str(&contents).map_err(|e| { - // io::Error::new( - // io::ErrorKind::InvalidData, - // format!("Failed to parse TOML: {}", e), - // ) - // })?; - - - // Clean up any floating point precision issues in existing configs - let mut needs_save = false; - for profile in &mut config.game { - let original = profile.flow_scale; - profile.flow_scale = round_to_2_decimals(profile.flow_scale); - if (original - profile.flow_scale).abs() > f32::EPSILON { - needs_save = true; - } - } - - // Save the cleaned config if we made changes - if needs_save { - let _ = save_config(&config); - } - - Ok(config) - } else { - println!("Config file not found at {:?}, creating default.", config_path); - Ok(Config { version: 1, ordered_global: OrderedGlobalConfig { global: None }, game: Vec::new() }) - } -} - -pub fn save_config(config: &Config) -> Result<(), io::Error> { - let config_path = get_config_path()?; - println!("Attempting to save config to: {:?}", config_path); - let toml_string = toml::to_string_pretty(config) - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to serialize TOML: {}", e)))?; - fs::write(&config_path, toml_string)?; - println!("Successfully saved config."); - Ok(()) -} diff --git a/ui/src/settings_window.rs b/ui/src/settings_window.rs deleted file mode 100644 index 73417e7..0000000 --- a/ui/src/settings_window.rs +++ /dev/null @@ -1,155 +0,0 @@ -use gtk::prelude::*; -use gtk::{glib, Label, Switch, Entry, Box, Orientation}; -use libadwaita::prelude::*; -use libadwaita::{ApplicationWindow, PreferencesGroup, PreferencesPage, PreferencesWindow, ActionRow}; -use std::rc::Rc; -use std::cell::RefCell; - -use crate::app_state::AppState; - -pub fn create_settings_window(parent: &ApplicationWindow, app_state: Rc>) -> PreferencesWindow { - let settings_window = PreferencesWindow::builder() - .title("Settings") - .transient_for(parent) - .modal(true) - .search_enabled(false) - .default_width(450) // Set default width - .default_height(300) // Set default height - .build(); - - let page = PreferencesPage::builder() - .title("General") - .icon_name("preferences-system-symbolic") - .build(); - - let group = PreferencesGroup::builder() - .title("Global Settings") - .build(); - - // --- Custom DLL Toggle and Path (Programmatically created) --- - let custom_dll_switch = Switch::builder() - .halign(gtk::Align::End) - .valign(gtk::Align::Center) - .build(); - - let custom_dll_path_entry = Entry::builder() - .placeholder_text("/path/to/Lossless.dll") - .hexpand(true) - .build(); - - let custom_dll_row = ActionRow::builder() - .title("Custom Path to Lossless.dll") - .build(); - custom_dll_row.add_suffix(&custom_dll_switch); - custom_dll_row.set_activatable_widget(Some(&custom_dll_switch)); - - let custom_dll_box = Box::builder() - .orientation(Orientation::Vertical) - .spacing(6) - .margin_top(6) - .margin_bottom(6) - .build(); - custom_dll_box.append(&custom_dll_row); - custom_dll_box.append(&custom_dll_path_entry); - - group.add(&custom_dll_box); // Add the box directly to the group - - // Initial state setup for Custom DLL - let current_dll_path = app_state.borrow().config.ordered_global.global.as_ref() - .and_then(|g| g.dll.clone()); - - if let Some(path) = current_dll_path { - custom_dll_switch.set_active(true); - custom_dll_path_entry.set_text(&path); - custom_dll_path_entry.set_visible(true); - } else { - custom_dll_switch.set_active(false); - custom_dll_path_entry.set_visible(false); - } - - // Connect switch to show/hide entry and update config - let app_state_clone_switch = app_state.clone(); - let custom_dll_path_entry_clone = custom_dll_path_entry.clone(); - custom_dll_switch.connect_state_set(move |_sw, active| { - custom_dll_path_entry_clone.set_visible(active); - let mut state = app_state_clone_switch.borrow_mut(); - if active { - // If activating, ensure global config exists and set DLL path - let current_path = custom_dll_path_entry_clone.text().to_string(); - state.config.ordered_global.global.get_or_insert_with(Default::default).dll = Some(current_path); - } else { - // If deactivating, set DLL path to None - if let Some(global_config) = state.config.ordered_global.global.as_mut() { - global_config.dll = None; - } - } - glib::Propagation::Proceed - }); - - // Connect entry to update config - let app_state_clone_entry = app_state.clone(); - let custom_dll_switch_clone = custom_dll_switch.clone(); - custom_dll_path_entry.connect_changed(move |entry| { - let mut state = app_state_clone_entry.borrow_mut(); - if custom_dll_switch_clone.is_active() { - let path = entry.text().to_string(); - if !path.is_empty() { - state.config.ordered_global.global.get_or_insert_with(Default::default).dll = Some(path); - } else { - // If path is cleared, set dll to None - if let Some(global_config) = state.config.ordered_global.global.as_mut() { - global_config.dll = None; - } - } - } - }); - - // Save button for settings - let save_settings_button = gtk::Button::builder() - .label("Save Global Settings") - .halign(gtk::Align::End) - .margin_end(12) - .margin_bottom(12) - .margin_top(12) - .build(); - - // Create a box to hold the feedback label - let feedback_container_box = Box::builder() - .orientation(Orientation::Vertical) - .halign(gtk::Align::End) - .margin_end(12) - .margin_bottom(12) - .build(); - - group.add(&save_settings_button); // Add button first - group.add(&feedback_container_box); // Then add the container for feedback - - let app_state_clone_save = app_state.clone(); - let feedback_container_box_clone = feedback_container_box.clone(); // Clone for the closure - save_settings_button.connect_clicked(move |_| { - let state = app_state_clone_save.borrow_mut(); // Removed 'mut' - state.save_current_config(); - - let feedback_label = Label::new(Some("Saved!")); - feedback_label.set_halign(gtk::Align::End); - feedback_label.set_margin_end(12); - feedback_label.set_margin_bottom(12); - - // Append to the dedicated feedback container box - feedback_container_box_clone.append(&feedback_label); - - glib::timeout_add_local(std::time::Duration::new(2, 0), { - let feedback_label_clone = feedback_label.clone(); // Clone for the timeout - let feedback_container_box_clone_for_remove = feedback_container_box_clone.clone(); // Clone for the timeout - move || { - feedback_container_box_clone_for_remove.remove(&feedback_label_clone); - glib::ControlFlow::Break - } - }); - }); - - page.add(&group); - settings_window.add(&page); - - settings_window -} diff --git a/ui/src/utils.rs b/ui/src/utils.rs deleted file mode 100644 index 7fe2612..0000000 --- a/ui/src/utils.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::process::Command; -use std::io::{BufReader, BufRead}; - -pub fn round_to_2_decimals(value: f32) -> f32 { - // Use string formatting to get exactly 2 decimal places and then parse back - // This avoids floating point precision issues - format!("{:.2}", value).parse().unwrap_or(value) -} - -/// Executes a bash command to find running processes that use Vulkan -/// and are owned by the current user. -/// Returns a vector of process names. -pub fn get_vulkan_processes() -> Vec { - let mut processes = Vec::new(); - let command_str = r#" - for pid in /proc/[0-9]*; do - owner=$(stat -c %U "$pid" 2>/dev/null) - if [[ "$owner" == "$USER" ]]; then - if grep -qi 'vulkan' "$pid/maps" 2>/dev/null; then - procname=$(cat "$pid/comm" 2>/dev/null) - if [[ -n "$procname" ]]; then - printf "%s\n" "$procname" # Only print the process name - fi - fi - fi - done - "#; - - // Execute the bash command - let output = Command::new("bash") - .arg("-c") - .arg(command_str) - .output(); - - match output { - Ok(output) => { - if output.status.success() { - // Read stdout line by line - let reader = BufReader::new(output.stdout.as_slice()); - for line in reader.lines() { - if let Ok(proc_name) = line { - let trimmed_name = proc_name.trim().to_string(); - if !trimmed_name.is_empty() { - processes.push(trimmed_name); - } - } - } - } else { - // Print stderr if the command failed - eprintln!("Command failed with error: {}", String::from_utf8_lossy(&output.stderr)); - } - }, - Err(e) => { - // Print error if the command could not be executed - eprintln!("Failed to execute command: {}", e); - } - } - processes -} From bb13eb46560ee30efe70c807122080c101664465 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 23 Jul 2025 18:12:02 +0200 Subject: [PATCH 203/253] ui: create wrappers for ui --- ui/src/main.rs | 519 ++------------------------------ ui/src/wrapper/pref.rs | 49 +++ ui/src/wrapper/pref/dropdown.rs | 44 +++ ui/src/wrapper/pref/entry.rs | 44 +++ ui/src/wrapper/pref/switch.rs | 42 +++ ui/src/wrapper/ui.rs | 29 ++ ui/src/wrapper/ui/window.rs | 36 +++ 7 files changed, 268 insertions(+), 495 deletions(-) create mode 100644 ui/src/wrapper/pref.rs create mode 100644 ui/src/wrapper/pref/dropdown.rs create mode 100644 ui/src/wrapper/pref/entry.rs create mode 100644 ui/src/wrapper/pref/switch.rs create mode 100644 ui/src/wrapper/ui.rs create mode 100644 ui/src/wrapper/ui/window.rs diff --git a/ui/src/main.rs b/ui/src/main.rs index f3d066e..6b8099c 100644 --- a/ui/src/main.rs +++ b/ui/src/main.rs @@ -1,497 +1,26 @@ -use gtk::prelude::*; -use gtk::{glib, CssProvider, Builder, Label}; -use libadwaita::ApplicationWindow; -use libadwaita::prelude::AdwApplicationWindowExt; -use std::cell::RefCell; -use std::rc::Rc; +use adw; +use gtk::{gio, prelude::*}; -// Import modules -mod config; -mod app_state; -mod utils; -mod settings_window; - -use config::load_config; -use app_state::AppState; -use utils::round_to_2_decimals; -use config::OrderedGlobalConfig; - -fn main() -> glib::ExitCode { - let application = libadwaita::Application::builder() - .application_id("com.cali666.lsfg-vk-ui") - .build(); - - // Set the desktop file name for proper GNOME integration - glib::set_application_name("LSFG-VK UI"); - glib::set_prgname(Some("lsfg-vk-ui")); - - application.connect_startup(move |_app| { - // Load CSS for sidebar background - let provider = CssProvider::new(); - provider.load_from_data(&format!( - ".settings-icon-button {{ - font-size: 1.4rem; - }} - - .sidebar {{ - background-color: @theme_bg_color; - }} - - .sidebar-content {{ - background-color: shade(@theme_bg_color, {}); - color: @theme_fg_color; - padding: 12px; - }}\n - .linked-button-box {{ - margin-top: 12px; - margin-bottom: 12px; - }}", - 0.95 - )); - gtk::style_context_add_provider_for_display( - >k::gdk::Display::default().expect("Could not connect to a display."), - &provider, - gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, - ); - - // Set up icon theme for the application icon - if let Some(display) = gtk::gdk::Display::default() { - let icon_theme = gtk::IconTheme::for_display(&display); - icon_theme.add_resource_path("/com/cali666/lsfg-vk-ui/icons"); - } - }); - - application.connect_activate(move |app| { - // Load initial configuration - let initial_config = load_config().unwrap_or_else(|e| { - eprintln!("Error loading config: {}", e); - // Corrected Config initialization - config::Config { version: 1, ordered_global: OrderedGlobalConfig { global: None }, game: Vec::new() } - }); - - // Load UI from .ui file - let ui_bytes = include_bytes!("../resources/ui.ui"); - let builder = Builder::from_string(std::str::from_utf8(ui_bytes).unwrap()); - - // Get main window and other widgets - let main_window: ApplicationWindow = builder - .object("main_window") - .expect("Could not get main_window from builder"); - main_window.set_application(Some(app)); - - let settings_button: gtk::Button = builder - .object("settings_button") - .expect("Could not get settings_button from builder"); - - // Set application icon for proper dock integration - main_window.set_icon_name(Some("com.cali666.lsfg-vk-ui")); - - let sidebar_list_box: gtk::ListBox = builder - .object("sidebar_list_box") - .expect("Could not get sidebar_list_box from builder"); - let create_profile_button: gtk::Button = builder - .object("create_profile_button") - .expect("Could not get create_profile_button from builder"); - - let multiplier_dropdown: gtk::DropDown = builder - .object("multiplier_dropdown") - .expect("Could not get multiplier_dropdown from builder"); - let flow_scale_entry: gtk::Entry = builder - .object("flow_scale_entry") - .expect("Could not get flow_scale_entry from builder"); - let performance_mode_switch: gtk::Switch = builder - .object("performance_mode_switch") - .expect("Could not get performance_mode_switch from builder"); - let hdr_mode_switch: gtk::Switch = builder - .object("hdr_mode_switch") - .expect("Could not get hdr_mode_switch from builder"); - let experimental_present_mode_dropdown: gtk::DropDown = builder - .object("experimental_present_mode_dropdown") - .expect("Could not get experimental_present_mode_dropdown from builder"); - - let main_stack: gtk::Stack = builder - .object("main_stack") - .expect("Could not get main_stack from builder. Ensure it has id='main_stack' in ui.ui."); - let main_stack_switcher: gtk::StackSwitcher = builder - .object("main_stack_switcher") - .expect("Could not get main_stack_switcher from builder. Ensure it has id='main_stack_switcher' in ui.ui."); - - main_stack_switcher.set_stack(Some(&main_stack)); - - let main_settings_box: gtk::Box = builder - .object("main_box") - .expect("Could not get main_box from builder"); - - let save_button = gtk::Button::builder() - .label("Save Changes") - .halign(gtk::Align::End) - .margin_end(12) - .margin_bottom(12) - .build(); - - main_settings_box.append(&save_button); - - // Initialize application state (with None for handler IDs initially) - let app_state = Rc::new(RefCell::new(AppState { - config: initial_config, - selected_profile_index: None, - main_window: main_window.clone(), - sidebar_list_box: sidebar_list_box.clone(), - multiplier_dropdown: multiplier_dropdown.clone(), - flow_scale_entry: flow_scale_entry.clone(), - performance_mode_switch: performance_mode_switch.clone(), - hdr_mode_switch: hdr_mode_switch.clone(), - experimental_present_mode_dropdown: experimental_present_mode_dropdown.clone(), - save_button: save_button.clone(), - main_settings_box: main_settings_box.clone(), - main_stack: main_stack.clone(), - multiplier_dropdown_handler_id: None, - flow_scale_entry_handler_id: None, - performance_mode_switch_handler_id: None, - hdr_mode_switch_handler_id: None, - experimental_present_mode_dropdown_handler_id: None, - })); - - // --- Connect Signals --- - - // Connect settings button - let main_window_clone = main_window.clone(); - let app_state_clone_for_settings = app_state.clone(); // Clone for settings window - settings_button.connect_clicked(move |_| { - let settings_win = settings_window::create_settings_window(&main_window_clone, app_state_clone_for_settings.clone()); - settings_win.present(); - }); - - let app_state_clone = app_state.clone(); - sidebar_list_box.connect_row_activated(move |_list_box, row| { - let index = row.index() as usize; - let mut state = app_state_clone.borrow_mut(); - state.selected_profile_index = Some(index); - drop(state); - - let app_state_for_idle = app_state_clone.clone(); - glib::idle_add_local(move || { - app_state_for_idle.borrow().update_main_window_from_profile(); - glib::ControlFlow::Break - }); - }); - - let app_state_clone = app_state.clone(); - create_profile_button.connect_clicked(move |_| { - let dialog = gtk::MessageDialog::new( - Some(&app_state_clone.borrow().main_window), - gtk::DialogFlags::MODAL, - gtk::MessageType::Question, - gtk::ButtonsType::None, - "", - ); - dialog.set_title(Some("New Profile")); - dialog.set_secondary_text(Some("Enter or browse Application Name")); - - let entry = gtk::Entry::builder() - .placeholder_text("Application Name") - .hexpand(true) - .build(); - - let pick_process_button = gtk::Button::builder() - .label("🖵") - .tooltip_text("Pick a running Vulkan process") - .css_classes(["flat", "square", "icon-button"]) - .build(); - - let entry_box = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) - .spacing(6) - .margin_top(12) - .margin_bottom(12) - .margin_start(12) - .margin_end(12) - .build(); - entry_box.append(&entry); - entry_box.append(&pick_process_button); - - dialog.content_area().append(&entry_box); - - dialog.add_button("Cancel", gtk::ResponseType::Cancel); - dialog.add_button("Create", gtk::ResponseType::Other(1)); - - dialog.set_default_response(gtk::ResponseType::Other(1)); - - // Allow pressing Enter in the entry to trigger the "Create" button - let dialog_clone = dialog.clone(); - entry.connect_activate(move |_| { - dialog_clone.response(gtk::ResponseType::Other(1)); - }); - - // --- Process Picker Button Logic --- - let entry_clone_for_picker = entry.clone(); - let main_window_clone_for_picker = app_state_clone.borrow().main_window.clone(); - - pick_process_button.connect_clicked(move |_| { - let process_picker_window = libadwaita::ApplicationWindow::builder() - .title("Select Process") - .transient_for(&main_window_clone_for_picker) - .modal(true) - .default_width(400) - .default_height(600) - .build(); - - let scrolled_window = gtk::ScrolledWindow::builder() - .hscrollbar_policy(gtk::PolicyType::Never) - .vscrollbar_policy(gtk::PolicyType::Automatic) - .hexpand(true) // Make the scrolled window expand horizontally - .vexpand(true) // Make the scrolled window expand vertically - .margin_top(12) - .margin_start(12) - .margin_end(12) - .build(); - - let process_list_box = gtk::ListBox::builder() - .selection_mode(gtk::SelectionMode::Single) - .build(); - scrolled_window.set_child(Some(&process_list_box)); - - let content_box = gtk::Box::builder() - .orientation(gtk::Orientation::Vertical) - .build(); - content_box.append(&scrolled_window); // Add scrolled window first to take up space - - let close_button = gtk::Button::builder() - .label("Close") - .halign(gtk::Align::End) - .margin_end(12) - .margin_bottom(12) - .build(); - content_box.append(&close_button); // Add close button at the bottom - - process_picker_window.set_content(Some(&content_box)); - - // Populate the list with processes - let processes = utils::get_vulkan_processes(); // Call the new function from utils.rs - for proc_name in processes { - let row = gtk::ListBoxRow::new(); - let label = gtk::Label::builder() - .label(&proc_name) - .halign(gtk::Align::Start) - .margin_start(12) - .margin_end(12) - .margin_top(8) - .margin_bottom(8) - .build(); - row.set_child(Some(&label)); - process_list_box.append(&row); - } - - // Connect selection handler - let entry_clone_for_select = entry_clone_for_picker.clone(); - let picker_window_clone = process_picker_window.clone(); - process_list_box.connect_row_activated(move |_list_box, row| { - if let Some(label_widget) = row.child().and_then(|c| c.downcast::().ok()) { - let process_name = label_widget.label().to_string(); - entry_clone_for_select.set_text(&process_name); - picker_window_clone.close(); - } - }); - - // Connect close button - let picker_window_clone_for_close = process_picker_window.clone(); - close_button.connect_clicked(move |_| { - picker_window_clone_for_close.close(); - }); - - process_picker_window.present(); - }); - // --- End Process Picker Button Logic --- - - let app_state_clone_dialog = app_state_clone.clone(); - let entry_clone = entry.clone(); - dialog.connect_response( - move |d: >k::MessageDialog, response: gtk::ResponseType| { - if response == gtk::ResponseType::Other(1) { - let game_name = entry_clone.text().to_string(); - if !game_name.is_empty() { - let mut state = app_state_clone_dialog.borrow_mut(); - - if state.config.game.iter().any(|p| p.exe == game_name) { - let error_dialog = gtk::MessageDialog::new( - Some(d), - gtk::DialogFlags::MODAL, - gtk::MessageType::Error, - gtk::ButtonsType::Ok, - "A profile with this name already exists", - ); - error_dialog.set_title(Some("Error")); - error_dialog.connect_response(move |d, _| { d.close(); }); - error_dialog.present(); - return; - } - - let new_profile = config::GameProfile { - exe: game_name, - ..Default::default() - }; - - state.config.game.push(new_profile); - state.selected_profile_index = Some(state.config.game.len() - 1); - - state.save_current_config(); - - state.populate_sidebar_with_handlers(Some(app_state_clone_dialog.clone())); - drop(state); - - let app_state_for_idle = app_state_clone_dialog.clone(); - glib::idle_add_local(move || { - app_state_for_idle.borrow().update_main_window_from_profile(); - glib::ControlFlow::Break - }); - } - } - d.close(); - } - ); - dialog.present(); - }); - - let app_state_clone_for_handler_mult = app_state.clone(); - let multiplier_handler_id = multiplier_dropdown.connect_selected_item_notify(move |dropdown| { - let mut state = app_state_clone_for_handler_mult.borrow_mut(); - - if let Some(index) = state.selected_profile_index { - if index < state.config.game.len() { - if let Some(profile) = state.config.game.get_mut(index) { - if let Some(item) = dropdown.selected_item() { - if let Some(string_obj) = item.downcast_ref::() { - let text = string_obj.string(); - profile.multiplier = match text.as_str() { - "off" => 1, - _ => text.parse().unwrap_or(1), - }; - } - } - } - } - } - }); - app_state.borrow_mut().multiplier_dropdown_handler_id = Some(multiplier_handler_id); - - let app_state_clone_for_handler_flow = app_state.clone(); - let flow_handler_id = flow_scale_entry.connect_changed(move |entry| { - let mut state = app_state_clone_for_handler_flow.borrow_mut(); - if let Some(index) = state.selected_profile_index { - if let Some(profile) = state.config.game.get_mut(index) { - if let Ok(value) = entry.text().parse::() { - profile.flow_scale = round_to_2_decimals(value); - } - } - } - }); - app_state.borrow_mut().flow_scale_entry_handler_id = Some(flow_handler_id); - - let app_state_clone_for_handler_perf = app_state.clone(); - let perf_handler_id = performance_mode_switch.connect_state_set(move |_sw, active| { - let mut state = app_state_clone_for_handler_perf.borrow_mut(); - if let Some(index) = state.selected_profile_index { - if let Some(profile) = state.config.game.get_mut(index) { - profile.performance_mode = active; - } - } - drop(state); - glib::Propagation::Proceed - }); - app_state.borrow_mut().performance_mode_switch_handler_id = Some(perf_handler_id); - - let app_state_clone_for_handler_hdr = app_state.clone(); - let hdr_handler_id = hdr_mode_switch.connect_state_set(move |_sw, active| { - let mut state = app_state_clone_for_handler_hdr.borrow_mut(); - if let Some(index) = state.selected_profile_index { - if let Some(profile) = state.config.game.get_mut(index) { - profile.hdr_mode = active; - } - } - drop(state); - glib::Propagation::Proceed - }); - app_state.borrow_mut().hdr_mode_switch_handler_id = Some(hdr_handler_id); - - let app_state_clone_for_handler_exp = app_state.clone(); - let exp_handler_id = experimental_present_mode_dropdown.connect_selected_item_notify(move |dropdown| { - let mut state = app_state_clone_for_handler_exp.borrow_mut(); - if let Some(index) = state.selected_profile_index { - if let Some(profile) = state.config.game.get_mut(index) { - let selected_text = dropdown.selected_item().and_then(|item| item.downcast_ref::().map(|s| s.string().to_string())); - if let Some(text) = selected_text { - profile.experimental_present_mode = text; - } - } - } - }); - app_state.borrow_mut().experimental_present_mode_dropdown_handler_id = Some(exp_handler_id); - - let app_state_clone_save = app_state.clone(); - save_button.connect_clicked(move |_| { - let state_ref = app_state_clone_save.borrow(); - if let Some(index) = state_ref.selected_profile_index { - let multiplier_str = state_ref.multiplier_dropdown.selected_item().and_then(|item| item.downcast_ref::().map(|s| s.string().to_string())); - let flow_scale_text = state_ref.flow_scale_entry.text().to_string(); - let performance_mode_active = state_ref.performance_mode_switch.is_active(); - let hdr_mode_active = state_ref.hdr_mode_switch.is_active(); - let exp_mode_str = state_ref.experimental_present_mode_dropdown.selected_item().and_then(|item| item.downcast_ref::().map(|s| s.string().to_string())); - - drop(state_ref); - - let mut state = app_state_clone_save.borrow_mut(); - if let Some(profile) = state.config.game.get_mut(index) { - if let Some(text) = multiplier_str { - profile.multiplier = if text == "off" { 1 } else { text.parse().unwrap_or(1) }; - } - - if let Ok(value) = flow_scale_text.parse::() { - profile.flow_scale = round_to_2_decimals(value); - } - - profile.performance_mode = performance_mode_active; - profile.hdr_mode = hdr_mode_active; - - if let Some(text) = exp_mode_str { - profile.experimental_present_mode = text; - } - - state.save_current_config(); - - let feedback_label = Label::new(Some("Saved!")); - feedback_label.set_halign(gtk::Align::End); - feedback_label.set_margin_end(12); - feedback_label.set_margin_bottom(12); - - let main_settings_box_clone = state.main_settings_box.clone(); - - main_settings_box_clone.append(&feedback_label); - - glib::timeout_add_local(std::time::Duration::new(2, 0), move || { - main_settings_box_clone.remove(&feedback_label); - glib::ControlFlow::Break - }); - } - } - }); - - let app_state_clone_initial = app_state.clone(); - glib::idle_add_local(move || { - let mut state = app_state_clone_initial.borrow_mut(); - if state.config.game.first().is_some() { - state.selected_profile_index = Some(0); - } - state.populate_sidebar_with_handlers(Some(app_state_clone_initial.clone())); - drop(state); - - if app_state_clone_initial.borrow().selected_profile_index.is_some() { - app_state_clone_initial.borrow().update_main_window_from_profile(); - } - glib::ControlFlow::Break - }); - - main_window.present(); - }); - - application.run() +pub mod wrapper { + pub mod ui; + pub mod pref; +} + +const APP_ID: &str = "gay.pancake.lsfg-vk.ConfigurationUi"; + +fn main() { + gio::resources_register_include!("ui.gresource") + .expect("Failed to register resources"); + + let app = adw::Application::builder() + .application_id(APP_ID) + .build(); + app.connect_activate(build_ui); + app.run(); +} + +fn build_ui(app: &adw::Application) { + let window = wrapper::ui::Window::new(app); + window.set_application(Some(app)); + window.present(); } diff --git a/ui/src/wrapper/pref.rs b/ui/src/wrapper/pref.rs new file mode 100644 index 0000000..34a6b7b --- /dev/null +++ b/ui/src/wrapper/pref.rs @@ -0,0 +1,49 @@ +use gtk::glib; +use gtk; +use adw; + +pub mod dropdown; +pub mod entry; +pub mod switch; + +glib::wrapper! { + pub struct PrefDropdown(ObjectSubclass) + @extends + adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +glib::wrapper! { + pub struct PrefSwitch(ObjectSubclass) + @extends + adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +glib::wrapper! { + pub struct PrefEntry(ObjectSubclass) + @extends + adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, + @implements + gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +impl PrefDropdown { + pub fn new() -> Self { + glib::Object::new() + } +} + +impl PrefSwitch { + pub fn new() -> Self { + glib::Object::new() + } +} + +impl PrefEntry { + pub fn new() -> Self { + glib::Object::new() + } +} diff --git a/ui/src/wrapper/pref/dropdown.rs b/ui/src/wrapper/pref/dropdown.rs new file mode 100644 index 0000000..3d87371 --- /dev/null +++ b/ui/src/wrapper/pref/dropdown.rs @@ -0,0 +1,44 @@ +use std::cell::RefCell; + +use gtk::glib; +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use adw::prelude::*; + +#[derive(gtk::CompositeTemplate, glib::Properties, Default)] +#[properties(wrapper_type = super::PrefDropdown)] +#[template(resource = "/gay/pancake/lsfg-vk/pref/dropdown.ui")] +pub struct PrefDropdown { + #[property(get, set)] + opt_name: RefCell, + #[property(get, set)] + default_selection: RefCell, + #[property(get, set)] + options: RefCell, +} + +#[glib::object_subclass] +impl ObjectSubclass for PrefDropdown { + const NAME: &'static str = "LSPrefDropdown"; + type Type = super::PrefDropdown; + type ParentType = adw::PreferencesRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for PrefDropdown { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for PrefDropdown {} +impl ListBoxRowImpl for PrefDropdown {} +impl PreferencesRowImpl for PrefDropdown {} diff --git a/ui/src/wrapper/pref/entry.rs b/ui/src/wrapper/pref/entry.rs new file mode 100644 index 0000000..e60dcd4 --- /dev/null +++ b/ui/src/wrapper/pref/entry.rs @@ -0,0 +1,44 @@ +use std::cell::RefCell; + +use gtk::glib; +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use adw::prelude::*; + +#[derive(gtk::CompositeTemplate, glib::Properties, Default)] +#[properties(wrapper_type = super::PrefEntry)] +#[template(resource = "/gay/pancake/lsfg-vk/pref/entry.ui")] +pub struct PrefEntry { + #[property(get, set)] + opt_name: RefCell, + #[property(get, set)] + default_text: RefCell, + #[property(get, set)] + tooltip_text: RefCell +} + +#[glib::object_subclass] +impl ObjectSubclass for PrefEntry { + const NAME: &'static str = "LSPrefEntry"; + type Type = super::PrefEntry; + type ParentType = adw::PreferencesRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for PrefEntry { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for PrefEntry {} +impl ListBoxRowImpl for PrefEntry {} +impl PreferencesRowImpl for PrefEntry {} diff --git a/ui/src/wrapper/pref/switch.rs b/ui/src/wrapper/pref/switch.rs new file mode 100644 index 0000000..222b2ad --- /dev/null +++ b/ui/src/wrapper/pref/switch.rs @@ -0,0 +1,42 @@ +use std::cell::RefCell; + +use gtk::glib; +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use adw::prelude::*; + +#[derive(gtk::CompositeTemplate, glib::Properties, Default)] +#[properties(wrapper_type = super::PrefSwitch)] +#[template(resource = "/gay/pancake/lsfg-vk/pref/switch.ui")] +pub struct PrefSwitch { + #[property(get, set)] + opt_name: RefCell, + #[property(get, set)] + default_state: RefCell, +} + +#[glib::object_subclass] +impl ObjectSubclass for PrefSwitch { + const NAME: &'static str = "LSPrefSwitch"; + type Type = super::PrefSwitch; + type ParentType = adw::PreferencesRow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +#[glib::derived_properties] +impl ObjectImpl for PrefSwitch { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for PrefSwitch {} +impl ListBoxRowImpl for PrefSwitch {} +impl PreferencesRowImpl for PrefSwitch {} diff --git a/ui/src/wrapper/ui.rs b/ui/src/wrapper/ui.rs new file mode 100644 index 0000000..d59f2f4 --- /dev/null +++ b/ui/src/wrapper/ui.rs @@ -0,0 +1,29 @@ +use gtk::glib; +use gtk; +use adw; +use gtk::glib::types::StaticTypeExt; + +pub mod window; + +glib::wrapper! { + pub struct Window(ObjectSubclass) + @extends + adw::ApplicationWindow, adw::Window, + gtk::ApplicationWindow, gtk::Window, gtk::Widget, + @implements + gtk::gio::ActionGroup, gtk::gio::ActionMap, + gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, + gtk::Native, gtk::Root, gtk::ShortcutManager; +} + +impl Window { + pub fn new(app: &adw::Application) -> Self { + super::pref::PrefDropdown::ensure_type(); + super::pref::PrefSwitch::ensure_type(); + super::pref::PrefEntry::ensure_type(); + + glib::Object::builder() + .property("application", app) + .build() + } +} diff --git a/ui/src/wrapper/ui/window.rs b/ui/src/wrapper/ui/window.rs new file mode 100644 index 0000000..975961d --- /dev/null +++ b/ui/src/wrapper/ui/window.rs @@ -0,0 +1,36 @@ +use gtk::subclass::prelude::*; +use adw::subclass::prelude::*; +use gtk::{glib, CompositeTemplate}; + +#[derive(CompositeTemplate, Default)] +#[template(resource = "/gay/pancake/lsfg-vk/window.ui")] +pub struct Window { + +} + +#[glib::object_subclass] +impl ObjectSubclass for Window { + const NAME: &'static str = "LSApplicationWindow"; + type Type = super::Window; + type ParentType = adw::ApplicationWindow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } +} + +impl ObjectImpl for Window { + fn constructed(&self) { + self.parent_constructed(); + } +} + +impl WidgetImpl for Window {} +impl WindowImpl for Window {} +impl ApplicationWindowImpl for Window {} +impl AdwWindowImpl for Window {} +impl AdwApplicationWindowImpl for Window {} From 3dba2a7a3d84757624fedebe60c62d67fa77d0d3 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Wed, 23 Jul 2025 20:28:06 +0200 Subject: [PATCH 204/253] ui: more templates and initial handler --- ui/rsc/pref/dropdown.ui | 4 +- ui/rsc/pref/entry.ui | 4 +- ui/rsc/pref/number.ui | 41 ++++++++++++++ ui/rsc/pref/slider.ui | 41 ++++++++++++++ ui/rsc/pref/switch.ui | 4 +- ui/rsc/resources.gresource.xml | 2 + ui/rsc/window.ui | 33 ++++------- ui/src/main.rs | 25 ++------- ui/src/ui.rs | 36 ++++++++++++ ui/src/{wrapper => ui}/pref.rs | 30 ++++++++++ ui/src/{wrapper => ui}/pref/dropdown.rs | 3 + ui/src/{wrapper => ui}/pref/entry.rs | 5 +- ui/src/ui/pref/number.rs | 43 +++++++++++++++ ui/src/ui/pref/slider.rs | 43 +++++++++++++++ ui/src/{wrapper => ui}/pref/switch.rs | 3 + ui/src/{wrapper => ui}/ui.rs | 0 ui/src/ui/ui/window.rs | 73 +++++++++++++++++++++++++ ui/src/wrapper/ui/window.rs | 36 ------------ 18 files changed, 339 insertions(+), 87 deletions(-) create mode 100644 ui/rsc/pref/number.ui create mode 100644 ui/rsc/pref/slider.ui create mode 100644 ui/src/ui.rs rename ui/src/{wrapper => ui}/pref.rs (60%) rename ui/src/{wrapper => ui}/pref/dropdown.rs (94%) rename ui/src/{wrapper => ui}/pref/entry.rs (91%) create mode 100644 ui/src/ui/pref/number.rs create mode 100644 ui/src/ui/pref/slider.rs rename ui/src/{wrapper => ui}/pref/switch.rs (94%) rename ui/src/{wrapper => ui}/ui.rs (100%) create mode 100644 ui/src/ui/ui/window.rs delete mode 100644 ui/src/wrapper/ui/window.rs diff --git a/ui/rsc/pref/dropdown.ui b/ui/rsc/pref/dropdown.ui index bce91aa..a3b38c7 100644 --- a/ui/rsc/pref/dropdown.ui +++ b/ui/rsc/pref/dropdown.ui @@ -11,7 +11,7 @@ 8 8 - + start @@ -19,7 +19,7 @@ - + 8 8 - + start @@ -19,7 +19,7 @@ - + + + + diff --git a/ui/rsc/pref/slider.ui b/ui/rsc/pref/slider.ui new file mode 100644 index 0000000..eb833db --- /dev/null +++ b/ui/rsc/pref/slider.ui @@ -0,0 +1,41 @@ + + + + diff --git a/ui/rsc/pref/switch.ui b/ui/rsc/pref/switch.ui index e9ecbde..e167001 100644 --- a/ui/rsc/pref/switch.ui +++ b/ui/rsc/pref/switch.ui @@ -11,7 +11,7 @@ 8 8 - + start @@ -19,7 +19,7 @@ - + compact diff --git a/ui/rsc/resources.gresource.xml b/ui/rsc/resources.gresource.xml index d9e189c..47fcd0e 100644 --- a/ui/rsc/resources.gresource.xml +++ b/ui/rsc/resources.gresource.xml @@ -3,7 +3,9 @@ window.ui pref/dropdown.ui + pref/number.ui pref/entry.ui + pref/slider.ui pref/switch.ui diff --git a/ui/rsc/window.ui b/ui/rsc/window.ui index 2c4c35a..8eb1025 100644 --- a/ui/rsc/window.ui +++ b/ui/rsc/window.ui @@ -1,6 +1,5 @@ -