lsfg-vk/lsfg-vk-layer/src/swapchain.cpp

300 lines
11 KiB
C++

#include "swapchain.hpp"
#include "lsfg-vk-backend/lsfgvk.hpp"
#include "lsfg-vk-common/configuration/config.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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <functional>
#include <optional>
#include <utility>
#include <vector>
#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 ls::GameConf& profile, uint32_t maxImages,
VkSwapchainCreateInfoKHR& createInfo) {
createInfo.imageUsage |=
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
switch (profile.pacing) {
case ls::Pacing::None:
createInfo.minImageCount += profile.multiplier;
if (maxImages && createInfo.minImageCount > maxImages)
createInfo.minImageCount = maxImages;
createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;
break;
}
}
Swapchain::Swapchain(const vk::Vulkan& vk, lsfgvk::backend::Instance& backend,
ls::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);
try {
this->ctx = ls::owned_ptr<ls::R<lsfgvk::backend::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::backend::Context>& ctx) {
backend->closeContext(ctx);
}
);
} catch (const std::exception& e) {
throw ls::error("failed to create swapchain context", e);
}
this->renderCommandBuffer.emplace(vk);
this->renderFence.emplace(vk);
for (size_t i = 0; i < this->destinationImages.size(); i++) {
this->passes.emplace_back(RenderPass {
.commandBuffer = vk::CommandBuffer(vk),
.acquireSemaphore = vk::Semaphore(vk)
});
}
const size_t frames = std::max(this->info.images.size(), this->destinationImages.size() + 2);
for (size_t i = 0; i < frames; i++) {
this->postCopySemaphores.emplace_back(
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
try {
this->instance.get().scheduleFrames(this->ctx.get());
} catch (const std::exception& e) {
throw ls::error("failed to schedule frames", e);
}
// update present mode when not using pacing
if (this->profile.pacing == ls::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
if (this->fidx && !this->renderFence->wait(vk, 150UL * 1000 * 1000))
throw ls::vulkan_error(VK_TIMEOUT, "vkWaitForFences() failed");
this->renderFence->reset(vk);
// copy swapchain image into backend source image
const auto& cmdbuf = *this->renderCommandBuffer;
cmdbuf.begin(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.end(vk);
cmdbuf.submit(vk,
semaphores, nullptr, 0,
{}, this->syncSemaphore->handle(), this->idx++
);
for (size_t i = 0; i < this->destinationImages.size(); i++) {
auto& pcs = this->postCopySemaphores.at(this->idx % this->postCopySemaphores.size());
auto& destinationImage = this->destinationImages.at(i);
auto& pass = this->passes.at(i);
// 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.begin(vk);
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& prevPCS = this->postCopySemaphores.at((this->idx - 1) % this->postCopySemaphores.size());
waitSemaphores.push_back(prevPCS.second.handle());
}
const std::vector<VkSemaphore> signalSemaphores{
pcs.first.handle(),
pcs.second.handle()
};
cmdbuf.end(vk);
cmdbuf.submit(vk,
waitSemaphores, this->syncSemaphore->handle(), this->idx,
signalSemaphores, nullptr, 0,
i == this->destinationImages.size() - 1 ? this->renderFence->handle() : nullptr
);
// present swapchain image
const VkPresentInfoKHR presentInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pNext = i ? nullptr : next_chain,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &pcs.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
auto& lastPCS = this->postCopySemaphores.at((this->idx - 1) % this->postCopySemaphores.size());
const VkPresentInfoKHR presentInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &lastPCS.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");
this->fidx++;
return res;
}