refactor(cleanup): implement basic/none frame pacing

This commit is contained in:
PancakeTAS 2025-12-19 18:03:03 +01:00
parent 7e936784ab
commit e0fac3e5a9
15 changed files with 476 additions and 654 deletions

View file

@ -1,80 +0,0 @@
#pragma once
#include "hooks.hpp"
#include "mini/commandbuffer.hpp"
#include "mini/commandpool.hpp"
#include "mini/image.hpp"
#include "mini/semaphore.hpp"
#include <vulkan/vulkan_core.h>
#include <array>
#include <cstdint>
#include <memory>
#include <vector>
///
/// This class is the frame generation context. There should be one instance per swapchain.
///
class LsContext {
public:
///
/// Create the swapchain context.
///
/// @param info The device information to use.
/// @param swapchain The Vulkan swapchain to use.
/// @param extent The extent of the swapchain images.
/// @param swapchainImages The swapchain images to use.
///
/// @throws LSFG::vulkan_error if any Vulkan call fails.
///
LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain,
VkExtent2D extent, const std::vector<VkImage>& swapchainImages);
///
/// Custom present logic.
///
/// @param info The device information to use.
/// @param pNext Unknown pointer set in the present info structure.
/// @param queue The Vulkan queue to present the frame on.
/// @param gameRenderSemaphores The semaphores to wait on before presenting.
/// @param presentIdx The index of the swapchain image to present.
/// @return The result of the Vulkan present operation, which can be VK_SUCCESS or VK_SUBOPTIMAL_KHR.
///
/// @throws LSFG::vulkan_error if any Vulkan call fails.
///
VkResult present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue,
const std::vector<VkSemaphore>& gameRenderSemaphores, uint32_t presentIdx);
// Non-copyable, trivially moveable and destructible
LsContext(const LsContext&) = delete;
LsContext& operator=(const LsContext&) = delete;
LsContext(LsContext&&) = default;
LsContext& operator=(LsContext&&) = default;
~LsContext() = default;
private:
VkSwapchainKHR swapchain;
std::vector<VkImage> swapchainImages;
VkExtent2D extent;
std::shared_ptr<int32_t> lsfgCtxId; // lsfg context id
Mini::Image frame_0, frame_1; // frames shared with lsfg. write to frame_0 when fc % 2 == 0
std::vector<Mini::Image> out_n; // output images shared with lsfg, indexed by framegen id
Mini::CommandPool cmdPool;
uint64_t frameIdx{0};
struct RenderPassInfo {
Mini::CommandBuffer preCopyBuf; // copy from swapchain image to frame_0/frame_1
std::array<Mini::Semaphore, 2> preCopySemaphores; // signal when preCopyBuf is done
std::vector<Mini::Semaphore> renderSemaphores; // signal when lsfg is done with frame n
std::vector<Mini::Semaphore> acquireSemaphores; // signal for swapchain image n
std::vector<Mini::CommandBuffer> postCopyBufs; // copy from out_n to swapchain image
std::vector<Mini::Semaphore> postCopySemaphores; // signal when postCopyBuf is done
std::vector<Mini::Semaphore> prevPostCopySemaphores; // signal for previous postCopyBuf
}; // data for a single render pass
std::array<RenderPassInfo, 8> passInfos; // allocate 8 because why not
};

View file

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

View file

@ -528,8 +528,8 @@ void Context::scheduleFrames() {
this->beta1.render(ctx.vk, cmdbuf);
cmdbuf.submit(this->ctx.vk,
this->syncSemaphore, this->idx,
this->prepassSemaphore, this->idx
{}, this->syncSemaphore.handle(), this->idx,
{}, this->prepassSemaphore.handle(), this->idx
);
this->idx++;
@ -551,8 +551,8 @@ void Context::scheduleFrames() {
pass.generate->render(ctx.vk, cmdbuf, this->fidx);
cmdbuf.submit(this->ctx.vk,
this->prepassSemaphore, this->idx - 1,
this->syncSemaphore, this->idx + i
{}, this->prepassSemaphore.handle(), this->idx - 1,
{}, this->syncSemaphore.handle(), this->idx + i
);
}

View file

@ -5,11 +5,11 @@
#include "descriptor_set.hpp"
#include "image.hpp"
#include "shader.hpp"
#include "timeline_semaphore.hpp"
#include "vulkan.hpp"
#include <cstdint>
#include <optional>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
@ -34,6 +34,18 @@ namespace vk {
const vk::Image& image,
const std::optional<VkClearColorValue>& clearColor = std::nullopt) const;
/// blit an image
/// @param vk the vulkan instance
/// @param preBarriers image memory barriers to apply before blit
/// @param images source and destination images
/// @param extent the extent of the blit
/// @param postBarriers image memory barriers to apply after blit
/// throws ls::vulkan_error on failure
void blitImage(const vk::Vulkan& vk,
const std::vector<vk::Barrier>& preBarriers,
std::pair<VkImage, VkImage> images, VkExtent2D extent,
const std::vector<vk::Barrier>& postBarriers) const;
/// dispatch a compute shader
/// @param vk the vulkan instance
/// @param shader the compute shader
@ -44,7 +56,7 @@ namespace vk {
/// @param z dispatch size in Z
void dispatch(const vk::Vulkan& vk, const vk::Shader& shader, const vk::DescriptorSet& set,
const std::vector<vk::Barrier>& barriers,
uint32_t x, uint32_t y, uint32_t z) const;
uint32_t x, uint32_t y, uint32_t z) const;
/// copy buffer to image
/// @param vk the vulkan instance
@ -55,14 +67,18 @@ namespace vk {
/// submit the command buffer
/// @param vk the vulkan instance
/// @param waitSemaphore the semaphore to wait on
/// @param waitSemaphores the semaphores to wait on
/// @param waitTimelineSemaphore the timeline semaphore to wait on
/// @param waitValue the value to wait for
/// @param signalSemaphore the semaphore to signal
/// @param signalSemaphores the semaphores to signal
/// @param signalTimelineSemaphore the timeline semaphore to signal
/// @param signalValue the value to signal
/// @throws ls::vulkan_error on failure
void submit(const vk::Vulkan& vk,
const vk::TimelineSemaphore& waitSemaphore, uint64_t waitValue,
const vk::TimelineSemaphore& signalSemaphore, uint64_t signalValue) const;
std::vector<VkSemaphore> waitSemaphores,
VkSemaphore waitTimelineSemaphore, uint64_t waitValue,
std::vector<VkSemaphore> signalSemaphores,
VkSemaphore signalTimelineSemaphore, uint64_t signalValue) const;
/// submit the command buffer instantly
/// @param vk the vulkan instance

View file

@ -16,6 +16,10 @@ namespace vk {
/// @param fd optional file descriptor to import the semaphore from
/// @throws ls::vulkan_error on failure
Semaphore(const vk::Vulkan& vk, std::optional<int> fd = std::nullopt);
/// get the underlying VkSemaphore handle
/// @return the VkSemaphore handle
[[nodiscard]] const auto& handle() const { return *this->semaphore; }
private:
ls::owned_ptr<VkSemaphore> semaphore;
};

View file

@ -66,6 +66,7 @@ namespace vk {
PFN_vkBeginCommandBuffer BeginCommandBuffer;
PFN_vkEndCommandBuffer EndCommandBuffer;
PFN_vkCmdPipelineBarrier CmdPipelineBarrier;
PFN_vkCmdBlitImage CmdBlitImage;
PFN_vkCmdClearColorImage CmdClearColorImage;
PFN_vkCmdBindPipeline CmdBindPipeline;
PFN_vkCmdBindDescriptorSets CmdBindDescriptorSets;
@ -106,6 +107,8 @@ namespace vk {
PFN_vkGetSemaphoreFdKHR GetSemaphoreFdKHR;
PFN_vkCreateSwapchainKHR CreateSwapchainKHR;
PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR;
PFN_vkAcquireNextImageKHR AcquireNextImageKHR;
PFN_vkQueuePresentKHR QueuePresentKHR;
PFN_vkDestroySwapchainKHR DestroySwapchainKHR;
};

View file

@ -6,11 +6,11 @@
#include "lsfg-vk-common/vulkan/fence.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/shader.hpp"
#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstdint>
#include <optional>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
@ -122,6 +122,54 @@ void CommandBuffer::dispatch(const vk::Vulkan& vk,
vk.df().CmdDispatch(*this->commandBuffer, x, y, z);
}
void CommandBuffer::blitImage(const vk::Vulkan& vk,
const std::vector<vk::Barrier>& preBarriers,
std::pair<VkImage, VkImage> images, VkExtent2D extent,
const std::vector<vk::Barrier>& postBarriers) const {
vk.df().CmdPipelineBarrier(*this->commandBuffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
0, nullptr,
static_cast<uint32_t>(preBarriers.size()), preBarriers.data()
);
const VkImageBlit region{
.srcSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.layerCount = 1
},
.srcOffsets = {
{ 0, 0, 0 },
{ static_cast<int32_t>(extent.width),
static_cast<int32_t>(extent.height), 1 }
},
.dstSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.layerCount = 1
},
.dstOffsets = {
{ 0, 0, 0 },
{ static_cast<int32_t>(extent.width),
static_cast<int32_t>(extent.height), 1 }
}
};
vk.df().CmdBlitImage(*this->commandBuffer,
images.first, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
images.second, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &region,
VK_FILTER_NEAREST
);
vk.df().CmdPipelineBarrier(*this->commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
0, nullptr,
0, nullptr,
static_cast<uint32_t>(postBarriers.size()), postBarriers.data()
);
}
void CommandBuffer::copyBufferToImage(const vk::Vulkan& vk,
const vk::Buffer& buffer, const vk::Image& image) const {
const VkImageMemoryBarrier barrier{
@ -163,33 +211,48 @@ void CommandBuffer::copyBufferToImage(const vk::Vulkan& vk,
);
}
void CommandBuffer::submit(const vk::Vulkan& vk,
const vk::TimelineSemaphore& waitSemaphore, uint64_t waitValue,
const vk::TimelineSemaphore& signalSemaphore, uint64_t signalValue) const {
std::vector<VkSemaphore> waitSemaphores,
VkSemaphore waitTimelineSemaphore, uint64_t waitValue,
std::vector<VkSemaphore> signalSemaphores,
VkSemaphore signalTimelineSemaphore, uint64_t signalValue) const {
auto res = vk.df().EndCommandBuffer(*this->commandBuffer);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkEndCommandBuffer() failed");
// create arrays of semaphores and values
if (waitTimelineSemaphore)
waitSemaphores.push_back(waitTimelineSemaphore);
std::vector<uint64_t> waitValues(waitSemaphores.size(), 0);
waitValues.back() = waitValue;
if (signalTimelineSemaphore)
signalSemaphores.push_back(signalTimelineSemaphore);
std::vector<uint64_t> signalValues(signalSemaphores.size(), 0);
signalValues.back() = signalValue;
// create submit info
const VkTimelineSemaphoreSubmitInfo timelineInfo{
.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
.waitSemaphoreValueCount = 1,
.pWaitSemaphoreValues = &waitValue,
.signalSemaphoreValueCount = 1,
.pSignalSemaphoreValues = &signalValue
.waitSemaphoreValueCount = static_cast<uint32_t>(waitValues.size()),
.pWaitSemaphoreValues = waitValues.data(),
.signalSemaphoreValueCount = static_cast<uint32_t>(signalValues.size()),
.pSignalSemaphoreValues = signalValues.data()
};
const VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
std::vector<VkPipelineStageFlags> stages(waitSemaphores.size(),
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
const VkSubmitInfo submitInfo{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = &timelineInfo,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &waitSemaphore.handle(),
.pWaitDstStageMask = &stage,
.waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size()),
.pWaitSemaphores = waitSemaphores.data(),
.pWaitDstStageMask = stages.data(),
.commandBufferCount = 1,
.pCommandBuffers = &*this->commandBuffer,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &signalSemaphore.handle()
.signalSemaphoreCount = static_cast<uint32_t>(signalSemaphores.size()),
.pSignalSemaphores = signalSemaphores.data()
};
res = vk.df().QueueSubmit(vk.queue(), 1, &submitInfo, VK_NULL_HANDLE);
if (res != VK_SUCCESS)

View file

@ -320,6 +320,7 @@ VulkanDeviceFuncs vk::initVulkanDeviceFuncs(const VulkanInstanceFuncs& f, VkDevi
.BeginCommandBuffer = dpa<PFN_vkBeginCommandBuffer>(f, d, "vkBeginCommandBuffer"),
.EndCommandBuffer = dpa<PFN_vkEndCommandBuffer>(f, d, "vkEndCommandBuffer"),
.CmdPipelineBarrier = dpa<PFN_vkCmdPipelineBarrier>(f, d, "vkCmdPipelineBarrier"),
.CmdBlitImage = dpa<PFN_vkCmdBlitImage>(f, d, "vkCmdBlitImage"),
.CmdClearColorImage = dpa<PFN_vkCmdClearColorImage>(f, d, "vkCmdClearColorImage"),
.CmdBindPipeline = dpa<PFN_vkCmdBindPipeline>(f, d, "vkCmdBindPipeline"),
.CmdBindDescriptorSets = dpa<PFN_vkCmdBindDescriptorSets>(f, d, "vkCmdBindDescriptorSets"),
@ -367,6 +368,10 @@ VulkanDeviceFuncs vk::initVulkanDeviceFuncs(const VulkanInstanceFuncs& f, VkDevi
dpa<PFN_vkCreateSwapchainKHR>(f, d, "vkCreateSwapchainKHR") : nullptr,
.GetSwapchainImagesKHR = graphical ?
dpa<PFN_vkGetSwapchainImagesKHR>(f, d, "vkGetSwapchainImagesKHR") : nullptr,
.AcquireNextImageKHR = graphical ?
dpa<PFN_vkAcquireNextImageKHR>(f, d, "vkAcquireNextImageKHR") : nullptr,
.QueuePresentKHR = graphical ?
dpa<PFN_vkQueuePresentKHR>(f, d, "vkQueuePresentKHR") : nullptr,
.DestroySwapchainKHR = graphical ?
dpa<PFN_vkDestroySwapchainKHR>(f, d, "vkDestroySwapchainKHR") : nullptr
};

View file

@ -63,11 +63,7 @@ namespace {
cmdbuf.copyBufferToImage(vk, stagingbuf, image);
const vk::TimelineSemaphore sema{vk, 0};
cmdbuf.submit(vk, sema, 1, sema, 2);
sema.signal(vk, 1);
if (!sema.wait(vk, 2))
throw std::runtime_error("image upload failed");
cmdbuf.submit(vk);
}
}

View file

@ -52,7 +52,7 @@ Identification layer::identify() {
if (!line.ends_with(".exe"))
continue;
size_t pos = line.find_first_of('/');
size_t pos = line.find_last_of('/');
if (pos == std::string::npos) {
pos = line.find_last_of(' ');
if (pos == std::string::npos)

View file

@ -1,16 +1,51 @@
#include "swapchain.hpp"
#include "../configuration/config.hpp"
#include "lsfg-vk-backend/lsfgvk.hpp"
#include "lsfg-vk-common/helpers/errors.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/semaphore.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <utility>
#include <vector>
#include <iostream>
#include <vulkan/vulkan_core.h>
using namespace lsfgvk;
using namespace lsfgvk::layer;
namespace {
VkImageMemoryBarrier barrierHelper(VkImage handle,
VkAccessFlags srcAccessMask,
VkAccessFlags dstAccessMask,
VkImageLayout oldLayout,
VkImageLayout newLayout) {
return VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = srcAccessMask,
.dstAccessMask = dstAccessMask,
.oldLayout = oldLayout,
.newLayout = newLayout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = handle,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1
}
};
}
}
void layer::context_ModifySwapchainCreateInfo(const GameConf& profile, uint32_t maxImages,
VkSwapchainCreateInfoKHR& createInfo) {
createInfo.imageUsage |=
@ -28,6 +63,224 @@ void layer::context_ModifySwapchainCreateInfo(const GameConf& profile, uint32_t
}
Swapchain::Swapchain(const vk::Vulkan& vk, lsfgvk::Instance& backend,
const GameConf& profile, const SwapchainInfo& info) {
std::cerr << "lsfg-vk: swapchain created :3\n";
GameConf profile, SwapchainInfo info) :
instance(backend),
profile(std::move(profile)), info(std::move(info)) {
const VkExtent2D extent = this->info.extent;
const bool hdr = this->info.format > 57;
std::vector<int> sourceFds(2);
std::vector<int> destinationFds(this->profile.multiplier - 1);
this->sourceImages.reserve(sourceFds.size());
for (int& fd : sourceFds)
this->sourceImages.emplace_back(vk,
extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
std::nullopt, &fd);
this->destinationImages.reserve(destinationFds.size());
for (int& fd : destinationFds)
this->destinationImages.emplace_back(vk,
extent, hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
std::nullopt, &fd);
int syncFd{};
this->syncSemaphore.emplace(vk, 0, std::nullopt, &syncFd);
this->ctx = ls::owned_ptr<ls::R<lsfgvk::Context>>(
new ls::R(backend.openContext(
{ sourceFds.at(0), sourceFds.at(1) }, destinationFds, syncFd,
extent.width, extent.height,
hdr, this->profile.flow_scale, this->profile.performance_mode
)),
[backend = &backend](ls::R<lsfgvk::Context>& ctx) {
backend->closeContext(ctx);
}
);
this->renderCommandBuffer.emplace(vk);
this->renderSemaphore.emplace(vk, 0);
for (size_t i = 0; i < this->destinationImages.size(); i++) {
this->passes.emplace_back(RenderPass {
.commandBuffer = vk::CommandBuffer(vk),
.acquireSemaphore = vk::Semaphore(vk),
.postCopySemaphore = {
vk::Semaphore(vk),
vk::Semaphore(vk)
}
});
}
}
VkResult Swapchain::present(const vk::Vulkan& vk,
VkQueue queue, VkSwapchainKHR swapchain,
void* next_chain, uint32_t imageIdx,
const std::vector<VkSemaphore>& semaphores) {
const auto& swapchainImage = this->info.images.at(imageIdx);
const auto& sourceImage = this->sourceImages.at(this->fidx++ % 2);
// schedule frame generation
this->instance.get().scheduleFrames(this->ctx.get());
// update present mode when not using pacing
if (this->profile.pacing == Pacing::None) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
auto* info = reinterpret_cast<VkSwapchainPresentModeInfoEXT*>(next_chain);
while (info) {
if (info->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT) {
for (size_t i = 0; i < info->swapchainCount; i++)
const_cast<VkPresentModeKHR*>(info->pPresentModes)[i] = // NOLINT
VK_PRESENT_MODE_FIFO_KHR;
}
info = reinterpret_cast<VkSwapchainPresentModeInfoEXT*>(const_cast<void*>(info->pNext));
}
#pragma clang diagnostic pop
}
// wait for completion of previous frame
auto no_timeout = this->renderSemaphore->wait(vk, this->idx - 1, 150UL * 1000 * 1000); // 150ms
if (!no_timeout) throw ls::vulkan_error(VK_TIMEOUT, "vkWaitSemaphores() failed");
// copy swapchain image into backend source image
auto& cmdbuf = this->renderCommandBuffer.emplace(vk);
cmdbuf.blitImage(vk,
{
barrierHelper(swapchainImage,
VK_ACCESS_NONE,
VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
),
barrierHelper(sourceImage.handle(),
VK_ACCESS_NONE,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
),
},
{ swapchainImage, sourceImage.handle() },
sourceImage.getExtent(),
{
barrierHelper(swapchainImage,
VK_ACCESS_TRANSFER_READ_BIT,
VK_ACCESS_MEMORY_READ_BIT,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
),
}
);
cmdbuf.submit(vk,
semaphores, nullptr, 0,
{}, this->syncSemaphore->handle(), this->idx++
);
for (size_t i = 0; i < this->destinationImages.size(); i++) {
auto& destinationImage = this->destinationImages.at(i);
auto& pass = this->passes.at(i);
pass = RenderPass {
.commandBuffer = vk::CommandBuffer(vk),
.acquireSemaphore = vk::Semaphore(vk),
.postCopySemaphore = {
vk::Semaphore(vk),
vk::Semaphore(vk)
}
};
// acquire swapchain image
uint32_t aqImageIdx{};
auto res = vk.df().AcquireNextImageKHR(vk.dev(), swapchain,
UINT64_MAX, pass.acquireSemaphore.handle(),
VK_NULL_HANDLE,
&aqImageIdx
);
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
throw ls::vulkan_error(res, "vkAcquireNextImageKHR() failed");
const auto& aquiredSwapchainImage = this->info.images.at(aqImageIdx);
// copy backend destination image into swapchain image
auto& cmdbuf = pass.commandBuffer;
cmdbuf.blitImage(vk,
{
barrierHelper(destinationImage.handle(),
VK_ACCESS_NONE,
VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
),
barrierHelper(aquiredSwapchainImage,
VK_ACCESS_NONE,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
),
},
{ destinationImage.handle(), aquiredSwapchainImage },
destinationImage.getExtent(),
{
barrierHelper(aquiredSwapchainImage,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_MEMORY_READ_BIT,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
),
}
);
std::vector<VkSemaphore> waitSemaphores{ pass.acquireSemaphore.handle() };
if (i) { // non-first pass
const auto& prevPass = this->passes.at(i - 1);
waitSemaphores.push_back(prevPass.postCopySemaphore.second.handle());
}
const std::vector<VkSemaphore> signalSemaphores{
pass.postCopySemaphore.first.handle(),
pass.postCopySemaphore.second.handle()
};
cmdbuf.submit(vk,
waitSemaphores, this->syncSemaphore->handle(), this->idx,
signalSemaphores, this->renderSemaphore->handle(), this->idx
);
// present swapchain image
const VkPresentInfoKHR presentInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pNext = i ? nullptr : next_chain,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &pass.postCopySemaphore.first.handle(),
.swapchainCount = 1,
.pSwapchains = &swapchain,
.pImageIndices = &aqImageIdx,
};
res = vk.df().QueuePresentKHR(queue,
&presentInfo);
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
throw ls::vulkan_error(res, "vkQueuePresentKHR() failed");
this->idx++;
}
// present original swapchain image
const auto& lastPass = this->passes.at(this->destinationImages.size() - 1);
const VkPresentInfoKHR presentInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &lastPass.postCopySemaphore.second.handle(),
.swapchainCount = 1,
.pSwapchains = &swapchain,
.pImageIndices = &imageIdx,
};
auto res = vk.df().QueuePresentKHR(queue, &presentInfo);
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
throw ls::vulkan_error(res, "vkQueuePresentKHR() failed");
return res;
}

View file

@ -2,8 +2,15 @@
#include "../configuration/config.hpp"
#include "lsfg-vk-backend/lsfgvk.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/command_buffer.hpp"
#include "lsfg-vk-common/vulkan/image.hpp"
#include "lsfg-vk-common/vulkan/semaphore.hpp"
#include "lsfg-vk-common/vulkan/timeline_semaphore.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstdint>
#include <optional>
#include <vector>
#include <vulkan/vulkan_core.h>
@ -31,12 +38,44 @@ namespace lsfgvk::layer {
public:
/// create a new swapchain context
/// @param vk vulkan instance
/// @param profile active game profile
/// @param backend lsfg-vk backend instance
/// @param profile active game profile
/// @param info swapchain info
Swapchain(const vk::Vulkan& vk, lsfgvk::Instance& backend,
const GameConf& profile, const SwapchainInfo& info);
GameConf profile, SwapchainInfo info);
/// present a frame
/// @param vk vulkan instance
/// @param queue presentation queue
/// @param next_chain next chain pointer for the present info (WARN: shared!)
/// @param imageIdx swapchain image index to present to
/// @param semaphores semaphores to wait on before presenting
/// @throws ls::vulkan_error on vulkan errors
VkResult present(const vk::Vulkan& vk,
VkQueue queue, VkSwapchainKHR swapchain,
void* next_chain, uint32_t imageIdx,
const std::vector<VkSemaphore>& semaphores);
private:
std::vector<vk::Image> sourceImages;
std::vector<vk::Image> destinationImages;
ls::lazy<vk::TimelineSemaphore> syncSemaphore;
std::optional<vk::CommandBuffer> renderCommandBuffer;
ls::lazy<vk::TimelineSemaphore> renderSemaphore;
struct RenderPass {
vk::CommandBuffer commandBuffer;
vk::Semaphore acquireSemaphore;
std::pair<vk::Semaphore, vk::Semaphore> postCopySemaphore;
};
std::vector<RenderPass> passes;
ls::R<lsfgvk::Instance> instance;
ls::owned_ptr<ls::R<lsfgvk::Context>> ctx;
size_t idx{1};
size_t fidx{0}; // real frame index
GameConf profile;
SwapchainInfo info;
};
}

View file

@ -1,7 +1,9 @@
#include "context/instance.hpp"
#include "lsfg-vk-common/helpers/errors.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include <cstddef>
#include <cstdint>
#include <exception>
#include <iostream>
@ -30,6 +32,7 @@ namespace {
vk::VulkanInstanceFuncs funcs;
std::unordered_map<VkDevice, vk::Vulkan> devices;
std::unordered_map<VkSwapchainKHR, ls::R<vk::Vulkan>> swapchains;
}* instance_info;
// create instance
@ -276,8 +279,13 @@ namespace {
try {
// retire old swapchain
if (info->oldSwapchain)
if (info->oldSwapchain) {
const auto& mapping = instance_info->swapchains.find(info->oldSwapchain);
if (mapping != instance_info->swapchains.end())
instance_info->swapchains.erase(mapping);
layer_info->root.removeSwapchainContext(info->oldSwapchain);
}
layer_info->root.update(); // ensure config is up to date
@ -314,6 +322,9 @@ namespace {
.presentMode = newInfo.presentMode
});
instance_info->swapchains.emplace(*swapchain,
ls::R<vk::Vulkan>(it->second));
return res;
} catch (const ls::vulkan_error& e) {
std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain creation:\n";
@ -326,6 +337,51 @@ namespace {
}
}
VkResult myvkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* info) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
VkResult result = VK_SUCCESS;
// present each swapchain
for (size_t i = 0; i < info->swapchainCount; i++) {
const auto& swapchain = info->pSwapchains[i]; // NOLINT (array index)
const auto& it = instance_info->swapchains.find(swapchain);
if (it == instance_info->swapchains.end())
return VK_ERROR_INITIALIZATION_FAILED;
try {
std::vector<VkSemaphore> waitSemaphores;
waitSemaphores.reserve(info->waitSemaphoreCount);
for (size_t j = 0; j < info->waitSemaphoreCount; j++)
waitSemaphores.push_back(info->pWaitSemaphores[j]); // NOLINT (array index)
auto& context = layer_info->root.getSwapchainContext(swapchain);
result = context.present(it->second,
queue, swapchain,
const_cast<void*>(info->pNext),
info->pImageIndices[i], // NOLINT (array index)
{ waitSemaphores.begin(), waitSemaphores.end() }
);
} catch (const ls::vulkan_error& e) {
std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain presentation:\n";
std::cerr << "- " << e.what() << '\n';
result = e.error();
} catch (const std::exception& e) {
std::cerr << "lsfg-vk: something went wrong during lsfg-vk swapchain presentation:\n";
std::cerr << "- " << e.what() << '\n';
result = VK_ERROR_UNKNOWN;
}
if (result != VK_SUCCESS && info->pResults)
info->pResults[i] = result; // NOLINT (array index)
}
return result;
#pragma clang diagnostic pop
}
void myvkDestroySwapchainKHR(
VkDevice device,
VkSwapchainKHR swapchain,
@ -334,7 +390,10 @@ namespace {
if (it == instance_info->devices.end())
return;
// destroy lsfg-vk swapchain
const auto& mapping = instance_info->swapchains.find(swapchain);
if (mapping != instance_info->swapchains.end())
instance_info->swapchains.erase(mapping);
layer_info->root.removeSwapchainContext(swapchain);
// destroy swapchain
@ -361,6 +420,7 @@ VkResult vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface* pVers
{ "vkDestroyDevice", VKPTR(myvkDestroyDevice) },
{ "vkDestroyInstance", VKPTR(myvkDestroyInstance) },
{ "vkCreateSwapchainKHR", VKPTR(myvkCreateSwapchainKHR) },
{ "vkQueuePresentKHR", VKPTR(myvkQueuePresentKHR) },
{ "vkDestroySwapchainKHR", VKPTR(myvkDestroySwapchainKHR) }
#undef VKPTR
},

View file

@ -1,203 +0,0 @@
#include "context.hpp"
#include "config/config.hpp"
#include "common/exception.hpp"
#include "extract/extract.hpp"
#include "utils/utils.hpp"
#include "hooks.hpp"
#include "layer.hpp"
#include <vulkan/vulkan_core.h>
#include <lsfg_3_1.hpp>
#include <lsfg_3_1p.hpp>
#include <stdexcept>
#include <cstdint>
#include <cstdlib>
#include <vector>
#include <memory>
#include <string>
#include <array>
LsContext::LsContext(const Hooks::DeviceInfo& info, VkSwapchainKHR swapchain,
VkExtent2D extent, const std::vector<VkImage>& swapchainImages)
: swapchain(swapchain), swapchainImages(swapchainImages),
extent(extent) {
if (!Config::currentConf.has_value())
throw std::runtime_error("No configuration set");
auto& globalConf = Config::globalConf;
auto& conf = *Config::currentConf;
// we could take the format from the swapchain,
// but honestly this is safer.
const VkFormat format = conf.hdr
? VK_FORMAT_R8G8B8A8_UNORM
: VK_FORMAT_R16G16B16A16_SFLOAT;
// prepare textures for lsfg
std::array<int, 2> fds{};
this->frame_0 = Mini::Image(info.device, info.physicalDevice,
extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT,
&fds.at(0));
this->frame_1 = Mini::Image(info.device, info.physicalDevice,
extent, format, VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT,
&fds.at(1));
std::vector<int> outFds(conf.multiplier - 1);
for (size_t i = 0; i < (conf.multiplier - 1); ++i)
this->out_n.emplace_back(info.device, info.physicalDevice,
extent, format,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_IMAGE_ASPECT_COLOR_BIT,
&outFds.at(i));
// initialize lsfg
auto* lsfgInitialize = LSFG_3_1::initialize;
auto* lsfgCreateContext = LSFG_3_1::createContext;
auto* lsfgDeleteContext = LSFG_3_1::deleteContext;
if (conf.performance) {
lsfgInitialize = LSFG_3_1P::initialize;
lsfgCreateContext = LSFG_3_1P::createContext;
lsfgDeleteContext = LSFG_3_1P::deleteContext;
}
setenv("DISABLE_LSFG", "1", 1); // NOLINT
lsfgInitialize(
Utils::getDeviceUUID(info.physicalDevice),
conf.hdr, 1.0F / conf.flowScale, conf.multiplier - 1,
globalConf.no_fp16,
Extract::getShader
);
this->lsfgCtxId = std::shared_ptr<int32_t>(
new int32_t(lsfgCreateContext(fds.at(0), fds.at(1), outFds, extent, format)),
[lsfgDeleteContext = lsfgDeleteContext](const int32_t* id) {
lsfgDeleteContext(*id);
}
);
unsetenv("DISABLE_LSFG"); // NOLINT
// prepare render passes
this->cmdPool = Mini::CommandPool(info.device, info.queue.first);
for (size_t i = 0; i < 8; i++) {
auto& pass = this->passInfos.at(i);
pass.renderSemaphores.resize(conf.multiplier - 1);
pass.acquireSemaphores.resize(conf.multiplier - 1);
pass.postCopyBufs.resize(conf.multiplier - 1);
pass.postCopySemaphores.resize(conf.multiplier - 1);
pass.prevPostCopySemaphores.resize(conf.multiplier - 1);
}
}
VkResult LsContext::present(const Hooks::DeviceInfo& info, const void* pNext, VkQueue queue,
const std::vector<VkSemaphore>& gameRenderSemaphores, uint32_t presentIdx) {
if (!Config::currentConf.has_value())
throw std::runtime_error("No configuration set");
auto& conf = *Config::currentConf;
auto& pass = this->passInfos.at(this->frameIdx % 8);
// 1. copy swapchain image to frame_0/frame_1
int preCopySemaphoreFd{};
pass.preCopySemaphores.at(0) = Mini::Semaphore(info.device, &preCopySemaphoreFd);
pass.preCopySemaphores.at(1) = Mini::Semaphore(info.device);
pass.preCopyBuf = Mini::CommandBuffer(info.device, this->cmdPool);
pass.preCopyBuf.begin();
Utils::copyImage(pass.preCopyBuf.handle(),
this->swapchainImages.at(presentIdx),
this->frameIdx % 2 == 0 ? this->frame_0.handle() : this->frame_1.handle(),
this->extent.width, this->extent.height,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
true, false);
pass.preCopyBuf.end();
std::vector<VkSemaphore> gameRenderSemaphores2 = gameRenderSemaphores;
if (this->frameIdx > 0)
gameRenderSemaphores2.emplace_back(this->passInfos.at((this->frameIdx - 1) % 8)
.preCopySemaphores.at(1).handle());
pass.preCopyBuf.submit(info.queue.second,
gameRenderSemaphores2,
{ pass.preCopySemaphores.at(0).handle(),
pass.preCopySemaphores.at(1).handle() });
// 2. render intermediary frames
std::vector<int> renderSemaphoreFds(conf.multiplier - 1);
for (size_t i = 0; i < (conf.multiplier - 1); ++i)
pass.renderSemaphores.at(i) = Mini::Semaphore(info.device, &renderSemaphoreFds.at(i));
if (conf.performance)
LSFG_3_1P::presentContext(*this->lsfgCtxId,
preCopySemaphoreFd,
renderSemaphoreFds);
else
LSFG_3_1::presentContext(*this->lsfgCtxId,
preCopySemaphoreFd,
renderSemaphoreFds);
for (size_t i = 0; i < (conf.multiplier - 1); i++) {
// 3. acquire next swapchain image
pass.acquireSemaphores.at(i) = Mini::Semaphore(info.device);
uint32_t imageIdx{};
auto res = Layer::ovkAcquireNextImageKHR(info.device, this->swapchain, UINT64_MAX,
pass.acquireSemaphores.at(i).handle(), VK_NULL_HANDLE, &imageIdx);
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
throw LSFG::vulkan_error(res, "Failed to acquire next swapchain image");
// 4. copy output image to swapchain image
pass.postCopySemaphores.at(i) = Mini::Semaphore(info.device);
pass.prevPostCopySemaphores.at(i) = Mini::Semaphore(info.device);
pass.postCopyBufs.at(i) = Mini::CommandBuffer(info.device, this->cmdPool);
pass.postCopyBufs.at(i).begin();
Utils::copyImage(pass.postCopyBufs.at(i).handle(),
this->out_n.at(i).handle(),
this->swapchainImages.at(imageIdx),
this->extent.width, this->extent.height,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
false, true);
pass.postCopyBufs.at(i).end();
pass.postCopyBufs.at(i).submit(info.queue.second,
{ pass.acquireSemaphores.at(i).handle(),
pass.renderSemaphores.at(i).handle() },
{ pass.postCopySemaphores.at(i).handle(),
pass.prevPostCopySemaphores.at(i).handle() });
// 5. present swapchain image
std::vector<VkSemaphore> 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<uint32_t>(waitSemaphores.size()),
.pWaitSemaphores = waitSemaphores.data(),
.swapchainCount = 1,
.pSwapchains = &this->swapchain,
.pImageIndices = &imageIdx,
};
res = Layer::ovkQueuePresentKHR(queue, &presentInfo);
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
throw LSFG::vulkan_error(res, "Failed to present swapchain image");
}
// 6. present actual next frame
VkSemaphore lastPrevPostCopySemaphore =
pass.prevPostCopySemaphores.at(conf.multiplier - 1 - 1).handle();
const VkPresentInfoKHR presentInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &lastPrevPostCopySemaphore,
.swapchainCount = 1,
.pSwapchains = &this->swapchain,
.pImageIndices = &presentIdx,
};
auto res = Layer::ovkQueuePresentKHR(queue, &presentInfo);
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
throw LSFG::vulkan_error(res, "Failed to present swapchain image");
this->frameIdx++;
return res;
}

View file

@ -1,312 +0,0 @@
#include "hooks.hpp"
#include "common/exception.hpp"
#include "config/config.hpp"
#include "utils/utils.hpp"
#include "context.hpp"
#include "layer.hpp"
#include <vulkan/vulkan_core.h>
#include <unordered_map>
#include <stdexcept>
#include <algorithm>
#include <exception>
#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <vector>
using namespace Hooks;
namespace {
///
/// Add extensions to the instance create info.
///
VkResult myvkCreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* pInstance) {
auto extensions = Utils::addExtensions(
pCreateInfo->ppEnabledExtensionNames,
pCreateInfo->enabledExtensionCount,
{
"VK_KHR_get_physical_device_properties2",
"VK_KHR_external_memory_capabilities",
"VK_KHR_external_semaphore_capabilities"
}
);
VkInstanceCreateInfo createInfo = *pCreateInfo;
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
auto res = Layer::ovkCreateInstance(&createInfo, pAllocator, pInstance);
if (res == VK_ERROR_EXTENSION_NOT_PRESENT)
throw std::runtime_error(
"Required Vulkan instance extensions are not present."
"Your GPU driver is not supported.");
return res;
}
/// Map of devices to related information.
std::unordered_map<VkDevice, DeviceInfo> deviceToInfo;
///
/// Add extensions to the device create info.
/// (function pointers are not initialized yet)
///
VkResult myvkCreateDevicePre(
VkPhysicalDevice physicalDevice,
const VkDeviceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDevice* pDevice) {
// add extensions
auto extensions = Utils::addExtensions(
pCreateInfo->ppEnabledExtensionNames,
pCreateInfo->enabledExtensionCount,
{
"VK_KHR_external_memory",
"VK_KHR_external_memory_fd",
"VK_KHR_external_semaphore",
"VK_KHR_external_semaphore_fd"
}
);
VkDeviceCreateInfo createInfo = *pCreateInfo;
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
auto res = Layer::ovkCreateDevice(physicalDevice, &createInfo, pAllocator, pDevice);
if (res == VK_ERROR_EXTENSION_NOT_PRESENT)
throw std::runtime_error(
"Required Vulkan device extensions are not present."
"Your GPU driver is not supported.");
return res;
}
///
/// Add related device information after the device is created.
///
VkResult myvkCreateDevicePost(
VkPhysicalDevice physicalDevice,
VkDeviceCreateInfo* pCreateInfo,
const VkAllocationCallbacks*,
VkDevice* pDevice) {
deviceToInfo.emplace(*pDevice, DeviceInfo {
.device = *pDevice,
.physicalDevice = physicalDevice,
.queue = Utils::findQueue(*pDevice, physicalDevice, pCreateInfo, VK_QUEUE_GRAPHICS_BIT)
});
return VK_SUCCESS;
}
/// Erase the device information when the device is destroyed.
void myvkDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) noexcept {
deviceToInfo.erase(device);
Layer::ovkDestroyDevice(device, pAllocator);
}
std::unordered_map<VkSwapchainKHR, LsContext> swapchains;
std::unordered_map<VkSwapchainKHR, VkDevice> swapchainToDeviceTable;
///
/// Adjust swapchain creation parameters and create a swapchain context.
///
VkResult myvkCreateSwapchainKHR(
VkDevice device,
const VkSwapchainCreateInfoKHR* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSwapchainKHR* pSwapchain) noexcept {
// retire potential old swapchain
if (pCreateInfo->oldSwapchain) {
swapchains.erase(pCreateInfo->oldSwapchain);
swapchainToDeviceTable.erase(pCreateInfo->oldSwapchain);
}
// ensure configuration is up to date
Config::checkStatus();
// return early if disabled
if (!Config::currentConf.has_value() || Config::currentConf->multiplier <= 1)
return Layer::ovkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain);
auto& conf = Config::currentConf;
// find device
auto it = deviceToInfo.find(device);
if (it == deviceToInfo.end()) {
Utils::logLimitN("swapMap", 5, "Device not found in map");
return Layer::ovkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain);
}
Utils::resetLimitN("swapMap");
auto& deviceInfo = it->second;
// increase amount of images in swapchain
VkSwapchainCreateInfoKHR createInfo = *pCreateInfo;
const auto maxImages = Utils::getMaxImageCount(
deviceInfo.physicalDevice, pCreateInfo->surface);
createInfo.minImageCount = createInfo.minImageCount + 1
+ static_cast<uint32_t>(deviceInfo.queue.first);
if (createInfo.minImageCount > maxImages) {
createInfo.minImageCount = maxImages;
Utils::logLimitN("swapCount", 10,
"Requested image count (" +
std::to_string(pCreateInfo->minImageCount) + ") "
"exceeds maximum allowed (" +
std::to_string(maxImages) + "). "
"Continuing with maximum allowed image count. "
"This might lead to performance degradation.");
} else {
Utils::resetLimitN("swapCount");
}
// allow copy operations on swapchain images
createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
createInfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
// enforce present mode
createInfo.presentMode = conf->e_present;
// 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 all swapchain images
uint32_t imageCount{};
res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr);
if (res != VK_SUCCESS || imageCount == 0)
throw LSFG::vulkan_error(res, "Failed to get swapchain image count");
std::vector<VkImage> swapchainImages(imageCount);
res = Layer::ovkGetSwapchainImagesKHR(device, *pSwapchain,
&imageCount, swapchainImages.data());
if (res != VK_SUCCESS)
throw LSFG::vulkan_error(res, "Failed to get swapchain images");
// create swapchain context
swapchainToDeviceTable.emplace(*pSwapchain, device);
swapchains.emplace(*pSwapchain, LsContext(
deviceInfo, *pSwapchain, pCreateInfo->imageExtent,
swapchainImages
));
std::cerr << "lsfg-vk: Swapchain context " <<
(createInfo.oldSwapchain ? "recreated" : "created")
<< " (using " << imageCount << " images).\n";
Utils::resetLimitN("swapCtxCreate");
} catch (const std::exception& e) {
Utils::logLimitN("swapCtxCreate", 5,
"An error occurred while creating the swapchain wrapper:\n"
"- " + std::string(e.what()));
return VK_SUCCESS; // swapchain is still valid
}
return VK_SUCCESS;
}
///
/// Update presentation parameters and present the next frame(s).
///
VkResult myvkQueuePresentKHR(
VkQueue queue,
const VkPresentInfoKHR* pPresentInfo) noexcept {
// ensure configuration is up to date
if (!Config::checkStatus())
return VK_ERROR_OUT_OF_DATE_KHR;
// return early if disabled
if (!Config::currentConf.has_value() || Config::currentConf->multiplier <= 1)
return Layer::ovkQueuePresentKHR(queue, pPresentInfo);
auto& conf = Config::currentConf;
// find swapchain device
auto it = swapchainToDeviceTable.find(*pPresentInfo->pSwapchains);
if (it == swapchainToDeviceTable.end()) {
Utils::logLimitN("swapMap", 5,
"Swapchain not found in map");
return Layer::ovkQueuePresentKHR(queue, pPresentInfo);
}
Utils::resetLimitN("swapMap");
// find device info
auto it2 = deviceToInfo.find(it->second);
if (it2 == deviceToInfo.end()) {
Utils::logLimitN("deviceMap", 5,
"Device not found in map");
return Layer::ovkQueuePresentKHR(queue, pPresentInfo);
}
Utils::resetLimitN("deviceMap");
auto& deviceInfo = it2->second;
// find swapchain context
auto it3 = swapchains.find(*pPresentInfo->pSwapchains);
if (it3 == swapchains.end()) {
Utils::logLimitN("swapCtxMap", 5,
"Swapchain context not found in map");
return Layer::ovkQueuePresentKHR(queue, pPresentInfo);
}
Utils::resetLimitN("swapCtxMap");
auto& swapchain = it3->second;
// enforce present mode | NOLINTBEGIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
const VkSwapchainPresentModeInfoEXT* presentModeInfo =
reinterpret_cast<const VkSwapchainPresentModeInfoEXT*>(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<VkPresentModeKHR*>(presentModeInfo->pPresentModes)[i] =
conf->e_present;
}
presentModeInfo =
reinterpret_cast<const VkSwapchainPresentModeInfoEXT*>(presentModeInfo->pNext);
}
#pragma clang diagnostic pop
// NOLINTEND | present the next frame
VkResult res{}; // might return VK_SUBOPTIMAL_KHR
try {
// present the swapchain
std::vector<VkSemaphore> semaphores(pPresentInfo->waitSemaphoreCount);
std::copy_n(pPresentInfo->pWaitSemaphores, semaphores.size(), semaphores.data());
res = swapchain.present(deviceInfo, pPresentInfo->pNext,
queue, semaphores, *pPresentInfo->pImageIndices);
Utils::resetLimitN("swapPresent");
} catch (const std::exception& e) {
Utils::logLimitN("swapPresent", 5,
"An error occurred while presenting the swapchain:\n"
"- " + std::string(e.what()));
return VK_ERROR_INITIALIZATION_FAILED;
}
return res;
}
/// Erase the swapchain context and mapping when the swapchain is destroyed.
void myvkDestroySwapchainKHR(
VkDevice device,
VkSwapchainKHR swapchain,
const VkAllocationCallbacks* pAllocator) noexcept {
swapchains.erase(swapchain);
swapchainToDeviceTable.erase(swapchain);
Layer::ovkDestroySwapchainKHR(device, swapchain, pAllocator);
}
}
std::unordered_map<std::string, PFN_vkVoidFunction> Hooks::hooks = {
// instance hooks
{"vkCreateInstance", reinterpret_cast<PFN_vkVoidFunction>(myvkCreateInstance)},
// device hooks
{"vkCreateDevicePre", reinterpret_cast<PFN_vkVoidFunction>(myvkCreateDevicePre)},
{"vkCreateDevicePost", reinterpret_cast<PFN_vkVoidFunction>(myvkCreateDevicePost)},
{"vkDestroyDevice", reinterpret_cast<PFN_vkVoidFunction>(myvkDestroyDevice)},
// swapchain hooks
{"vkCreateSwapchainKHR", reinterpret_cast<PFN_vkVoidFunction>(myvkCreateSwapchainKHR)},
{"vkQueuePresentKHR", reinterpret_cast<PFN_vkVoidFunction>(myvkQueuePresentKHR)},
{"vkDestroySwapchainKHR", reinterpret_cast<PFN_vkVoidFunction>(myvkDestroySwapchainKHR)}
};