From 134c82abbabc6e7934dba15818c3f14eb651ce72 Mon Sep 17 00:00:00 2001 From: Krzysztof Kondrak Date: Mon, 4 May 2020 18:23:36 +0200 Subject: [PATCH] Don't rebuild entire window if Vulkan fails (fixes #95). --- README.md | 1 + linux/vid_so.c | 7 +++++ macos/vid_dsym.c | 7 +++++ ref_vk/qvk.h | 2 ++ ref_vk/vk_common.c | 28 ++++++++++--------- ref_vk/vk_draw.c | 3 ++ ref_vk/vk_local.h | 2 +- ref_vk/vk_rmain.c | 69 ++++++++++++++++++++++++++++++++++++++++------ ref_vk/vk_rmisc.c | 9 ++++++ win32/vid_dll.c | 8 ++++++ 10 files changed, 114 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 25b949c2..cacf48ea 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ The following commands are available when using the Vulkan renderer: | `vk_aniso` | Toggle anisotropic filtering. (default: `1`) | | `vk_vsync` | Toggle vertical sync. (default: `0`) | | `vk_postprocess` | Toggle additional color/gamma correction. (default: `1`) | +| `vk_restart` | Recreate entire Vulkan subsystem. | | `vk_mip_nearfilter` | Use nearest-neighbor filtering for mipmaps. (default: `0`) | | `vk_texturemode` | Change current texture filtering mode:
`VK_NEAREST` - nearest-neighbor interpolation, no mipmaps
`VK_LINEAR` - linear interpolation, no mipmaps
`VK_MIPMAP_NEAREST` - nearest-neighbor interpolation with mipmaps
`VK_MIPMAP_LINEAR` - linear interpolation with mipmaps (default) | | `vk_lmaptexturemode` | Same as `vk_texturemode` but applied to lightmap textures. | diff --git a/linux/vid_so.c b/linux/vid_so.c index 3c624acf..ec0b0ae1 100644 --- a/linux/vid_so.c +++ b/linux/vid_so.c @@ -46,6 +46,7 @@ cvar_t *vid_ref; // Name of Refresh DLL loaded cvar_t *vid_xpos; // X coordinate of window position cvar_t *vid_ypos; // Y coordinate of window position cvar_t *vid_fullscreen; +cvar_t *vid_refresh; cvar_t *vid_hudscale; cvar_t *r_customwidth; cvar_t *r_customheight; @@ -429,6 +430,11 @@ Com_Printf("Trying mode 0\n"); cls.disable_screen = false; } + if ( vid_refresh->modified ) + { + vid_refresh->modified = false; + cl.refresh_prepped = false; + } } /* @@ -442,6 +448,7 @@ void VID_Init (void) vid_xpos = Cvar_Get ("vid_xpos", "3", CVAR_ARCHIVE); vid_ypos = Cvar_Get ("vid_ypos", "22", CVAR_ARCHIVE); vid_fullscreen = Cvar_Get ("vid_fullscreen", "0", CVAR_ARCHIVE); + vid_refresh = Cvar_Get ("vid_refresh", "0", CVAR_NOSET); vid_gamma = Cvar_Get( "vid_gamma", "1", CVAR_ARCHIVE ); r_customwidth = Cvar_Get( "r_customwidth", "1024", CVAR_ARCHIVE ); r_customheight = Cvar_Get( "r_customheight", "768", CVAR_ARCHIVE ); diff --git a/macos/vid_dsym.c b/macos/vid_dsym.c index 49acb3e2..903ead29 100644 --- a/macos/vid_dsym.c +++ b/macos/vid_dsym.c @@ -42,6 +42,7 @@ cvar_t *vid_ref; // Name of Refresh DLL loaded cvar_t *vid_xpos; // X coordinate of window position cvar_t *vid_ypos; // Y coordinate of window position cvar_t *vid_fullscreen; +cvar_t *vid_refresh; cvar_t *vid_hudscale; cvar_t *r_customwidth; cvar_t *r_customheight; @@ -403,6 +404,11 @@ void VID_CheckChanges (void) cls.disable_screen = false; } + if ( vid_refresh->modified ) + { + vid_refresh->modified = false; + cl.refresh_prepped = false; + } } /* @@ -416,6 +422,7 @@ void VID_Init (void) vid_xpos = Cvar_Get ("vid_xpos", "3", CVAR_ARCHIVE); vid_ypos = Cvar_Get ("vid_ypos", "22", CVAR_ARCHIVE); vid_fullscreen = Cvar_Get ("vid_fullscreen", "0", CVAR_ARCHIVE); + vid_refresh = Cvar_Get ("vid_refresh", "0", CVAR_NOSET); vid_gamma = Cvar_Get( "vid_gamma", "1", CVAR_ARCHIVE ); r_customwidth = Cvar_Get( "r_customwidth", "1024", CVAR_ARCHIVE ); r_customheight = Cvar_Get( "r_customheight", "768", CVAR_ARCHIVE ); diff --git a/ref_vk/qvk.h b/ref_vk/qvk.h index 877cfb8a..d999037a 100644 --- a/ref_vk/qvk.h +++ b/ref_vk/qvk.h @@ -253,6 +253,8 @@ extern qvktexture_t vk_colorbuffer; extern qvktexture_t vk_colorbufferWarp; // indicator if the frame is currently being rendered extern qboolean vk_frameStarted; +// indicator if the renderer needs to restart next frame +extern qboolean vk_restart; // function pointers extern PFN_vkCreateDebugUtilsMessengerEXT qvkCreateDebugUtilsMessengerEXT; diff --git a/ref_vk/vk_common.c b/ref_vk/vk_common.c index 83512adc..7818590f 100644 --- a/ref_vk/vk_common.c +++ b/ref_vk/vk_common.c @@ -134,6 +134,8 @@ uint32_t vk_imageIndex = 0; int vk_activeStagingBuffer = 0; // started rendering frame? qboolean vk_frameStarted = false; +// renderer needs to restart next frame? +qboolean vk_restart = false; // render pipelines qvkpipeline_t vk_drawTexQuadPipeline = QVKPIPELINE_INIT; @@ -1848,6 +1850,17 @@ VkResult QVk_BeginFrame() ReleaseSwapBuffers(); VkResult result = vkAcquireNextImageKHR(vk_device.logical, vk_swapchain.sc, UINT32_MAX, vk_imageAvailableSemaphores[vk_activeBufferIdx], VK_NULL_HANDLE, &vk_imageIndex); + // 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) + { + ri.Con_Printf(PRINT_ALL, "QVk_BeginFrame(): received %s after vkAcquireNextImageKHR - restarting video!\n", QVk_GetError(result)); + return result; + } + else if (result != VK_SUCCESS) + { + Sys_Error("QVk_BeginFrame(): unexpected error after vkAcquireNextImageKHR: %s", QVk_GetError(result)); + } + vk_activeCmdbuffer = vk_commandbuffers[vk_activeBufferIdx]; // swap dynamic buffers @@ -1860,17 +1873,6 @@ VkResult QVk_BeginFrame() vmaInvalidateAllocation(vk_malloc, vk_dynVertexBuffers[vk_activeDynBufferIdx].allocation, 0, VK_WHOLE_SIZE); vmaInvalidateAllocation(vk_malloc, vk_dynIndexBuffers[vk_activeDynBufferIdx].allocation, 0, VK_WHOLE_SIZE); - // 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) - { - ri.Con_Printf(PRINT_ALL, "QVk_BeginFrame(): received %s after vkAcquireNextImageKHR - restarting video!\n", QVk_GetError(result)); - return result; - } - else if (result != VK_SUCCESS) - { - Sys_Error("QVk_BeginFrame(): unexpected error after vkAcquireNextImageKHR: %s", 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])); @@ -1895,7 +1897,8 @@ VkResult QVk_EndFrame(qboolean force) { // continue only if QVk_BeginFrame() had been previously issued if (!vk_frameStarted) - return VK_NOT_READY; + return VK_SUCCESS; + // this may happen if Sys_Error is issued mid-frame, so we need to properly advance the draw pipeline if (force) { @@ -1944,7 +1947,6 @@ VkResult QVk_EndFrame(qboolean force) if (renderResult == VK_ERROR_OUT_OF_DATE_KHR || renderResult == VK_SUBOPTIMAL_KHR || renderResult == VK_ERROR_SURFACE_LOST_KHR) { ri.Con_Printf(PRINT_ALL, "QVk_EndFrame(): received %s after vkQueuePresentKHR - restarting video!\n", QVk_GetError(renderResult)); - vid_ref->modified = true; } else if (renderResult != VK_SUCCESS) { diff --git a/ref_vk/vk_draw.c b/ref_vk/vk_draw.c index f60ae35b..f694dbb2 100644 --- a/ref_vk/vk_draw.c +++ b/ref_vk/vk_draw.c @@ -252,6 +252,9 @@ void Draw_StretchRaw (int x, int y, int w, int h, int cols, int rows, byte *data int row; float t; + if (!vk_frameStarted) + return; + if (rows <= 256) { hscale = 1; diff --git a/ref_vk/vk_local.h b/ref_vk/vk_local.h index cd8e0e79..fa99ad27 100644 --- a/ref_vk/vk_local.h +++ b/ref_vk/vk_local.h @@ -44,7 +44,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "qvk.h" #define REF_VERSION "Vulkan (vkQuake2 v"VKQUAKE2_VERSION")" -#define VKQUAKE2_VERSION "1.4.4" +#define VKQUAKE2_VERSION "1.4.5" // verify if VkResult is VK_SUCCESS #ifdef _DEBUG diff --git a/ref_vk/vk_rmain.c b/ref_vk/vk_rmain.c index 853f4546..f9e7d02e 100644 --- a/ref_vk/vk_rmain.c +++ b/ref_vk/vk_rmain.c @@ -46,6 +46,7 @@ int c_brush_polys, c_alias_polys; float v_blend[4]; // final blending color void Vk_Strings_f(void); +void Vk_PollRestart_f(void); void Vk_Mem_f(void); // @@ -124,6 +125,7 @@ cvar_t *vk_device_idx; cvar_t *vid_fullscreen; cvar_t *vid_gamma; cvar_t *vid_ref; +cvar_t *vid_refresh; cvar_t *viewsize; /* @@ -1075,9 +1077,11 @@ void R_Register( void ) vid_fullscreen = ri.Cvar_Get("vid_fullscreen", "0", CVAR_ARCHIVE); vid_gamma = ri.Cvar_Get("vid_gamma", "1.0", CVAR_ARCHIVE); vid_ref = ri.Cvar_Get("vid_ref", "soft", CVAR_ARCHIVE); + vid_refresh = ri.Cvar_Get("vid_refresh", "0", CVAR_NOSET); viewsize = ri.Cvar_Get("viewsize", "100", CVAR_ARCHIVE); ri.Cmd_AddCommand("vk_strings", Vk_Strings_f); + ri.Cmd_AddCommand("vk_restart", Vk_PollRestart_f); ri.Cmd_AddCommand("vk_mem", Vk_Mem_f); ri.Cmd_AddCommand("imagelist", Vk_ImageList_f); ri.Cmd_AddCommand("screenshot", Vk_ScreenShot_f); @@ -1196,6 +1200,7 @@ void R_Shutdown (void) { ri.Cmd_RemoveCommand("vk_strings"); ri.Cmd_RemoveCommand("vk_mem"); + ri.Cmd_RemoveCommand("vk_restart"); ri.Cmd_RemoveCommand("imagelist"); ri.Cmd_RemoveCommand("screenshot"); @@ -1220,13 +1225,16 @@ R_BeginFrame void R_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); + if (QVk_EndFrame(true) != VK_SUCCESS) + { + Vk_PollRestart_f(); + return; + } /* ** change modes if necessary */ - if (vk_mode->modified || vid_fullscreen->modified || vk_msaa->modified || vk_clear->modified || vk_picmip->modified || - vk_validation->modified || vk_texturemode->modified || vk_lmaptexturemode->modified || vk_aniso->modified || vid_gamma->modified || - vk_mip_nearfilter->modified || vk_sampleshading->modified || vk_vsync->modified || vk_device_idx->modified) + if (vk_mode->modified || vid_fullscreen->modified || vk_texturemode->modified || + vk_lmaptexturemode->modified || vk_aniso->modified || vk_device_idx->modified) { if (vk_texturemode->modified || vk_lmaptexturemode->modified || vk_aniso->modified) { @@ -1267,9 +1275,7 @@ void R_BeginFrame( float camera_separation ) // if the swapchain is invalid, just recreate the video system and revert to safe windowed mode if (swapChainValid != VK_SUCCESS) { - vid_ref->modified = true; - vid_fullscreen->value = false; - ri.Cvar_SetValue("vid_fullscreen", 0); + Vk_PollRestart_f(); } else { @@ -1277,6 +1283,13 @@ void R_BeginFrame( float camera_separation ) } } +static qboolean R_ShouldRestart() +{ + return vk_restart || vk_validation->modified || vk_msaa->modified || vk_clear->modified || + vk_picmip->modified || vid_gamma->modified || vk_mip_nearfilter->modified || + vk_sampleshading->modified || vk_vsync->modified; +} + /* @@@@@@@@@@@@@@@@@@@@@ R_EndFrame @@ -1284,7 +1297,47 @@ R_EndFrame */ void R_EndFrame( void ) { - QVk_EndFrame(false); + if (QVk_EndFrame(false) != VK_SUCCESS) + Vk_PollRestart_f(); + + // restart Vulkan renderer without rebuilding the entire window + if (R_ShouldRestart()) + { + vk_restart = false; + vk_validation->modified = false; + vk_msaa->modified = false; + vk_clear->modified = false; + vk_picmip->modified = false; + vid_gamma->modified = false; + vk_mip_nearfilter->modified = false; + vk_sampleshading->modified = false; + vk_vsync->modified = false; + + // shutdown + vkDeviceWaitIdle(vk_device.logical); + Mod_FreeAll(); + Vk_ShutdownImages(); + QVk_Shutdown(); + numvktextures = 0; + + // initialize + if (!QVk_Init()) + { + ri.Sys_Error(ERR_FATAL, "R_EndFrame(): could not re-initialize Vulkan!"); + } + + ri.Con_Printf(PRINT_ALL, "Successfully restarted Vulkan!\n"); + + Vk_Strings_f(); + + Vk_InitImages(); + Mod_Init(); + R_InitParticleTexture(); + Draw_InitLocal(); + + extern cvar_t *vid_refresh; + vid_refresh->modified = true; + } } /* diff --git a/ref_vk/vk_rmisc.c b/ref_vk/vk_rmisc.c index bd7cc5e0..66191f7f 100644 --- a/ref_vk/vk_rmisc.c +++ b/ref_vk/vk_rmisc.c @@ -278,6 +278,15 @@ void Vk_Strings_f(void) ri.Con_Printf(PRINT_ALL, "\n"); } +/* +** Vk_PollRestart_f +*/ + +void Vk_PollRestart_f(void) +{ + vk_restart = true; +} + /* ** Vk_Mem_f */ diff --git a/win32/vid_dll.c b/win32/vid_dll.c index 0f13b7b6..e4517981 100644 --- a/win32/vid_dll.c +++ b/win32/vid_dll.c @@ -45,6 +45,7 @@ cvar_t *vid_ref; // Name of Refresh DLL loaded cvar_t *vid_xpos; // X coordinate of window position cvar_t *vid_ypos; // Y coordinate of window position cvar_t *vid_fullscreen; +cvar_t *vid_refresh; cvar_t *vid_hudscale; cvar_t *r_customwidth; cvar_t *r_customheight; @@ -735,6 +736,12 @@ void VID_CheckChanges (void) vid_xpos->modified = false; vid_ypos->modified = false; } + + if ( vid_refresh->modified ) + { + vid_refresh->modified = false; + cl.refresh_prepped = false; + } } /* @@ -749,6 +756,7 @@ void VID_Init (void) vid_xpos = Cvar_Get ("vid_xpos", "3", CVAR_ARCHIVE); vid_ypos = Cvar_Get ("vid_ypos", "22", CVAR_ARCHIVE); vid_fullscreen = Cvar_Get ("vid_fullscreen", "0", CVAR_ARCHIVE); + vid_refresh = Cvar_Get ("vid_refresh", "0", CVAR_NOSET); vid_gamma = Cvar_Get( "vid_gamma", "1", CVAR_ARCHIVE ); win_noalttab = Cvar_Get( "win_noalttab", "0", CVAR_ARCHIVE ); r_customwidth = Cvar_Get( "r_customwidth", "1024", CVAR_ARCHIVE );