diff --git a/lsfg-vk-backend/src/lsfgvk.cpp b/lsfg-vk-backend/src/lsfgvk.cpp index c5cc874..27b9321 100644 --- a/lsfg-vk-backend/src/lsfgvk.cpp +++ b/lsfg-vk-backend/src/lsfgvk.cpp @@ -1,7 +1,231 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ #include "lsfg-vk/lsfgvk.hpp" +#include "lsfgvk.hpp" +#include "modules/library.hpp" +#include "modules/pipeline.hpp" +#include "utility/pipelines.hpp" +#include "utility/vkhelper.hpp" +#include "vulkan/vulkan.hpp" + +#include +#include +#include +#include +#include +#include +#include using namespace lsfgvk; -/* TODO */ +Instance::Instance( + const std::string& deviceId, + const std::filesystem::path& lsfgvkDllPath, + bool allowFP16 +) { + // Create Vulkan context + vk::detail::DispatchLoaderDynamic dld{}; + + auto instance{vkhelper::createInstance(dld)}; + auto physdev{vkhelper::findPhysicalDevice(dld, *instance, deviceId)}; + + const uint32_t qfi{vkhelper::findComputeQueueFamilyIndex(dld, physdev)}; + const bool fp16{allowFP16 && vkhelper::checkHalfPrecisionSupport(dld, physdev)}; + + auto [device, queue] = vkhelper::createDevice(dld, physdev, qfi, fp16); + + // Construct instance + library::ShaderLibrary library{ + dld, + *device, + fp16, + lsfgvkDllPath + }; + + this->m_priv = std::make_unique(priv::Instance { + .vk = { + .dld = dld, + .instance = std::move(instance), + .physdev = physdev, + .device = std::move(device), + .queue = queue, + .qfi = qfi, + .fp16 = fp16 + }, + .shaderLibrary = std::move(library) + }); +} + +Context::Context( + const Instance& instance, + uint32_t width, + uint32_t height, + float flowScale, + bool performanceMode +) { + const auto& vk{instance.m_priv->vk}; + + pipeline::Pipeline pipeline{ + vk.dld, + *vk.device, + vk.physdev, + vk.queue, + vk.qfi, + instance.m_priv->shaderLibrary, + lsfgvk::getPipelineSignature(performanceMode), + { width, height }, + flowScale, + performanceMode, + false /* TODO: HDR */ + }; + + this->m_priv = std::make_unique(priv::Context { + .instance = std::ref(*instance.m_priv), + .pipeline = std::move(pipeline), + .syncSemaphore = { vkhelper::createTimelineSemaphore(vk.dld, *vk.device, true), 0 }, + .internalSemaphores = { vkhelper::createTimelineSemaphore(vk.dld, *vk.device), 0 }, + .fence = vkhelper::createFence(vk.dld, *vk.device), + }); +} + +FileDescriptors Context::exportFds() const { + const auto& vk{this->m_priv->instance.get().vk}; + const auto& pipeline{this->m_priv->pipeline}; + + return{ + .sourceFd = vkhelper::exportMemoryFd( + vk.dld, *vk.device, + pipeline.getExternalInputs().front().memory + ), + .destinationFd = vkhelper::exportMemoryFd( + vk.dld, *vk.device, + pipeline.getExternalOutputs().front().memory + ), + .syncFd = vkhelper::exportSemaphoreFd( + vk.dld, *vk.device, + *this->m_priv->syncSemaphore.first + ) + }; +} + +void Context::dispatch(uint32_t total) { + auto& ctx{*this->m_priv}; + const auto& vk{ctx.instance.get().vk}; + + // Increment iteration counter after previous frame is completed + auto* mapped{ctx.pipeline.getMappedBuffer()}; + if (ctx.firstIteration) { + ctx.firstIteration = false; + mapped->iteration = 0; + } else { + if (vk.device->waitForFences(*ctx.fence, true, UINT64_MAX, vk.dld) != vk::Result::eSuccess) + throw std::runtime_error("Unable to wait for completion of previous iteration"); + vk.device->resetFences(*ctx.fence, vk.dld); + mapped->iteration++; + } + + const auto& cmdbufs{ctx.pipeline.getCmdbufs()}; + + // Dispatch pre-pass + auto& sync{ctx.syncSemaphore}; + sync.second++; + + auto& internal{ctx.internalSemaphores}; + internal.second++; + + vk::TimelineSemaphoreSubmitInfo timelineInfo{ + .waitSemaphoreValueCount = 1, + .pWaitSemaphoreValues = &sync.second, + .signalSemaphoreValueCount = 1, + .pSignalSemaphoreValues = &internal.second + }; + + const vk::PipelineStageFlags waitStage{vk::PipelineStageFlagBits::eTopOfPipe}; + vk.queue.submit( + {{ + .pNext = &timelineInfo, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*sync.first, + .pWaitDstStageMask = &waitStage, + .commandBufferCount = 1U, + .pCommandBuffers = &*cmdbufs.at(0), + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*internal.first + }}, + nullptr, + vk.dld + ); + + // Dispatch main passes + uint64_t prevInternal{}; + for (uint32_t i = 0; i < total; i++) { + const auto& transCmdbuf{ctx.pipeline.getCmdbufs().at(0)}; // FIXME: replace with actual buf + + // Transition command buffer to next timestamp + if (i == 0) { + prevInternal = internal.second; + timelineInfo.pWaitSemaphoreValues = &prevInternal; + } else { + sync.second++; + timelineInfo.pWaitSemaphoreValues = &sync.second; + } + + internal.second++; + timelineInfo.pSignalSemaphoreValues = &internal.second; + + vk.queue.submit( + {{ + .pNext = &timelineInfo, + .waitSemaphoreCount = 1, + .pWaitSemaphores = i == 0 ? &*internal.first : &*sync.first, + .pWaitDstStageMask = &waitStage, + .commandBufferCount = 1, + .pCommandBuffers = &*transCmdbuf, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*internal.first + }}, + nullptr, + vk.dld + ); + + // Dispatch main pass + timelineInfo.pWaitSemaphoreValues = &internal.second; + + sync.second++; + timelineInfo.pSignalSemaphoreValues = &sync.second; + + vk.queue.submit( + {{ + .pNext = &timelineInfo, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*internal.first, + .pWaitDstStageMask = &waitStage, + .commandBufferCount = 1, + .pCommandBuffers = &*cmdbufs.at(1), + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*sync.first + }}, + i == (total - 1) ? *ctx.fence : nullptr, + vk.dld + ); + } +} + +void Context::idle() const { + const auto& ctx{*this->m_priv}; + const auto& vk{ctx.instance.get().vk}; + + vk.device->waitIdle(vk.dld); +} + +Context::~Context() { + try { + // NOTE: This will freeze if the user didn't signal the sync semaphore high enough to + // allow the pipeline to complete. + this->idle(); + } catch (...) { // NOLINT (empty catch) + // Not much we can do here.. + } +} + +Instance::~Instance() = default; diff --git a/lsfg-vk-backend/src/lsfgvk.hpp b/lsfg-vk-backend/src/lsfgvk.hpp new file mode 100644 index 0000000..31a84e9 --- /dev/null +++ b/lsfg-vk-backend/src/lsfgvk.hpp @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#pragma once + +#include "modules/pipeline.hpp" +#include "modules/library.hpp" +#include "utility/vkhelper.hpp" + +#include +#include +#include + +namespace lsfgvk::priv { + + + /// Internal state of lsfg-vk + struct Instance { + /// Vulkan context + struct Vulkan { + /// Vulkan dispatch loader + vk::detail::DispatchLoaderDynamic dld; + /// Vulkan instance (1.2) + vk::UniqueInstance instance; + /// Vulkan physical device + vk::PhysicalDevice physdev; + /// Vulkan device with synchronization2 (extension), external memory & semaphore + /// fd (extension) and timeline semaphores (core) enabled + vk::UniqueDevice device; + /// Compute queue + vk::Queue queue; + /// Compute queue family index + uint32_t qfi; + /// Whether fp16 is enabled and supported (shaderFloat16 is enabled) + bool fp16; + } vk; + /// Shader library + library::ShaderLibrary shaderLibrary; + }; + + /// Internal context for frame generation + struct Context { + /// Parent instance + std::reference_wrapper instance; + /// Pipeline instance + pipeline::Pipeline pipeline; + /// Shared synchronization semaphores + std::pair syncSemaphore; + /// Internal synchronization semaphores + std::pair internalSemaphores; + /// Frames-in-flight fence + vk::UniqueFence fence; + /// Is first iteration + bool firstIteration{true}; + }; + +} diff --git a/lsfg-vk-backend/src/utility/vkhelper.cpp b/lsfg-vk-backend/src/utility/vkhelper.cpp index cd61a74..44e63a0 100644 --- a/lsfg-vk-backend/src/utility/vkhelper.cpp +++ b/lsfg-vk-backend/src/utility/vkhelper.cpp @@ -470,6 +470,24 @@ vk::UniqueCommandBuffer vkhelper::createCommandBuffer( return { std::move(device.allocateCommandBuffersUnique(cmdbufInfo, dld).front()) }; } +vk::UniqueSemaphore vkhelper::createTimelineSemaphore( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + bool exportable +) { + const vk::ExportSemaphoreCreateInfo exportInfo{ + .handleTypes = vk::ExternalSemaphoreHandleTypeFlagBits::eOpaqueFd + }; + const vk::SemaphoreTypeCreateInfo typeInfo{ + .pNext = exportable ? &exportInfo : nullptr, + .semaphoreType = vk::SemaphoreType::eTimeline, + }; + const vk::SemaphoreCreateInfo createInfo{ + .pNext = &typeInfo, + }; + return device.createSemaphoreUnique(createInfo, nullptr, dld); +} + vk::UniqueFence vkhelper::createFence( const vk::detail::DispatchLoaderDynamic& dld, const vk::Device& device @@ -549,3 +567,27 @@ std::pair vkhelper::createExternalImage std::move(memory) }; } + +int vkhelper::exportMemoryFd( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::DeviceMemory& memory +) { + const vk::MemoryGetFdInfoKHR fdInfo{ + .memory = memory, + .handleType = vk::ExternalMemoryHandleTypeFlagBits::eOpaqueFd + }; + return device.getMemoryFdKHR(fdInfo, dld); +} + +int vkhelper::exportSemaphoreFd( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::Semaphore& semaphore +) { + const vk::SemaphoreGetFdInfoKHR fdInfo{ + .semaphore = semaphore, + .handleType = vk::ExternalSemaphoreHandleTypeFlagBits::eOpaqueFd + }; + return device.getSemaphoreFdKHR(fdInfo, dld); +} diff --git a/lsfg-vk-backend/src/utility/vkhelper.hpp b/lsfg-vk-backend/src/utility/vkhelper.hpp index a5136a3..5d3aec3 100644 --- a/lsfg-vk-backend/src/utility/vkhelper.hpp +++ b/lsfg-vk-backend/src/utility/vkhelper.hpp @@ -338,6 +338,21 @@ namespace vkhelper { const vk::CommandPool& cmdpool ); + /// + /// Create a timeline semaphore + /// + /// @param dld Dynamic dispatch loader + /// @param device Vulkan device + /// @param exportable Whether the semaphore should be exportable as a fd + /// @return RAII-wrapped Vulkan semaphore + /// @throws std::runtime_error on failure + /// + vk::UniqueSemaphore createTimelineSemaphore( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + bool exportable = false + ); + /// /// Create a fence /// @@ -376,4 +391,34 @@ namespace vkhelper { vk::ImageUsageFlags usage ); + /// + /// Export a Vulkan memory allocation as a fd + /// + /// @param dld Dynamic dispatch loader + /// @param device Vulkan device + /// @param memory Vulkan device memory + /// @return File descriptor to the allocation + /// @throws std::runtime_error on failure + /// + int exportMemoryFd( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::DeviceMemory& memory + ); + + /// + /// Export a Vulkan semaphore as a fd + /// + /// @param dld Dynamic dispatch loader + /// @param device Vulkan device + /// @param semaphore Vulkan semaphore + /// @return File descriptor to the semaphore + /// @throws std::runtime_error on failure + /// + int exportSemaphoreFd( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::Semaphore& semaphore + ); + }