From 627a3109b32dde253ea103c1484d673b5b1f4391 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Thu, 26 Nov 2020 01:32:29 +1000 Subject: [PATCH] libretro: Re-query hardware render interface after AV system info change I suspect the frontend is supposed to call context_reset/destroy here, but it's not for whatever reason, and this works around it. --- .../libretro_d3d11_host_display.cpp | 28 ++++++++++++++ .../libretro_d3d11_host_display.h | 1 + .../libretro_host_interface.cpp | 31 ++++++++------- .../libretro_opengl_host_display.cpp | 6 +++ .../libretro_opengl_host_display.h | 1 + .../libretro_vulkan_host_display.cpp | 38 +++++++++++++++---- .../libretro_vulkan_host_display.h | 3 +- 7 files changed, 87 insertions(+), 21 deletions(-) diff --git a/src/duckstation-libretro/libretro_d3d11_host_display.cpp b/src/duckstation-libretro/libretro_d3d11_host_display.cpp index 039797b60..2c2a8d8b4 100644 --- a/src/duckstation-libretro/libretro_d3d11_host_display.cpp +++ b/src/duckstation-libretro/libretro_d3d11_host_display.cpp @@ -71,6 +71,34 @@ void LibretroD3D11HostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new_ m_window_info.surface_height = static_cast(new_window_height); } +bool LibretroD3D11HostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) +{ + // Check that the device hasn't changed. + retro_hw_render_interface* ri = nullptr; + if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, &ri)) + { + Log_ErrorPrint("Failed to get HW render interface"); + return false; + } + else if (ri->interface_type != RETRO_HW_RENDER_INTERFACE_D3D11 || + ri->interface_version != RETRO_HW_RENDER_INTERFACE_D3D11_VERSION) + { + Log_ErrorPrintf("Unexpected HW interface - type %u version %u", static_cast(ri->interface_type), + static_cast(ri->interface_version)); + return false; + } + + const retro_hw_render_interface_d3d11* d3d11_ri = reinterpret_cast(ri); + if (d3d11_ri->device != m_device.Get() || d3d11_ri->context != m_context.Get()) + { + Log_ErrorPrintf("D3D device/context changed outside our control"); + return false; + } + + m_window_info = new_wi; + return true; +} + bool LibretroD3D11HostDisplay::Render() { const u32 resolution_scale = g_libretro_host_interface.GetResolutionScale(); diff --git a/src/duckstation-libretro/libretro_d3d11_host_display.h b/src/duckstation-libretro/libretro_d3d11_host_display.h index 663d4443d..21f955be0 100644 --- a/src/duckstation-libretro/libretro_d3d11_host_display.h +++ b/src/duckstation-libretro/libretro_d3d11_host_display.h @@ -14,6 +14,7 @@ public: bool CreateRenderDevice(const WindowInfo& wi, std::string_view adapter_name, bool debug_device) override; void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + bool ChangeRenderWindow(const WindowInfo& new_wi) override; void SetVSync(bool enabled) override; diff --git a/src/duckstation-libretro/libretro_host_interface.cpp b/src/duckstation-libretro/libretro_host_interface.cpp index 890a025c7..bcab9298e 100644 --- a/src/duckstation-libretro/libretro_host_interface.cpp +++ b/src/duckstation-libretro/libretro_host_interface.cpp @@ -1145,6 +1145,16 @@ void LibretroHostInterface::HardwareRendererContextReset() void LibretroHostInterface::SwitchToHardwareRenderer() { + struct retro_system_av_info avi; + g_libretro_host_interface.GetSystemAVInfo(&avi, true); + + WindowInfo wi; + wi.type = WindowInfo::Type::Libretro; + wi.display_connection = &g_libretro_host_interface.m_hw_render_callback; + wi.surface_width = avi.geometry.base_width; + wi.surface_height = avi.geometry.base_height; + wi.surface_scale = 1.0f; + // use the existing device if we just resized the window std::optional renderer; std::unique_ptr display = std::move(m_hw_render_display); @@ -1152,10 +1162,15 @@ void LibretroHostInterface::SwitchToHardwareRenderer() { Log_InfoPrintf("Using existing hardware display"); renderer = RenderAPIToRenderer(display->GetRenderAPI()); - if (!display->CreateResources()) - Panic("Failed to recreate resources after reinit"); + if (!display->ChangeRenderWindow(wi) || !display->CreateResources()) + { + Log_ErrorPrintf("Failed to recreate resources after reinit"); + display->DestroyRenderDevice(); + display.reset(); + } } - else + + if (!display) { renderer = RetroHwContextToRenderer(m_hw_render_callback.context_type); if (!renderer.has_value()) @@ -1184,16 +1199,6 @@ void LibretroHostInterface::SwitchToHardwareRenderer() Log_ErrorPrintf("Unhandled renderer '%s'", Settings::GetRendererName(renderer.value())); return; } - - struct retro_system_av_info avi; - g_libretro_host_interface.GetSystemAVInfo(&avi, true); - - WindowInfo wi; - wi.type = WindowInfo::Type::Libretro; - wi.display_connection = &g_libretro_host_interface.m_hw_render_callback; - wi.surface_width = avi.geometry.base_width; - wi.surface_height = avi.geometry.base_height; - wi.surface_scale = 1.0f; if (!display || !display->CreateRenderDevice(wi, {}, g_settings.gpu_use_debug_device) || !display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device)) { diff --git a/src/duckstation-libretro/libretro_opengl_host_display.cpp b/src/duckstation-libretro/libretro_opengl_host_display.cpp index f508bcc19..fa819a86f 100644 --- a/src/duckstation-libretro/libretro_opengl_host_display.cpp +++ b/src/duckstation-libretro/libretro_opengl_host_display.cpp @@ -135,6 +135,12 @@ void LibretroOpenGLHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new m_window_info.surface_height = static_cast(new_window_height); } +bool LibretroOpenGLHostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) +{ + m_window_info = new_wi; + return true; +} + bool LibretroOpenGLHostDisplay::Render() { const GLuint fbo = static_cast( diff --git a/src/duckstation-libretro/libretro_opengl_host_display.h b/src/duckstation-libretro/libretro_opengl_host_display.h index 2f5bb2012..3049248cb 100644 --- a/src/duckstation-libretro/libretro_opengl_host_display.h +++ b/src/duckstation-libretro/libretro_opengl_host_display.h @@ -21,6 +21,7 @@ public: void DestroyRenderDevice() override; void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + bool ChangeRenderWindow(const WindowInfo& new_wi) override; void SetVSync(bool enabled) override; diff --git a/src/duckstation-libretro/libretro_vulkan_host_display.cpp b/src/duckstation-libretro/libretro_vulkan_host_display.cpp index d4d3f29ee..8af173c75 100644 --- a/src/duckstation-libretro/libretro_vulkan_host_display.cpp +++ b/src/duckstation-libretro/libretro_vulkan_host_display.cpp @@ -107,11 +107,8 @@ bool LibretroVulkanHostDisplay::CreateRenderDevice(const WindowInfo& wi, std::st return false; } - // Keeping the pointer instead of memcpying can cause crashes, e.g. fullscreen switches. - std::memcpy(&m_ri, reinterpret_cast(ri), - sizeof(retro_hw_render_interface_vulkan)); - // TODO: Grab queue? it should be the same + m_ri = reinterpret_cast(ri); return true; } @@ -150,6 +147,33 @@ void LibretroVulkanHostDisplay::ResizeRenderWindow(s32 new_window_width, s32 new m_window_info.surface_height = static_cast(new_window_height); } +bool LibretroVulkanHostDisplay::ChangeRenderWindow(const WindowInfo& new_wi) +{ + // re-query hardware render interface - in vulkan, things get recreated without us being notified + retro_hw_render_interface* ri = nullptr; + if (!g_retro_environment_callback(RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, &ri)) + { + Log_ErrorPrint("Failed to get HW render interface"); + return false; + } + else if (ri->interface_type != RETRO_HW_RENDER_INTERFACE_VULKAN || + ri->interface_version != RETRO_HW_RENDER_INTERFACE_VULKAN_VERSION) + { + Log_ErrorPrintf("Unexpected HW interface - type %u version %u", static_cast(ri->interface_type), + static_cast(ri->interface_version)); + return false; + } + + retro_hw_render_interface_vulkan* vri = reinterpret_cast(ri); + if (vri != m_ri) + { + Log_WarningPrintf("HW render interface pointer changed without us being notified, this might cause issues?"); + m_ri = vri; + } + + return true; +} + bool LibretroVulkanHostDisplay::Render() { const u32 resolution_scale = g_libretro_host_interface.GetResolutionScale(); @@ -186,13 +210,13 @@ bool LibretroVulkanHostDisplay::Render() vkCmdEndRenderPass(cmdbuffer); m_frame_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); m_frame_view.image_layout = m_frame_texture.GetLayout(); - m_ri.set_image(m_ri.handle, &m_frame_view, 0, nullptr, VK_QUEUE_FAMILY_IGNORED); + m_ri->set_image(m_ri->handle, &m_frame_view, 0, nullptr, VK_QUEUE_FAMILY_IGNORED); // TODO: We can't use this because it doesn't support passing fences... // m_ri.set_command_buffers(m_ri.handle, 1, &cmdbuffer); - m_ri.lock_queue(m_ri.handle); + m_ri->lock_queue(m_ri->handle); g_vulkan_context->SubmitCommandBuffer(); - m_ri.unlock_queue(m_ri.handle); + m_ri->unlock_queue(m_ri->handle); g_vulkan_context->MoveToNextCommandBuffer(); g_retro_video_refresh_callback(RETRO_HW_FRAME_BUFFER_VALID, display_width, display_height, 0); diff --git a/src/duckstation-libretro/libretro_vulkan_host_display.h b/src/duckstation-libretro/libretro_vulkan_host_display.h index 9b7f1ce46..5a55fc121 100644 --- a/src/duckstation-libretro/libretro_vulkan_host_display.h +++ b/src/duckstation-libretro/libretro_vulkan_host_display.h @@ -18,6 +18,7 @@ public: void DestroyRenderDevice() override; void ResizeRenderWindow(s32 new_window_width, s32 new_window_height) override; + bool ChangeRenderWindow(const WindowInfo& new_wi) override; void SetVSync(bool enabled) override; @@ -33,7 +34,7 @@ private: bool CheckFramebufferSize(u32 width, u32 height); - retro_hw_render_interface_vulkan m_ri; + retro_hw_render_interface_vulkan* m_ri = nullptr; Vulkan::Texture m_frame_texture; retro_vulkan_image m_frame_view = {};