Avoid restarting the whole video on VK_ERROR_OUT_OF_DATE_KHR

If an error is detected in vkAcquireNextImageKHR or vkQueuePresentKHR,
avoid restarting the whole video system and the game window. Instead,
shut down the Vulkan subsystem as gracefully as possible and restart it
without touching the window.

This fixes the problem with infinite video restarts under the Gnome
desktop environment.
This commit is contained in:
Ricardo Garcia 2021-03-07 23:11:10 +01:00
parent 80c637b5d6
commit 6989d22a2e
3 changed files with 77 additions and 40 deletions

View file

@ -260,6 +260,8 @@ extern qvktexture_t vk_colorbuffer;
extern qvktexture_t vk_colorbufferWarp;
// indicator if the frame is currently being rendered
extern qboolean vk_frameStarted;
// Indicates if the renderer needs to be restarted.
extern qboolean vk_restartNeeded;
// function pointers
extern PFN_vkCreateDebugUtilsMessengerEXT qvkCreateDebugUtilsMessengerEXT;
@ -271,8 +273,12 @@ extern PFN_vkCmdEndDebugUtilsLabelEXT qvkCmdEndDebugUtilsLabelEXT;
extern PFN_vkCmdInsertDebugUtilsLabelEXT qvkInsertDebugUtilsLabelEXT;
// The Interface Functions (tm)
qboolean QVk_Init(SDL_Window *window);
void QVk_SetWindow(SDL_Window*);
qboolean QVk_Init(void);
void QVk_PostInit(void);
void QVk_WaitAndShutdownAll(void);
void QVk_Shutdown(void);
void QVk_Restart(void);
void QVk_CreateValidationLayers(void);
void QVk_DestroyValidationLayers(void);
qboolean QVk_CreateDevice(int preferredDeviceIdx);

View file

@ -31,6 +31,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include <float.h>
#include "header/local.h"
static SDL_Window *vk_window;
// Vulkan instance, surface and memory allocator
VkInstance vk_instance = VK_NULL_HANDLE;
VkSurfaceKHR vk_surface = VK_NULL_HANDLE;
@ -121,6 +123,8 @@ static uint32_t vk_imageIndex = 0;
static int vk_activeStagingBuffer = 0;
// started rendering frame?
qboolean vk_frameStarted = false;
// the renderer needs to be restarted.
qboolean vk_restartNeeded = false;
// render pipelines
qvkpipeline_t vk_drawTexQuadPipeline[RP_COUNT] = {
@ -1637,13 +1641,46 @@ void QVk_Shutdown( void )
}
}
void QVk_SetWindow(SDL_Window *window)
{
vk_window = window;
}
void QVk_WaitAndShutdownAll (void)
{
if (vk_device.logical != VK_NULL_HANDLE)
{
vkDeviceWaitIdle(vk_device.logical);
}
Mod_FreeAll();
Vk_ShutdownImages();
QVk_Shutdown();
}
void QVk_Restart(void)
{
QVk_WaitAndShutdownAll();
if (!QVk_Init())
ri.Sys_Error(ERR_FATAL, "Unable to restart Vulkan renderer");
QVk_PostInit();
}
void QVk_PostInit(void)
{
Vk_InitImages();
Mod_Init();
RE_InitParticleTexture();
Draw_InitLocal();
}
/*
** QVk_Init
**
** This is responsible for initializing Vulkan.
**
*/
qboolean QVk_Init(SDL_Window *window)
qboolean QVk_Init(void)
{
PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion)vkGetInstanceProcAddr(NULL, "vkEnumerateInstanceVersion");
uint32_t instanceVersion = VK_API_VERSION_1_0;
@ -1682,7 +1719,7 @@ qboolean QVk_Init(SDL_Window *window)
vk_config.triangle_fan_index_max_usage = 0;
vk_config.triangle_fan_index_count = TRIANGLE_FAN_INDEX_CNT;
if (!SDL_Vulkan_GetInstanceExtensions(window, &extCount, NULL))
if (!SDL_Vulkan_GetInstanceExtensions(vk_window, &extCount, NULL))
{
R_Printf(PRINT_ALL, "%s() SDL_Vulkan_GetInstanceExtensions failed: %s",
__func__, SDL_GetError());
@ -1694,7 +1731,7 @@ qboolean QVk_Init(SDL_Window *window)
extCount++;
wantedExtensions = malloc(extCount * sizeof(const char *));
if (!SDL_Vulkan_GetInstanceExtensions(window, &extCount, wantedExtensions))
if (!SDL_Vulkan_GetInstanceExtensions(vk_window, &extCount, wantedExtensions))
{
R_Printf(PRINT_ALL, "%s() SDL_Vulkan_GetInstanceExtensions failed: %s",
__func__, SDL_GetError());
@ -1798,7 +1835,7 @@ qboolean QVk_Init(SDL_Window *window)
if (vk_validation->value)
QVk_CreateValidationLayers();
if (!Vkimp_CreateSurface(window))
if (!Vkimp_CreateSurface(vk_window))
{
return false;
}
@ -2009,6 +2046,17 @@ VkResult QVk_BeginFrame(const VkViewport* viewport, const VkRect2D* scissor)
ReleaseSwapBuffers();
VkResult result = vkAcquireNextImageKHR(vk_device.logical, vk_swapchain.sc, UINT32_MAX, vk_imageAvailableSemaphores[vk_activeBufferIdx], VK_NULL_HANDLE, &vk_imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_SURFACE_LOST_KHR)
{
// for VK_OUT_OF_DATE_KHR and VK_SUBOPTIMAL_KHR it'd be fine to just rebuild the swapchain but let's take the easy way out and restart Vulkan.
R_Printf(PRINT_ALL, "%s(): received %s after vkAcquireNextImageKHR - restarting video!\n", __func__, QVk_GetError(result));
return result;
}
else if (result != VK_SUCCESS)
{
Sys_Error("%s(): unexpected error after vkAcquireNextImageKHR: %s", __func__, QVk_GetError(result));
}
vk_activeCmdbuffer = vk_commandbuffers[vk_activeBufferIdx];
// swap dynamic buffers
@ -2021,17 +2069,6 @@ VkResult QVk_BeginFrame(const VkViewport* viewport, const VkRect2D* scissor)
VK_VERIFY(buffer_invalidate(&vk_dynVertexBuffers[vk_activeDynBufferIdx].resource));
VK_VERIFY(buffer_invalidate(&vk_dynIndexBuffers[vk_activeDynBufferIdx].resource));
// for VK_OUT_OF_DATE_KHR and VK_SUBOPTIMAL_KHR it'd be fine to just rebuild the swapchain but let's take the easy way out and restart video system
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_SURFACE_LOST_KHR)
{
R_Printf(PRINT_ALL, "%s(): received %s after vkAcquireNextImageKHR - restarting video!\n", __func__, QVk_GetError(result));
return result;
}
else if (result != VK_SUCCESS)
{
Sys_Error("%s(): unexpected error after vkAcquireNextImageKHR: %s", __func__, QVk_GetError(result));
}
VK_VERIFY(vkWaitForFences(vk_device.logical, 1, &vk_fences[vk_activeBufferIdx], VK_TRUE, UINT32_MAX));
VK_VERIFY(vkResetFences(vk_device.logical, 1, &vk_fences[vk_activeBufferIdx]));
@ -2109,8 +2146,8 @@ VkResult QVk_EndFrame(qboolean force)
// for VK_OUT_OF_DATE_KHR and VK_SUBOPTIMAL_KHR it'd be fine to just rebuild the swapchain but let's take the easy way out and restart video system
if (renderResult == VK_ERROR_OUT_OF_DATE_KHR || renderResult == VK_SUBOPTIMAL_KHR || renderResult == VK_ERROR_SURFACE_LOST_KHR)
{
R_Printf(PRINT_ALL, "%s(): received %s after vkQueuePresentKHR - restarting video!\n", __func__, QVk_GetError(renderResult));
vid_fullscreen->modified = true;
R_Printf(PRINT_ALL, "%s(): received %s after vkQueuePresentKHR - will restart video!\n", __func__, QVk_GetError(renderResult));
vk_restartNeeded = true;
}
else if (renderResult != VK_SUCCESS)
{

View file

@ -1328,10 +1328,7 @@ static qboolean RE_Init( void )
// print device information during startup
Vk_Strings_f();
Vk_InitImages();
Mod_Init();
RE_InitParticleTexture();
Draw_InitLocal();
QVk_PostInit();
R_Printf(PRINT_ALL, "Successfully initialized Vulkan!\n");
@ -1347,7 +1344,6 @@ static qboolean RE_Init( void )
*/
static void RE_ShutdownContext( void )
{
// Shutdown Vulkan subsystem
QVk_Shutdown();
}
@ -1365,14 +1361,7 @@ void RE_Shutdown (void)
ri.Cmd_RemoveCommand("screenshot");
ri.Cmd_RemoveCommand( "modellist" );
if (vk_device.logical != VK_NULL_HANDLE)
{
vkDeviceWaitIdle(vk_device.logical);
}
Mod_FreeAll();
Vk_ShutdownImages();
RE_ShutdownContext();
QVk_WaitAndShutdownAll();
}
/*
@ -1388,6 +1377,7 @@ RE_BeginFrame( float camera_separation )
// if ri.Sys_Error() had been issued mid-frame, we might end up here without properly submitting the image, so call QVk_EndFrame to be safe
QVk_EndFrame(true);
/*
** change modes if necessary
*/
@ -1417,18 +1407,21 @@ RE_BeginFrame( float camera_separation )
}
}
VkResult swapChainValid = QVk_BeginFrame(&vk_viewport, &vk_scissor);
// if the swapchain is invalid, just recreate the video system and revert to safe windowed mode
if (swapChainValid != VK_SUCCESS)
if (vk_restartNeeded)
{
vid_fullscreen->value = false;
vid_fullscreen->modified = true;
ri.Cvar_SetValue("vid_fullscreen", 0);
QVk_Restart();
vk_restartNeeded = false;
}
else
for (;;)
{
QVk_BeginRenderpass(RP_WORLD);
VkResult swapChainValid = QVk_BeginFrame(&vk_viewport, &vk_scissor);
if (swapChainValid == VK_SUCCESS)
break;
QVk_Restart();
}
QVk_BeginRenderpass(RP_WORLD);
}
/*
@ -1590,7 +1583,8 @@ RE_InitContext(void *win)
SDL_SetWindowTitle(window, title);
// window is ready, initialize Vulkan now
if (!QVk_Init(window))
QVk_SetWindow(window);
if (!QVk_Init())
{
R_Printf(PRINT_ALL, "%s() - could not initialize Vulkan!\n", __func__);
return false;