xash3d-fwgs/ref/vk/vk_swapchain.c

299 lines
10 KiB
C

#include "vk_swapchain.h"
#include "vk_image.h"
#include "profiler.h"
#include "eiface.h" // ARRAYSIZE
extern ref_globals_t *gpGlobals;
static struct {
// These don't belong here
VkRenderPass render_pass;
VkFormat depth_format;
VkSwapchainKHR swapchain;
VkFormat image_format;
uint32_t num_images;
VkImage *images;
VkImageView *image_views;
VkFramebuffer *framebuffers;
r_vk_image_t depth;
uint32_t width, height;
uint32_t recreate_requested;
} g_swapchain = {0};
// TODO move to common
static uint32_t clamp_u32(uint32_t v, uint32_t min, uint32_t max) {
if (v < min) v = min;
if (v > max) v = max;
return v;
}
static void createDepthImage(int w, int h, VkFormat depth_format) {
const r_vk_image_create_t xic = {
.debug_name = "depth",
.format = depth_format,
.flags = 0,
.mips = 1,
.layers = 1,
.width = w,
.height = h,
.depth = 1,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
};
g_swapchain.depth = R_VkImageCreate( &xic );
}
static void destroySwapchainAndFramebuffers( VkSwapchainKHR swapchain ) {
XVK_CHECK(vkDeviceWaitIdle( vk_core.device ));
for (uint32_t i = 0; i < g_swapchain.num_images; ++i) {
vkDestroyImageView(vk_core.device, g_swapchain.image_views[i], NULL);
vkDestroyFramebuffer(vk_core.device, g_swapchain.framebuffers[i], NULL);
}
R_VkImageDestroy( &g_swapchain.depth );
vkDestroySwapchainKHR(vk_core.device, swapchain, NULL);
}
static qboolean recreateSwapchainIfNeeded( qboolean force ) {
const uint32_t prev_num_images = g_swapchain.num_images;
uint32_t new_width, new_height;
VkSurfaceCapabilitiesKHR surface_caps;
XVK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk_core.physical_device.device, vk_core.surface.surface, &surface_caps));
new_width = surface_caps.currentExtent.width;
new_height = surface_caps.currentExtent.height;
if (new_width == 0xfffffffful || new_width == 0)
new_width = gpGlobals->width;
if (new_height == 0xfffffffful || new_height == 0)
new_height = gpGlobals->height;
new_width = clamp_u32(new_width, surface_caps.minImageExtent.width, surface_caps.maxImageExtent.width);
new_height = clamp_u32(new_height, surface_caps.minImageExtent.height, surface_caps.maxImageExtent.height);
if (new_height == 0 || new_width == 0) {
return false;
}
if (new_width == g_swapchain.width && new_height == g_swapchain.height && !force)
return true;
g_swapchain.width = new_width;
g_swapchain.height = new_height;
{
VkSwapchainCreateInfoKHR create_info = {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.pNext = NULL,
.surface = vk_core.surface.surface,
.imageFormat = g_swapchain.image_format,
.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR, // TODO get from surface_formats
.imageExtent.width = g_swapchain.width,
.imageExtent.height = g_swapchain.height,
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | (vk_core.rtx ? VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT : 0),
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.preTransform = surface_caps.currentTransform,
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
.presentMode = VK_PRESENT_MODE_FIFO_KHR, // TODO caps, MAILBOX is better
//.presentMode = VK_PRESENT_MODE_MAILBOX_KHR, // TODO caps, MAILBOX is better
//.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR, // TODO caps, MAILBOX is better
.clipped = VK_TRUE,
.oldSwapchain = g_swapchain.swapchain,
.minImageCount = surface_caps.minImageCount + 1,
};
if (surface_caps.maxImageCount && surface_caps.maxImageCount < create_info.minImageCount)
create_info.minImageCount = surface_caps.maxImageCount;
gEngine.Con_Printf("Creating swapchain %dx%d min_count=%d (extent %dx%d)\n",
g_swapchain.width, g_swapchain.height, create_info.minImageCount,
surface_caps.currentExtent.width, surface_caps.currentExtent.height);
XVK_CHECK(vkCreateSwapchainKHR(vk_core.device, &create_info, NULL, &g_swapchain.swapchain));
if (create_info.oldSwapchain)
destroySwapchainAndFramebuffers( create_info.oldSwapchain );
}
createDepthImage(g_swapchain.width, g_swapchain.height, g_swapchain.depth_format);
g_swapchain.num_images = 0;
XVK_CHECK(vkGetSwapchainImagesKHR(vk_core.device, g_swapchain.swapchain, &g_swapchain.num_images, NULL));
if (prev_num_images != g_swapchain.num_images)
{
if (g_swapchain.images)
{
Mem_Free(g_swapchain.images);
Mem_Free(g_swapchain.image_views);
Mem_Free(g_swapchain.framebuffers);
}
g_swapchain.images = Mem_Malloc(vk_core.pool, sizeof(*g_swapchain.images) * g_swapchain.num_images);
g_swapchain.image_views = Mem_Malloc(vk_core.pool, sizeof(*g_swapchain.image_views) * g_swapchain.num_images);
g_swapchain.framebuffers = Mem_Malloc(vk_core.pool, sizeof(*g_swapchain.framebuffers) * g_swapchain.num_images);
}
XVK_CHECK(vkGetSwapchainImagesKHR(vk_core.device, g_swapchain.swapchain, &g_swapchain.num_images, g_swapchain.images));
// TODO move this out to where render pipelines are created
for (uint32_t i = 0; i < g_swapchain.num_images; ++i) {
const VkImageViewCreateInfo ivci = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = g_swapchain.image_format,
.image = g_swapchain.images[i],
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.subresourceRange.levelCount = 1,
.subresourceRange.layerCount = 1,
};
XVK_CHECK(vkCreateImageView(vk_core.device, &ivci, NULL, g_swapchain.image_views + i));
{
const VkImageView attachments[] = {
g_swapchain.image_views[i],
g_swapchain.depth.view,
};
const VkFramebufferCreateInfo fbci = {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = g_swapchain.render_pass,
.attachmentCount = ARRAYSIZE(attachments),
.pAttachments = attachments,
.width = g_swapchain.width,
.height = g_swapchain.height,
.layers = 1,
};
XVK_CHECK(vkCreateFramebuffer(vk_core.device, &fbci, NULL, g_swapchain.framebuffers + i));
}
SET_DEBUG_NAMEF(g_swapchain.images[i], VK_OBJECT_TYPE_IMAGE, "swapchain image[%d]", i);
}
return true;
}
qboolean R_VkSwapchainInit( VkRenderPass render_pass, VkFormat depth_format ) {
const uint32_t prev_num_images = g_swapchain.num_images;
VkSurfaceCapabilitiesKHR surface_caps;
XVK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk_core.physical_device.device, vk_core.surface.surface, &surface_caps));
/*
[2023:10:09|13:03:52] Error: Validation: Validation Error: [ VUID-VkSwapchainCreateInfoKHR-imageFormat-01778 ] Object 0: handle = 0x555556af6a00, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xc036022f | vkCreateSwapchainKHR(): pCreateInfo->imageFormat VK_FORMAT_B8G8R8A8_SRGB with tiling VK_IMAGE_TILING_OPTIMAL does not support usage that includes VK_IMAGE_USAGE_STORAGE_BIT. The Vulkan spec states: The implied image creation parameters of the swapchain must be supported as reported by vkGetPhysicalDeviceImageFormatProperties (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-VkSwapchainCreateInfoKHR-imageFormat-01778)
*/
//g_swapchain.image_format = VK_FORMAT_B8G8R8A8_SRGB;
g_swapchain.image_format = VK_FORMAT_B8G8R8A8_UNORM; // TODO get from surface_formats
g_swapchain.render_pass = render_pass;
g_swapchain.depth_format = depth_format;
return recreateSwapchainIfNeeded( true );
}
void R_VkSwapchainShutdown( void ) {
destroySwapchainAndFramebuffers( g_swapchain.swapchain );
}
r_vk_swapchain_framebuffer_t R_VkSwapchainAcquire( VkSemaphore sem_image_available ) {
APROF_SCOPE_DECLARE_BEGIN(function, __FUNCTION__);
r_vk_swapchain_framebuffer_t ret = {0};
for (;;) {
// Check that swapchain has the same size
if (!recreateSwapchainIfNeeded(!!g_swapchain.recreate_requested)) {
goto finalize;
}
APROF_SCOPE_DECLARE_BEGIN_EX(vkAcquireNextImageKHR, "vkAcquireNextImageKHR", APROF_SCOPE_FLAG_WAIT);
const VkResult acquire_result = vkAcquireNextImageKHR(vk_core.device, g_swapchain.swapchain, UINT64_MAX, sem_image_available, VK_NULL_HANDLE, &ret.index);
APROF_SCOPE_END(vkAcquireNextImageKHR);
switch (acquire_result) {
case VK_SUCCESS:
g_swapchain.recreate_requested = 0;
break;
case VK_SUBOPTIMAL_KHR:
// Would need to wait on the semaphore here somehow
gEngine.Con_Printf(S_WARN "vkAcquireNextImageKHR returned %s (%0#x), will recreate swapchain for the next frame\n", R_VkResultName(acquire_result), acquire_result);
++g_swapchain.recreate_requested;
break;
case VK_ERROR_OUT_OF_HOST_MEMORY:
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
case VK_ERROR_DEVICE_LOST:
gEngine.Host_Error("vkAcquireNextImageKHR returned %s, this is unrecoverable, crashing.\n", R_VkResultName(acquire_result));
XVK_CHECK(acquire_result);
goto finalize;
case VK_TIMEOUT:
case VK_NOT_READY:
gEngine.Con_Printf(S_ERROR "vkAcquireNextImageKHR returned %s (%0#x), frame will be lost\n", R_VkResultName(acquire_result), acquire_result);
goto finalize;
case VK_ERROR_OUT_OF_DATE_KHR:
case VK_ERROR_SURFACE_LOST_KHR:
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
default:
gEngine.Con_Printf(S_WARN "vkAcquireNextImageKHR returned %s (%0#x)\n", R_VkResultName(acquire_result), acquire_result);
if (g_swapchain.recreate_requested) {
gEngine.Con_Printf(S_WARN "second vkAcquireNextImageKHR failed with %s, frame will be lost\n", R_VkResultName(acquire_result));
goto finalize;
}
++g_swapchain.recreate_requested;
continue;
}
break;
}
ret.framebuffer = g_swapchain.framebuffers[ret.index];
ret.width = g_swapchain.width;
ret.height = g_swapchain.height;
ret.image = g_swapchain.images[ret.index];
ret.view = g_swapchain.image_views[ret.index];
finalize:
APROF_SCOPE_END(function);
return ret;
}
void R_VkSwapchainPresent( uint32_t index, VkSemaphore done ) {
const VkPresentInfoKHR presinfo = {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pSwapchains = &g_swapchain.swapchain,
.pImageIndices = &index,
.swapchainCount = 1,
.pWaitSemaphores = &done,
.waitSemaphoreCount = 1,
};
const VkResult present_result = vkQueuePresentKHR(vk_core.queue, &presinfo);
switch (present_result) {
case VK_ERROR_OUT_OF_DATE_KHR:
case VK_ERROR_SURFACE_LOST_KHR:
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
gEngine.Con_Printf(S_WARN "vkQueuePresentKHR returned %s, frame will be lost\n", R_VkResultName(present_result));
break;
case VK_SUBOPTIMAL_KHR:
gEngine.Con_Printf(S_WARN "vkQueuePresentKHR returned %s\n", R_VkResultName(present_result));
break;
default:
XVK_CHECK(present_result);
}
}