Fix support for headless vulkan context

This commit is contained in:
Wunkolo 2023-07-25 13:48:07 -07:00
parent 17101e9bb9
commit d0832ca558
2 changed files with 192 additions and 159 deletions

View file

@ -32,9 +32,14 @@ class RendererVK final : public Renderer {
std::vector<vk::Image> swapchainImages = {};
std::vector<vk::UniqueImageView> swapchainImageViews = {};
// Per-swapchain-image data
// Each vector is `swapchainImageCount` in size
std::vector<vk::UniqueCommandBuffer> presentCommandBuffers = {};
// This value is the degree of parallelism to allow multiple frames to be in-flight
// aka: "double-buffer"/"triple-buffering"
// Todo: make this a configuration option
static constexpr usize frameBufferingCount = 3;
// Frame-buffering data
// Each vector is `frameBufferingCount` in size
std::vector<vk::UniqueCommandBuffer> frameCommandBuffers = {};
std::vector<vk::UniqueSemaphore> swapImageFreeSemaphore = {};
std::vector<vk::UniqueSemaphore> renderFinishedSemaphore = {};
std::vector<vk::UniqueFence> frameFinishedFences = {};
@ -42,7 +47,8 @@ class RendererVK final : public Renderer {
// Recreate the swapchain, possibly re-using the old one in the case of a resize
vk::Result recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent);
u64 currentFrame = 0;
u64 frameBufferingIndex = 0;
public:
RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
~RendererVK() override;

View file

@ -155,48 +155,6 @@ vk::Result RendererVK::recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D sw
Helpers::panic("Error creating acquiring swapchain images: %s\n", vk::to_string(getResult.result).c_str());
}
// Swapchain Command buffer(s)
vk::CommandBufferAllocateInfo commandBuffersInfo = {};
commandBuffersInfo.commandPool = commandPool.get();
commandBuffersInfo.level = vk::CommandBufferLevel::ePrimary;
commandBuffersInfo.commandBufferCount = swapchainImageCount;
if (auto allocateResult = device->allocateCommandBuffersUnique(commandBuffersInfo); allocateResult.result == vk::Result::eSuccess) {
presentCommandBuffers = std::move(allocateResult.value);
} else {
Helpers::panic("Error allocating command buffer: %s\n", vk::to_string(allocateResult.result).c_str());
}
// Swapchain synchronization primitives
vk::FenceCreateInfo fenceInfo = {};
fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled;
vk::SemaphoreCreateInfo semaphoreInfo = {};
swapImageFreeSemaphore.resize(swapchainImageCount);
renderFinishedSemaphore.resize(swapchainImageCount);
frameFinishedFences.resize(swapchainImageCount);
for (usize i = 0; i < swapchainImageCount; i++) {
if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) {
swapImageFreeSemaphore[i] = std::move(createResult.value);
} else {
Helpers::panic("Error creating 'present-ready' semaphore: %s\n", vk::to_string(createResult.result).c_str());
}
if (auto createResult = device->createSemaphoreUnique(semaphoreInfo); createResult.result == vk::Result::eSuccess) {
renderFinishedSemaphore[i] = std::move(createResult.value);
} else {
Helpers::panic("Error creating 'post-render' semaphore: %s\n", vk::to_string(createResult.result).c_str());
}
if (auto createResult = device->createFenceUnique(fenceInfo); createResult.result == vk::Result::eSuccess) {
frameFinishedFences[i] = std::move(createResult.value);
} else {
Helpers::panic("Error creating 'present-ready' semaphore: %s\n", vk::to_string(createResult.result).c_str());
}
}
return vk::Result::eSuccess;
}
@ -208,133 +166,143 @@ RendererVK::~RendererVK() {}
void RendererVK::reset() {}
void RendererVK::display() {
// Block, on the CPU, to ensure that this swapchain-frame is ready for more work
if (auto waitResult = device->waitForFences({frameFinishedFences[currentFrame].get()}, true, std::numeric_limits<u64>::max());
// Block, on the CPU, to ensure that this frame-buffering-frame is ready for more work
if (auto waitResult = device->waitForFences({frameFinishedFences[frameBufferingIndex].get()}, true, std::numeric_limits<u64>::max());
waitResult != vk::Result::eSuccess) {
Helpers::panic("Error waiting on swapchain fence: %s\n", vk::to_string(waitResult).c_str());
}
u32 swapchainImageIndex = std::numeric_limits<u32>::max();
if (const auto acquireResult =
device->acquireNextImageKHR(swapchain.get(), std::numeric_limits<u64>::max(), swapImageFreeSemaphore[currentFrame].get(), {});
acquireResult.result == vk::Result::eSuccess) {
swapchainImageIndex = acquireResult.value;
} else {
switch (acquireResult.result) {
case vk::Result::eSuboptimalKHR:
case vk::Result::eErrorOutOfDateKHR: {
// Surface resized
vk::Extent2D swapchainExtent;
{
int windowWidth, windowHeight;
// Block until we have a valid surface-area to present to
// Usually this is because the window has been minimized
// Todo: We should still be rendering even without a valid swapchain
do {
SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight);
} while (!windowWidth || !windowHeight);
swapchainExtent.width = windowWidth;
swapchainExtent.height = windowHeight;
// Get the next available swapchain image, and signal the semaphore when it's ready
static constexpr u32 swapchainImageInvalid = std::numeric_limits<u32>::max();
u32 swapchainImageIndex = swapchainImageInvalid;
if (swapchain) {
if (const auto acquireResult =
device->acquireNextImageKHR(swapchain.get(), std::numeric_limits<u64>::max(), swapImageFreeSemaphore[frameBufferingIndex].get(), {});
acquireResult.result == vk::Result::eSuccess) {
swapchainImageIndex = acquireResult.value;
} else {
switch (acquireResult.result) {
case vk::Result::eSuboptimalKHR:
case vk::Result::eErrorOutOfDateKHR: {
// Surface resized
vk::Extent2D swapchainExtent;
{
int windowWidth, windowHeight;
// Block until we have a valid surface-area to present to
// Usually this is because the window has been minimized
// Todo: We should still be rendering even without a valid swapchain
do {
SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight);
} while (!windowWidth || !windowHeight);
swapchainExtent.width = windowWidth;
swapchainExtent.height = windowHeight;
}
recreateSwapchain(surface.get(), swapchainExtent);
break;
}
default: {
Helpers::panic("Error acquiring next swapchain image: %s\n", vk::to_string(acquireResult.result).c_str());
}
recreateSwapchain(surface.get(), swapchainExtent);
break;
}
default: {
Helpers::panic("Error acquiring next swapchain image: %s\n", vk::to_string(acquireResult.result).c_str());
}
}
}
vk::UniqueCommandBuffer& presentCommandBuffer = presentCommandBuffers.at(currentFrame);
vk::UniqueCommandBuffer& frameCommandBuffer = frameCommandBuffers.at(frameBufferingIndex);
vk::CommandBufferBeginInfo beginInfo = {};
beginInfo.flags = vk::CommandBufferUsageFlagBits::eSimultaneousUse;
if (const vk::Result beginResult = presentCommandBuffer->begin(beginInfo); beginResult != vk::Result::eSuccess) {
if (const vk::Result beginResult = frameCommandBuffer->begin(beginInfo); beginResult != vk::Result::eSuccess) {
Helpers::panic("Error beginning command buffer recording: %s\n", vk::to_string(beginResult).c_str());
}
{
static const std::array<float, 4> presentScopeColor = {{1.0f, 0.0f, 1.0f, 1.0f}};
static const std::array<float, 4> frameScopeColor = {{1.0f, 0.0f, 1.0f, 1.0f}};
Vulkan::DebugLabelScope debugScope(presentCommandBuffer.get(), presentScopeColor, "Present");
Vulkan::DebugLabelScope debugScope(frameCommandBuffer.get(), frameScopeColor, "Frame");
// Prepare for color-clear
presentCommandBuffer->pipelineBarrier(
vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {},
{vk::ImageMemoryBarrier(
vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined,
vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex],
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)
)}
);
if (swapchainImageIndex != swapchainImageInvalid) {
frameCommandBuffer->pipelineBarrier(
vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlags(), {}, {},
{vk::ImageMemoryBarrier(
vk::AccessFlagBits::eMemoryRead, vk::AccessFlagBits::eTransferWrite, vk::ImageLayout::eUndefined,
vk::ImageLayout::eTransferDstOptimal, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex],
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)
)}
);
presentCommandBuffer->clearColorImage(
swapchainImages[swapchainImageIndex], vk::ImageLayout::eTransferDstOptimal, presentScopeColor,
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)
);
// Prepare for present
presentCommandBuffer->pipelineBarrier(
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::DependencyFlags(), {}, {},
{vk::ImageMemoryBarrier(
vk::AccessFlagBits::eNone, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal,
vk::ImageLayout::ePresentSrcKHR, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex],
static const std::array<float, 4> clearColor = {{0.0f, 0.0f, 0.0f, 1.0f}};
frameCommandBuffer->clearColorImage(
swapchainImages[swapchainImageIndex], vk::ImageLayout::eTransferDstOptimal, clearColor,
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)
)}
);
);
// Prepare for present
frameCommandBuffer->pipelineBarrier(
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::DependencyFlags(), {}, {},
{vk::ImageMemoryBarrier(
vk::AccessFlagBits::eNone, vk::AccessFlagBits::eColorAttachmentWrite, vk::ImageLayout::eTransferDstOptimal,
vk::ImageLayout::ePresentSrcKHR, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, swapchainImages[swapchainImageIndex],
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)
)}
);
}
}
if (const vk::Result endResult = presentCommandBuffer->end(); endResult != vk::Result::eSuccess) {
if (const vk::Result endResult = frameCommandBuffer->end(); endResult != vk::Result::eSuccess) {
Helpers::panic("Error ending command buffer recording: %s\n", vk::to_string(endResult).c_str());
}
vk::SubmitInfo submitInfo = {};
// Wait for any previous uses of the image image to finish presenting
submitInfo.setWaitSemaphores(swapImageFreeSemaphore[currentFrame].get());
if (swapchainImageIndex != swapchainImageInvalid) {
submitInfo.setWaitSemaphores(swapImageFreeSemaphore[frameBufferingIndex].get());
static const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
submitInfo.setWaitDstStageMask(waitStageMask);
}
// Signal when finished
submitInfo.setSignalSemaphores(renderFinishedSemaphore[currentFrame].get());
submitInfo.setSignalSemaphores(renderFinishedSemaphore[frameBufferingIndex].get());
static const vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
submitInfo.setWaitDstStageMask(waitStageMask);
submitInfo.setCommandBuffers(frameCommandBuffer.get());
submitInfo.setCommandBuffers(presentCommandBuffer.get());
device->resetFences({frameFinishedFences[frameBufferingIndex].get()});
device->resetFences({frameFinishedFences[currentFrame].get()});
if (const vk::Result submitResult = graphicsQueue.submit({submitInfo}, frameFinishedFences[currentFrame].get());
if (const vk::Result submitResult = graphicsQueue.submit({submitInfo}, frameFinishedFences[frameBufferingIndex].get());
submitResult != vk::Result::eSuccess) {
Helpers::panic("Error submitting to graphics queue: %s\n", vk::to_string(submitResult).c_str());
}
vk::PresentInfoKHR presentInfo = {};
presentInfo.setWaitSemaphores(renderFinishedSemaphore[currentFrame].get());
presentInfo.setSwapchains(swapchain.get());
presentInfo.setImageIndices(swapchainImageIndex);
if (swapchainImageIndex != swapchainImageInvalid) {
vk::PresentInfoKHR presentInfo = {};
presentInfo.setWaitSemaphores(renderFinishedSemaphore[frameBufferingIndex].get());
presentInfo.setSwapchains(swapchain.get());
presentInfo.setImageIndices(swapchainImageIndex);
if (const auto presentResult = presentQueue.presentKHR(presentInfo); presentResult == vk::Result::eSuccess) {
} else {
switch (presentResult) {
case vk::Result::eSuboptimalKHR:
case vk::Result::eErrorOutOfDateKHR: {
// Surface resized
vk::Extent2D swapchainExtent;
{
int windowWidth, windowHeight;
SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight);
swapchainExtent.width = windowWidth;
swapchainExtent.height = windowHeight;
if (const auto presentResult = presentQueue.presentKHR(presentInfo); presentResult == vk::Result::eSuccess) {
} else {
switch (presentResult) {
case vk::Result::eSuboptimalKHR:
case vk::Result::eErrorOutOfDateKHR: {
// Surface resized
vk::Extent2D swapchainExtent;
{
int windowWidth, windowHeight;
SDL_Vulkan_GetDrawableSize(targetWindow, &windowWidth, &windowHeight);
swapchainExtent.width = windowWidth;
swapchainExtent.height = windowHeight;
}
recreateSwapchain(surface.get(), swapchainExtent);
break;
}
default: {
Helpers::panic("Error presenting swapchain image: %s\n", vk::to_string(presentResult).c_str());
}
recreateSwapchain(surface.get(), swapchainExtent);
break;
}
default: {
Helpers::panic("Error presenting swapchain image: %s\n", vk::to_string(presentResult).c_str());
}
}
}
currentFrame = ((currentFrame + 1) % swapchainImageCount);
frameBufferingIndex = ((frameBufferingIndex + 1) % frameBufferingCount);
}
void RendererVK::initGraphicsContext(SDL_Window* window) {
@ -365,11 +333,11 @@ void RendererVK::initGraphicsContext(SDL_Window* window) {
};
// Get any additional extensions that SDL wants as well
{
if (targetWindow) {
unsigned int extensionCount = 0;
SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr);
SDL_Vulkan_GetInstanceExtensions(targetWindow, &extensionCount, nullptr);
std::vector<const char*> sdlInstanceExtensions(extensionCount);
SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, sdlInstanceExtensions.data());
SDL_Vulkan_GetInstanceExtensions(targetWindow, &extensionCount, sdlInstanceExtensions.data());
instanceExtensions.insert(instanceExtensions.end(), sdlInstanceExtensions.begin(), sdlInstanceExtensions.end());
}
@ -411,10 +379,12 @@ void RendererVK::initGraphicsContext(SDL_Window* window) {
}
// Create surface
if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) {
surface.reset(newSurface);
} else {
Helpers::warn("Error creating Vulkan surface");
if (window) {
if (VkSurfaceKHR newSurface; SDL_Vulkan_CreateSurface(window, instance.get(), &newSurface)) {
surface.reset(newSurface);
} else {
Helpers::warn("Error creating Vulkan surface");
}
}
// Pick physical device
@ -423,18 +393,20 @@ void RendererVK::initGraphicsContext(SDL_Window* window) {
std::vector<vk::PhysicalDevice>::iterator partitionEnd = physicalDevices.end();
// Prefer GPUs that can access the surface
const auto surfaceSupport = [this](const vk::PhysicalDevice& physicalDevice) -> bool {
const usize queueCount = physicalDevice.getQueueFamilyProperties().size();
for (usize queueIndex = 0; queueIndex < queueCount; ++queueIndex) {
if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueIndex, surface.get());
supportResult.result == vk::Result::eSuccess) {
return supportResult.value;
if (surface) {
const auto surfaceSupport = [this](const vk::PhysicalDevice& physicalDevice) -> bool {
const usize queueCount = physicalDevice.getQueueFamilyProperties().size();
for (usize queueIndex = 0; queueIndex < queueCount; ++queueIndex) {
if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueIndex, surface.get());
supportResult.result == vk::Result::eSuccess) {
return supportResult.value;
}
}
}
return false;
};
return false;
};
partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, surfaceSupport);
partitionEnd = std::stable_partition(physicalDevices.begin(), partitionEnd, surfaceSupport);
}
// Prefer Discrete GPUs
const auto isDiscrete = [](const vk::PhysicalDevice& physicalDevice) -> bool {
@ -454,26 +426,32 @@ void RendererVK::initGraphicsContext(SDL_Window* window) {
std::vector<vk::DeviceQueueCreateInfo> deviceQueueInfos;
{
const std::vector<vk::QueueFamilyProperties> queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
std::unordered_set<u32> queueFamilyRequests;
// Get present queue family
for (usize queueFamilyIndex = 0; queueFamilyIndex < queueFamilyProperties.size(); ++queueFamilyIndex) {
if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, surface.get());
supportResult.result == vk::Result::eSuccess) {
if (supportResult.value) {
presentQueueFamily = queueFamilyIndex;
break;
if (surface) {
for (usize queueFamilyIndex = 0; queueFamilyIndex < queueFamilyProperties.size(); ++queueFamilyIndex) {
if (auto supportResult = physicalDevice.getSurfaceSupportKHR(queueFamilyIndex, surface.get());
supportResult.result == vk::Result::eSuccess) {
if (supportResult.value) {
presentQueueFamily = queueFamilyIndex;
break;
}
}
}
queueFamilyRequests.emplace(presentQueueFamily);
}
static const float queuePriority = 1.0f;
graphicsQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eGraphics);
queueFamilyRequests.emplace(graphicsQueueFamily);
computeQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eCompute);
queueFamilyRequests.emplace(computeQueueFamily);
transferQueueFamily = findQueueFamily(queueFamilyProperties, vk::QueueFlagBits::eTransfer);
queueFamilyRequests.emplace(transferQueueFamily);
// Requests a singular queue for each unique queue-family
const std::unordered_set<u32> queueFamilyRequests = {presentQueueFamily, graphicsQueueFamily, computeQueueFamily, transferQueueFamily};
for (const u32 queueFamilyIndex : queueFamilyRequests) {
deviceQueueInfos.emplace_back(vk::DeviceQueueCreateInfo({}, queueFamilyIndex, 1, &queuePriority));
}
@ -482,15 +460,31 @@ void RendererVK::initGraphicsContext(SDL_Window* window) {
// Create Device
vk::DeviceCreateInfo deviceInfo = {};
static const char* deviceExtensions[] = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
// Device extensions
std::vector<const char*> deviceExtensions = {
#if defined(__APPLE__)
"VK_KHR_portability_subset",
#endif
// VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME
};
deviceInfo.ppEnabledExtensionNames = deviceExtensions;
deviceInfo.enabledExtensionCount = std::size(deviceExtensions);
std::unordered_set<std::string> physicalDeviceExtensions;
if (const auto enumerateResult = physicalDevice.enumerateDeviceExtensionProperties(); enumerateResult.result == vk::Result::eSuccess) {
for (const auto& extension : enumerateResult.value) {
physicalDeviceExtensions.insert(extension.extensionName);
}
} else {
Helpers::panic("Error enumerating physical devices extensions: %s\n", vk::to_string(enumerateResult.result).c_str());
}
// Opertional extensions
// Optionally enable the swapchain, to support "headless" rendering
if (physicalDeviceExtensions.contains(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
deviceExtensions.emplace_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
deviceInfo.setPEnabledExtensionNames(deviceExtensions);
vk::StructureChain<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceTimelineSemaphoreFeatures> deviceFeatureChain = {};
@ -528,6 +522,39 @@ void RendererVK::initGraphicsContext(SDL_Window* window) {
}
// Create swapchain
if (targetWindow && surface) {
vk::Extent2D swapchainExtent;
{
int windowWidth, windowHeight;
SDL_Vulkan_GetDrawableSize(window, &windowWidth, &windowHeight);
swapchainExtent.width = windowWidth;
swapchainExtent.height = windowHeight;
}
recreateSwapchain(surface.get(), swapchainExtent);
}
// Create frame-buffering data
// Frame-buffering Command buffer(s)
vk::CommandBufferAllocateInfo commandBuffersInfo = {};
commandBuffersInfo.commandPool = commandPool.get();
commandBuffersInfo.level = vk::CommandBufferLevel::ePrimary;
commandBuffersInfo.commandBufferCount = frameBufferingCount;
if (auto allocateResult = device->allocateCommandBuffersUnique(commandBuffersInfo); allocateResult.result == vk::Result::eSuccess) {
frameCommandBuffers = std::move(allocateResult.value);
} else {
Helpers::panic("Error allocating command buffer: %s\n", vk::to_string(allocateResult.result).c_str());
}
// Frame-buffering synchronization primitives
vk::FenceCreateInfo fenceInfo = {};
fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled;
vk::SemaphoreCreateInfo semaphoreInfo = {};
swapImageFreeSemaphore.resize(frameBufferingCount);
renderFinishedSemaphore.resize(frameBufferingCount);
frameFinishedFences.resize(frameBufferingCount);
vk::Extent2D swapchainExtent;
{
int windowWidth, windowHeight;