mirror of
https://github.com/PancakeTAS/lsfg-vk.git
synced 2026-04-05 10:06:25 +00:00
300 lines
11 KiB
C++
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;
|
|
}
|