From e5c5ba22d7f400a1768326befdf49bc6d6c5046a Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 12 Oct 2024 22:18:48 +1000 Subject: [PATCH] GPUDevice: Extract swap chain to separate class --- src/core/fullscreen_ui.cpp | 7 +- src/core/gpu.cpp | 49 +- src/core/gpu_hw.cpp | 8 +- src/core/gte.cpp | 6 +- src/core/host.cpp | 87 +++- src/core/host.h | 18 +- src/core/imgui_overlays.cpp | 9 +- src/core/settings.cpp | 15 +- src/core/settings.h | 3 - src/core/system.cpp | 97 ++-- src/duckstation-qt/displaywidget.cpp | 4 +- src/duckstation-qt/displaywidget.h | 4 +- src/duckstation-qt/graphicssettingswidget.cpp | 4 +- src/duckstation-qt/mainwindow.cpp | 38 +- src/duckstation-qt/mainwindow.h | 4 +- src/duckstation-qt/qthost.cpp | 66 ++- src/duckstation-qt/qthost.h | 12 +- src/duckstation-qt/qtutils.cpp | 9 +- src/duckstation-qt/qtutils.h | 4 +- src/util/CMakeLists.txt | 4 +- src/util/d3d11_device.cpp | 371 +++++++------- src/util/d3d11_device.h | 82 ++- src/util/d3d11_stream_buffer.cpp | 2 +- src/util/d3d11_texture.cpp | 2 +- src/util/d3d12_device.cpp | 473 +++++++++--------- src/util/d3d12_device.h | 111 ++-- src/util/d3d12_pipeline.cpp | 2 +- src/util/d3d12_stream_buffer.cpp | 2 +- src/util/d3d12_texture.cpp | 2 +- src/util/d3d_common.cpp | 44 +- src/util/d3d_common.h | 8 +- src/util/gpu_device.cpp | 219 ++++---- src/util/gpu_device.h | 138 ++--- src/util/imgui_manager.cpp | 12 +- src/util/input_manager.cpp | 29 +- src/util/input_manager.h | 9 +- src/util/metal_device.h | 63 ++- src/util/metal_device.mm | 356 ++++++------- src/util/metal_layer.h | 5 +- src/util/metal_stream_buffer.mm | 2 +- src/util/opengl_context.cpp | 20 +- src/util/opengl_context.h | 23 +- src/util/opengl_context_agl.mm | 2 +- src/util/opengl_context_egl.cpp | 367 +++++++------- src/util/opengl_context_egl.h | 48 +- src/util/opengl_context_egl_wayland.cpp | 150 +++--- src/util/opengl_context_egl_wayland.h | 24 +- src/util/opengl_context_egl_x11.cpp | 30 +- src/util/opengl_context_egl_x11.h | 12 +- src/util/opengl_context_wgl.cpp | 360 +++++++------ src/util/opengl_context_wgl.h | 33 +- src/util/opengl_device.cpp | 221 ++++---- src/util/opengl_device.h | 52 +- src/util/opengl_pipeline.cpp | 2 +- src/util/opengl_texture.cpp | 2 +- src/util/platform_misc_mac.mm | 31 +- src/util/postprocessing.cpp | 6 +- src/util/postprocessing_shader_fx.cpp | 9 +- src/util/postprocessing_shader_glsl.cpp | 3 +- src/util/util.props | 3 +- src/util/vulkan_device.cpp | 302 ++++------- src/util/vulkan_device.h | 48 +- src/util/vulkan_loader.cpp | 2 +- src/util/vulkan_pipeline.cpp | 2 +- src/util/vulkan_stream_buffer.cpp | 3 +- src/util/vulkan_swap_chain.cpp | 456 +++++++++-------- src/util/vulkan_swap_chain.h | 66 +-- src/util/vulkan_texture.cpp | 2 +- src/util/window_info.cpp | 17 +- src/util/window_info.h | 23 +- 70 files changed, 2472 insertions(+), 2227 deletions(-) diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 7b9de5c27..f633564d0 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -4341,10 +4341,11 @@ void FullscreenUI::DrawDisplaySettingsPage() options.emplace_back(FSUI_STR("Borderless Fullscreen"), strvalue.has_value() && strvalue->empty()); if (selected_adapter) { - for (const std::string& mode : selected_adapter->fullscreen_modes) + for (const GPUDevice::ExclusiveFullscreenMode& mode : selected_adapter->fullscreen_modes) { - const bool checked = (strvalue.has_value() && strvalue.value() == mode); - options.emplace_back(mode, checked); + const TinyString mode_str = mode.ToString(); + const bool checked = (strvalue.has_value() && strvalue.value() == mode_str); + options.emplace_back(std::string(mode_str.view()), checked); } } diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index c0ba4f6de..87daa434f 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -1145,8 +1145,16 @@ void GPU::UpdateCommandTickEvent() void GPU::ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x, float* display_y) const { + if (!g_gpu_device->HasMainSwapChain()) [[unlikely]] + { + *display_x = 0.0f; + *display_y = 0.0f; + return; + } + GSVector4i display_rc, draw_rc; - CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), true, true, &display_rc, &draw_rc); + CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(), true, + true, &display_rc, &draw_rc); // convert coordinates to active display region, then to full display region const float scaled_display_x = @@ -1644,7 +1652,8 @@ bool GPU::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_sm if (display) { plconfig.layout = GPUPipeline::Layout::SingleTextureAndPushConstants; - plconfig.SetTargetFormats(g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8); + plconfig.SetTargetFormats(g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : + GPUTexture::Format::RGBA8); std::string vs = shadergen.GenerateDisplayVertexShader(); std::string fs; @@ -1839,10 +1848,13 @@ GPUDevice::PresentResult GPU::PresentDisplay() { FlushRender(); + if (!g_gpu_device->HasMainSwapChain()) + return GPUDevice::PresentResult::SkipPresent; + GSVector4i display_rect; GSVector4i draw_rect; - CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), !g_settings.debugging.show_vram, - true, &display_rect, &draw_rect); + CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(), + !g_settings.debugging.show_vram, true, &display_rect, &draw_rect); return RenderDisplay(nullptr, display_rect, draw_rect, !g_settings.debugging.show_vram); } @@ -1887,13 +1899,12 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i } } - const GPUTexture::Format hdformat = target ? target->GetFormat() : g_gpu_device->GetWindowFormat(); - const u32 target_width = target ? target->GetWidth() : g_gpu_device->GetWindowWidth(); - const u32 target_height = target ? target->GetHeight() : g_gpu_device->GetWindowHeight(); - const bool really_postfx = - (postfx && PostProcessing::DisplayChain.IsActive() && !g_gpu_device->GetWindowInfo().IsSurfaceless() && - hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 && - PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height)); + const GPUTexture::Format hdformat = target ? target->GetFormat() : g_gpu_device->GetMainSwapChain()->GetFormat(); + const u32 target_width = target ? target->GetWidth() : g_gpu_device->GetMainSwapChain()->GetWidth(); + const u32 target_height = target ? target->GetHeight() : g_gpu_device->GetMainSwapChain()->GetHeight(); + const bool really_postfx = (postfx && PostProcessing::DisplayChain.IsActive() && g_gpu_device->HasMainSwapChain() && + hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 && + PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height)); const GSVector4i real_draw_rect = g_gpu_device->UsesLowerLeftOrigin() ? GPUDevice::FlipToLowerLeft(draw_rect, target_height) : draw_rect; if (really_postfx) @@ -1904,9 +1915,15 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i else { if (target) + { g_gpu_device->SetRenderTarget(target); - else if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(); pres != GPUDevice::PresentResult::OK) - return pres; + } + else + { + const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain()); + if (pres != GPUDevice::PresentResult::OK) + return pres; + } } if (display_texture) @@ -2559,7 +2576,7 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i displ GPUTexture::Format* out_format) { const GPUTexture::Format hdformat = - g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8; + g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : GPUTexture::Format::RGBA8; auto render_texture = g_gpu_device->FetchAutoRecycleTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget, hdformat); @@ -2605,8 +2622,8 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i displ void GPU::CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect, GSVector4i* draw_rect) const { - *width = g_gpu_device->GetWindowWidth(); - *height = g_gpu_device->GetWindowHeight(); + *width = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetWidth() : 1; + *height = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetHeight() : 1; CalculateDrawRect(*width, *height, true, !g_settings.debugging.show_vram, display_rect, draw_rect); const bool internal_resolution = (mode != DisplayScreenshotMode::ScreenResolution || g_settings.debugging.show_vram); diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index ffcf15474..d72d082f5 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -525,7 +525,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings) // When using very high upscaling, it's possible that we don't have enough VRAM for two sets of buffers. // Purge the pool, and idle the GPU so that all video memory is freed prior to creating the new buffers. g_gpu_device->PurgeTexturePool(); - g_gpu_device->ExecuteAndWaitForGPUIdle(); + g_gpu_device->WaitForGPUIdle(); if (!CreateBuffers()) Panic("Failed to recreate buffers."); @@ -680,7 +680,7 @@ u32 GPU_HW::CalculateResolutionScale() const { // Auto scaling. if (m_crtc_state.display_width == 0 || m_crtc_state.display_height == 0 || m_crtc_state.display_vram_width == 0 || - m_crtc_state.display_vram_height == 0 || m_GPUSTAT.display_disable) + m_crtc_state.display_vram_height == 0 || m_GPUSTAT.display_disable || !g_gpu_device->HasMainSwapChain()) { // When the system is starting and all borders crop is enabled, the registers are zero, and // display_height therefore is also zero. Keep the existing resolution until it updates. @@ -689,8 +689,8 @@ u32 GPU_HW::CalculateResolutionScale() const else { GSVector4i display_rect, draw_rect; - CalculateDrawRect(g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), true, true, &display_rect, - &draw_rect); + CalculateDrawRect(g_gpu_device->GetMainSwapChain()->GetWidth(), g_gpu_device->GetMainSwapChain()->GetHeight(), + true, true, &display_rect, &draw_rect); // We use the draw rect to determine scaling. This way we match the resolution as best we can, regardless of the // anamorphic aspect ratio. diff --git a/src/core/gte.cpp b/src/core/gte.cpp index e43005471..d9cc2b266 100644 --- a/src/core/gte.cpp +++ b/src/core/gte.cpp @@ -243,14 +243,14 @@ void GTE::UpdateAspectRatio() { case DisplayAspectRatio::MatchWindow: { - if (!g_gpu_device) + if (!g_gpu_device || !g_gpu_device->HasMainSwapChain()) { s_config.aspect_ratio = DisplayAspectRatio::R4_3; return; } - num = g_gpu_device->GetWindowWidth(); - denom = g_gpu_device->GetWindowHeight(); + num = g_gpu_device->GetMainSwapChain()->GetWidth(); + denom = g_gpu_device->GetMainSwapChain()->GetHeight(); } break; diff --git a/src/core/host.cpp b/src/core/host.cpp index feed7838f..98846bf36 100644 --- a/src/core/host.cpp +++ b/src/core/host.cpp @@ -274,13 +274,19 @@ std::string Host::GetHTTPUserAgent() return fmt::format("DuckStation for {} ({}) {}", TARGET_OS_STR, CPU_ARCH_STR, g_scm_tag_str); } -bool Host::CreateGPUDevice(RenderAPI api, Error* error) +bool Host::CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error) { DebugAssert(!g_gpu_device); INFO_LOG("Trying to create a {} GPU device...", GPUDevice::RenderAPIToString(api)); g_gpu_device = GPUDevice::CreateDeviceForAPI(api); + std::optional fullscreen_mode; + if (fullscreen && g_gpu_device && g_gpu_device->SupportsExclusiveFullscreen()) + { + fullscreen_mode = + GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", "")); + } std::optional exclusive_fullscreen_control; if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic) { @@ -300,18 +306,30 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error) if (g_settings.gpu_disable_raster_order_views) disabled_features |= GPUDevice::FEATURE_MASK_RASTER_ORDER_VIEWS; + // Don't dump shaders on debug builds for Android, users will complain about storage... +#if !defined(__ANDROID__) || defined(_DEBUG) + const std::string_view shader_dump_directory(EmuFolders::DataRoot); +#else + const std::string_view shader_dump_directory; +#endif + Error create_error; - if (!g_gpu_device || !g_gpu_device->Create( - g_settings.gpu_adapter, - g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache), - SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, System::GetEffectiveVSyncMode(), - System::ShouldAllowPresentThrottle(), exclusive_fullscreen_control, - static_cast(disabled_features), &create_error)) + std::optional wi; + if (!g_gpu_device || + !(wi = Host::AcquireRenderWindow(api, fullscreen, fullscreen_mode.has_value(), &create_error)).has_value() || + !g_gpu_device->Create( + g_settings.gpu_adapter, static_cast(disabled_features), shader_dump_directory, + g_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache), + SHADER_CACHE_VERSION, g_settings.gpu_use_debug_device, wi.value(), System::GetEffectiveVSyncMode(), + System::ShouldAllowPresentThrottle(), fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr, + exclusive_fullscreen_control, &create_error)) { ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription()); if (g_gpu_device) g_gpu_device->Destroy(); g_gpu_device.reset(); + if (wi.has_value()) + Host::ReleaseRenderWindow(); Error::SetStringFmt( error, @@ -327,27 +345,52 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error) Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription()); g_gpu_device->Destroy(); g_gpu_device.reset(); + Host::ReleaseRenderWindow(); return false; } - InputManager::SetDisplayWindowSize(static_cast(g_gpu_device->GetWindowWidth()), - static_cast(g_gpu_device->GetWindowHeight())); + InputManager::SetDisplayWindowSize(ImGuiManager::GetWindowWidth(), ImGuiManager::GetWindowHeight()); return true; } -void Host::UpdateDisplayWindow() +void Host::UpdateDisplayWindow(bool fullscreen) { if (!g_gpu_device) return; - if (!g_gpu_device->UpdateWindow()) + const GPUVSyncMode vsync_mode = + g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetVSyncMode() : GPUVSyncMode::Disabled; + const bool allow_present_throttle = + g_gpu_device->HasMainSwapChain() && g_gpu_device->GetMainSwapChain()->IsPresentThrottleAllowed(); + std::optional fullscreen_mode; + if (fullscreen && g_gpu_device->SupportsExclusiveFullscreen()) { - Host::ReportErrorAsync("Error", "Failed to change window after update. The log may contain more information."); + fullscreen_mode = + GPUDevice::ExclusiveFullscreenMode::Parse(Host::GetTinyStringSettingValue("GPU", "FullscreenMode", "")); + } + std::optional exclusive_fullscreen_control; + if (g_settings.display_exclusive_fullscreen_control != DisplayExclusiveFullscreenControl::Automatic) + { + exclusive_fullscreen_control = + (g_settings.display_exclusive_fullscreen_control == DisplayExclusiveFullscreenControl::Allowed); + } + + g_gpu_device->DestroyMainSwapChain(); + + Error error; + std::optional wi; + if (!(wi = Host::AcquireRenderWindow(g_gpu_device->GetRenderAPI(), fullscreen, fullscreen_mode.has_value(), &error)) + .has_value() || + !g_gpu_device->RecreateMainSwapChain(wi.value(), vsync_mode, allow_present_throttle, + fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr, + exclusive_fullscreen_control, &error)) + { + Host::ReportFatalError("Failed to change window after update", error.GetDescription()); return; } - const float f_width = static_cast(g_gpu_device->GetWindowWidth()); - const float f_height = static_cast(g_gpu_device->GetWindowHeight()); + const float f_width = static_cast(g_gpu_device->GetMainSwapChain()->GetWidth()); + const float f_height = static_cast(g_gpu_device->GetMainSwapChain()->GetHeight()); ImGuiManager::WindowResized(f_width, f_height); InputManager::SetDisplayWindowSize(f_width, f_height); System::HostDisplayResized(); @@ -365,15 +408,21 @@ void Host::UpdateDisplayWindow() void Host::ResizeDisplayWindow(s32 width, s32 height, float scale) { - if (!g_gpu_device) + if (!g_gpu_device || !g_gpu_device->HasMainSwapChain()) return; DEV_LOG("Display window resized to {}x{}", width, height); - g_gpu_device->ResizeWindow(width, height, scale); + Error error; + if (!g_gpu_device->GetMainSwapChain()->ResizeBuffers(width, height, scale, &error)) + { + ERROR_LOG("Failed to resize main swap chain: {}", error.GetDescription()); + UpdateDisplayWindow(Host::IsFullscreen()); + return; + } - const float f_width = static_cast(g_gpu_device->GetWindowWidth()); - const float f_height = static_cast(g_gpu_device->GetWindowHeight()); + const float f_width = static_cast(g_gpu_device->GetMainSwapChain()->GetWidth()); + const float f_height = static_cast(g_gpu_device->GetMainSwapChain()->GetHeight()); ImGuiManager::WindowResized(f_width, f_height); InputManager::SetDisplayWindowSize(f_width, f_height); @@ -404,4 +453,6 @@ void Host::ReleaseGPUDevice() INFO_LOG("Destroying {} GPU device...", GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI())); g_gpu_device->Destroy(); g_gpu_device.reset(); + + Host::ReleaseRenderWindow(); } diff --git a/src/core/host.h b/src/core/host.h index 025e70416..260255143 100644 --- a/src/core/host.h +++ b/src/core/host.h @@ -82,11 +82,25 @@ void DisplayLoadingScreen(const char* message, int progress_min = -1, int progre /// Safely executes a function on the VM thread. void RunOnCPUThread(std::function function, bool block = false); +/// Called when the core is creating a render device. +/// This could also be fullscreen transition. +std::optional AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, + Error* error); + +/// Called when the core is finished with a render window. +void ReleaseRenderWindow(); + +/// Returns true if the hosting application is currently fullscreen. +bool IsFullscreen(); + +/// Alters fullscreen state of hosting application. +void SetFullscreen(bool enabled); + /// Attempts to create the rendering device backend. -bool CreateGPUDevice(RenderAPI api, Error* error); +bool CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error); /// Handles fullscreen transitions and such. -void UpdateDisplayWindow(); +void UpdateDisplayWindow(bool fullscreen); /// Called when the window is resized. void ResizeDisplayWindow(s32 width, s32 height, float scale); diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index 8327969dc..0f5ea6b63 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -84,7 +84,7 @@ static std::tuple GetMinMax(std::span values) void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, int progress_max /*= -1*/, int progress_value /*= -1*/) { - if (!g_gpu_device) + if (!g_gpu_device || !g_gpu_device->HasMainSwapChain()) { INFO_LOG("{}: {}/{}", message, progress_value, progress_max); return; @@ -160,10 +160,11 @@ void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, // TODO: Glass effect or something. - if (g_gpu_device->BeginPresent() == GPUDevice::PresentResult::OK) + GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain(); + if (g_gpu_device->BeginPresent(swap_chain) == GPUDevice::PresentResult::OK) { - g_gpu_device->RenderImGui(); - g_gpu_device->EndPresent(false); + g_gpu_device->RenderImGui(swap_chain); + g_gpu_device->EndPresent(swap_chain, false); } ImGui::NewFrame(); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index c313a4824..9eed3059f 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -158,8 +158,6 @@ void Settings::Load(SettingsInterface& si, SettingsInterface& controller_si) turbo_speed = si.GetFloatValue("Main", "TurboSpeed", 0.0f); sync_to_host_refresh_rate = si.GetBoolValue("Main", "SyncToHostRefreshRate", false); inhibit_screensaver = si.GetBoolValue("Main", "InhibitScreensaver", true); - start_paused = si.GetBoolValue("Main", "StartPaused", false); - start_fullscreen = si.GetBoolValue("Main", "StartFullscreen", false); pause_on_focus_loss = si.GetBoolValue("Main", "PauseOnFocusLoss", false); pause_on_controller_disconnection = si.GetBoolValue("Main", "PauseOnControllerDisconnection", false); save_state_on_exit = si.GetBoolValue("Main", "SaveStateOnExit", true); @@ -505,8 +503,6 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const { si.SetBoolValue("Main", "SyncToHostRefreshRate", sync_to_host_refresh_rate); si.SetBoolValue("Main", "InhibitScreensaver", inhibit_screensaver); - si.SetBoolValue("Main", "StartPaused", start_paused); - si.SetBoolValue("Main", "StartFullscreen", start_fullscreen); si.SetBoolValue("Main", "PauseOnFocusLoss", pause_on_focus_loss); si.SetBoolValue("Main", "PauseOnControllerDisconnection", pause_on_controller_disconnection); si.SetBoolValue("Main", "SaveStateOnExit", save_state_on_exit); @@ -1657,10 +1653,11 @@ float Settings::GetDisplayAspectRatioValue() const { case DisplayAspectRatio::MatchWindow: { - if (!g_gpu_device) + if (!g_gpu_device || !g_gpu_device->HasMainSwapChain()) return s_display_aspect_ratio_values[static_cast(DEFAULT_DISPLAY_ASPECT_RATIO)]; - return static_cast(g_gpu_device->GetWindowWidth()) / static_cast(g_gpu_device->GetWindowHeight()); + return static_cast(g_gpu_device->GetMainSwapChain()->GetWidth()) / + static_cast(g_gpu_device->GetMainSwapChain()->GetHeight()); } case DisplayAspectRatio::Custom: @@ -2110,7 +2107,6 @@ std::string EmuFolders::Bios; std::string EmuFolders::Cache; std::string EmuFolders::Cheats; std::string EmuFolders::Covers; -std::string EmuFolders::Dumps; std::string EmuFolders::GameIcons; std::string EmuFolders::GameSettings; std::string EmuFolders::InputProfiles; @@ -2130,7 +2126,6 @@ void EmuFolders::SetDefaults() Cache = Path::Combine(DataRoot, "cache"); Cheats = Path::Combine(DataRoot, "cheats"); Covers = Path::Combine(DataRoot, "covers"); - Dumps = Path::Combine(DataRoot, "dump"); GameIcons = Path::Combine(DataRoot, "gameicons"); GameSettings = Path::Combine(DataRoot, "gamesettings"); InputProfiles = Path::Combine(DataRoot, "inputprofiles"); @@ -2162,7 +2157,6 @@ void EmuFolders::LoadConfig(SettingsInterface& si) Cache = LoadPathFromSettings(si, DataRoot, "Folders", "Cache", "cache"); Cheats = LoadPathFromSettings(si, DataRoot, "Folders", "Cheats", "cheats"); Covers = LoadPathFromSettings(si, DataRoot, "Folders", "Covers", "covers"); - Dumps = LoadPathFromSettings(si, DataRoot, "Folders", "Dumps", "dump"); GameIcons = LoadPathFromSettings(si, DataRoot, "Folders", "GameIcons", "gameicons"); GameSettings = LoadPathFromSettings(si, DataRoot, "Folders", "GameSettings", "gamesettings"); InputProfiles = LoadPathFromSettings(si, DataRoot, "Folders", "InputProfiles", "inputprofiles"); @@ -2179,7 +2173,6 @@ void EmuFolders::LoadConfig(SettingsInterface& si) DEV_LOG("Cache Directory: {}", Cache); DEV_LOG("Cheats Directory: {}", Cheats); DEV_LOG("Covers Directory: {}", Covers); - DEV_LOG("Dumps Directory: {}", Dumps); DEV_LOG("Game Icons Directory: {}", GameIcons); DEV_LOG("Game Settings Directory: {}", GameSettings); DEV_LOG("Input Profile Directory: {}", InputProfiles); @@ -2201,7 +2194,6 @@ void EmuFolders::Save(SettingsInterface& si) si.SetStringValue("Folders", "Cache", Path::MakeRelative(Cache, DataRoot).c_str()); si.SetStringValue("Folders", "Cheats", Path::MakeRelative(Cheats, DataRoot).c_str()); si.SetStringValue("Folders", "Covers", Path::MakeRelative(Covers, DataRoot).c_str()); - si.SetStringValue("Folders", "Dumps", Path::MakeRelative(Dumps, DataRoot).c_str()); si.SetStringValue("Folders", "GameIcons", Path::MakeRelative(GameIcons, DataRoot).c_str()); si.SetStringValue("Folders", "GameSettings", Path::MakeRelative(GameSettings, DataRoot).c_str()); si.SetStringValue("Folders", "InputProfiles", Path::MakeRelative(InputProfiles, DataRoot).c_str()); @@ -2242,7 +2234,6 @@ bool EmuFolders::EnsureFoldersExist() result = FileSystem::EnsureDirectoryExists(Path::Combine(Cache, "achievement_images").c_str(), false) && result; result = FileSystem::EnsureDirectoryExists(Cheats.c_str(), false) && result; result = FileSystem::EnsureDirectoryExists(Covers.c_str(), false) && result; - result = FileSystem::EnsureDirectoryExists(Dumps.c_str(), false) && result; result = FileSystem::EnsureDirectoryExists(GameIcons.c_str(), false) && result; result = FileSystem::EnsureDirectoryExists(GameSettings.c_str(), false) && result; result = FileSystem::EnsureDirectoryExists(InputProfiles.c_str(), false) && result; diff --git a/src/core/settings.h b/src/core/settings.h index cacd09f15..96a7d4da6 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -78,8 +78,6 @@ struct Settings float turbo_speed = 0.0f; bool sync_to_host_refresh_rate : 1 = false; bool inhibit_screensaver : 1 = true; - bool start_paused : 1 = false; - bool start_fullscreen : 1 = false; bool pause_on_focus_loss : 1 = false; bool pause_on_controller_disconnection : 1 = false; bool save_state_on_exit : 1 = true; @@ -572,7 +570,6 @@ extern std::string Bios; extern std::string Cache; extern std::string Cheats; extern std::string Covers; -extern std::string Dumps; extern std::string GameIcons; extern std::string GameSettings; extern std::string InputProfiles; diff --git a/src/core/system.cpp b/src/core/system.cpp index 13538dc98..9490122a3 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -145,21 +145,26 @@ static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_ static GameHash GetGameHashFromBuffer(std::string_view exe_name, std::span exe_buffer, const IsoReader::ISOPrimaryVolumeDescriptor& iso_pvd, u32 track_1_length); +/// Settings that are looked up on demand. +static bool ShouldStartFullscreen(); +static bool ShouldStartPaused(); + /// Checks for settings changes, std::move() the old settings away for comparing beforehand. static void CheckForSettingsChanges(const Settings& old_settings); static void WarnAboutUnsafeSettings(); static void LogUnsafeSettingsToConsole(const SmallStringBase& messages); -static bool Initialize(bool force_software_renderer, Error* error); +static bool Initialize(bool force_software_renderer, bool fullscreen, Error* error); static bool LoadBIOS(Error* error); static bool SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error* error); static void InternalReset(); static void ClearRunningGame(); static void DestroySystem(); -static bool CreateGPU(GPURenderer renderer, bool is_switching, Error* error); +static bool CreateGPU(GPURenderer renderer, bool is_switching, bool fullscreen, Error* error); static bool RecreateGPU(GPURenderer renderer, bool force_recreate_device = false, bool update_display = true); static void HandleHostGPUDeviceLost(); +static void HandleExclusiveFullscreenLost(); /// Returns true if boot is being fast forwarded. static bool IsFastForwardingBoot(); @@ -1201,10 +1206,11 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool { PostProcessing::Shutdown(); Host::ReleaseGPUDevice(); + Host::ReleaseRenderWindow(); } Error error; - if (!CreateGPU(renderer, true, &error)) + if (!CreateGPU(renderer, true, Host::IsFullscreen(), &error)) { if (!IsStartupCancelled()) Host::ReportErrorAsync("Error", error.GetDescription()); @@ -1265,6 +1271,12 @@ void System::HandleHostGPUDeviceLost() Host::OSD_CRITICAL_ERROR_DURATION); } +void System::HandleExclusiveFullscreenLost() +{ + WARNING_LOG("Lost exclusive fullscreen."); + Host::SetFullscreen(false); +} + void System::LoadSettings(bool display_osd_messages) { std::unique_lock lock = Host::GetSettingsLock(); @@ -1373,6 +1385,9 @@ void System::SetDefaultSettings(SettingsInterface& si) temp.Save(si, false); + si.SetBoolValue("Main", "StartPaused", false); + si.SetBoolValue("Main", "StartFullscreen", false); + #if !defined(_WIN32) && !defined(__ANDROID__) // On Linux, default the console to whether standard input is currently available. si.SetBoolValue("Logging", "LogToConsole", Log::IsConsoleOutputCurrentlyAvailable()); @@ -1793,7 +1808,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) } // Component setup. - if (!Initialize(parameters.force_software_renderer, error)) + if (!Initialize(parameters.force_software_renderer, parameters.override_fullscreen.value_or(ShouldStartFullscreen()), + error)) { s_boot_mode = System::BootMode::None; s_state = State::Shutdown; @@ -1853,7 +1869,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) if (parameters.start_media_capture) StartMediaCapture({}); - if (g_settings.start_paused || parameters.override_start_paused.value_or(false)) + if (ShouldStartPaused() || parameters.override_start_paused.value_or(false)) PauseSystem(true); UpdateSpeedLimiterState(); @@ -1861,7 +1877,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) return true; } -bool System::Initialize(bool force_software_renderer, Error* error) +bool System::Initialize(bool force_software_renderer, bool fullscreen, Error* error) { g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK); s_max_slice_ticks = ScaleTicksToOverclock(MASTER_CLOCK / 10); @@ -1911,7 +1927,7 @@ bool System::Initialize(bool force_software_renderer, Error* error) Bus::Initialize(); CPU::Initialize(); - if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, error)) + if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, fullscreen, error)) { CPU::Shutdown(); Bus::Shutdown(); @@ -1928,10 +1944,7 @@ bool System::Initialize(bool force_software_renderer, Error* error) { g_gpu.reset(); if (!s_keep_gpu_device_on_shutdown) - { Host::ReleaseGPUDevice(); - Host::ReleaseRenderWindow(); - } if (g_settings.gpu_pgxp_enable) CPU::PGXP::Shutdown(); CPU::Shutdown(); @@ -2018,7 +2031,6 @@ void System::DestroySystem() else { Host::ReleaseGPUDevice(); - Host::ReleaseRenderWindow(); } s_bios_hash = {}; @@ -2189,12 +2201,13 @@ void System::FrameDone() const bool is_unique_frame = (s_last_presented_internal_frame_number != s_internal_frame_number); s_last_presented_internal_frame_number = s_internal_frame_number; - const bool skip_this_frame = (((s_skip_presenting_duplicate_frames && !is_unique_frame && - s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) || - (!s_optimal_frame_pacing && current_time > s_next_frame_time && - s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) || - g_gpu_device->ShouldSkipPresentingFrame()) && - !s_syncing_to_host_with_vsync && !IsExecutionInterrupted()); + const bool skip_this_frame = + (((s_skip_presenting_duplicate_frames && !is_unique_frame && + s_skipped_frame_count < MAX_SKIPPED_DUPLICATE_FRAME_COUNT) || + (!s_optimal_frame_pacing && current_time > s_next_frame_time && + s_skipped_frame_count < MAX_SKIPPED_TIMEOUT_FRAME_COUNT) || + (g_gpu_device->HasMainSwapChain() && g_gpu_device->GetMainSwapChain()->ShouldSkipPresentingFrame())) && + !s_syncing_to_host_with_vsync && !IsExecutionInterrupted()); if (!skip_this_frame) { s_skipped_frame_count = 0; @@ -2211,7 +2224,7 @@ void System::FrameDone() const bool do_present = PresentDisplay(true, 0); Throttle(current_time); if (do_present) - g_gpu_device->SubmitPresent(); + g_gpu_device->SubmitPresent(g_gpu_device->GetMainSwapChain()); } else { @@ -2373,7 +2386,7 @@ void System::IncrementInternalFrameNumber() s_internal_frame_number++; } -bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error) +bool System::CreateGPU(GPURenderer renderer, bool is_switching, bool fullscreen, Error* error) { const RenderAPI api = Settings::GetRenderAPIForRenderer(renderer); @@ -2388,7 +2401,7 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error) } Host::ReleaseGPUDevice(); - if (!Host::CreateGPUDevice(api, error)) + if (!Host::CreateGPUDevice(api, fullscreen, error)) { Host::ReleaseRenderWindow(); return false; @@ -3436,9 +3449,9 @@ void System::UpdateSpeedLimiterState() s_syncing_to_host = false; s_syncing_to_host_with_vsync = false; - if (g_settings.sync_to_host_refresh_rate) + if (g_settings.sync_to_host_refresh_rate && g_gpu_device->HasMainSwapChain()) { - const float host_refresh_rate = g_gpu_device->GetWindowInfo().surface_refresh_rate; + const float host_refresh_rate = g_gpu_device->GetMainSwapChain()->GetWindowInfo().surface_refresh_rate; if (host_refresh_rate > 0.0f) { const float ratio = host_refresh_rate / System::GetThrottleFrequency(); @@ -3499,14 +3512,23 @@ void System::UpdateDisplayVSync() // Avoid flipping vsync on and off by manually throttling when vsync is on. const GPUVSyncMode vsync_mode = GetEffectiveVSyncMode(); const bool allow_present_throttle = ShouldAllowPresentThrottle(); - if (g_gpu_device->GetVSyncMode() == vsync_mode && g_gpu_device->IsPresentThrottleAllowed() == allow_present_throttle) + if (!g_gpu_device->HasMainSwapChain() || + (g_gpu_device->GetMainSwapChain()->GetVSyncMode() == vsync_mode && + g_gpu_device->GetMainSwapChain()->IsPresentThrottleAllowed() == allow_present_throttle)) + { return; + } VERBOSE_LOG("VSync: {}{}{}", vsync_modes[static_cast(vsync_mode)], s_syncing_to_host_with_vsync ? " (for throttling)" : "", allow_present_throttle ? " (present throttle allowed)" : ""); - g_gpu_device->SetVSyncMode(vsync_mode, allow_present_throttle); + Error error; + if (!g_gpu_device->GetMainSwapChain()->SetVSyncMode(vsync_mode, allow_present_throttle, &error)) + { + ERROR_LOG("Failed to update vsync mode to {}: {}", vsync_modes[static_cast(vsync_mode)], + error.GetDescription()); + } } GPUVSyncMode System::GetEffectiveVSyncMode() @@ -4230,6 +4252,16 @@ bool System::SwitchMediaSubImage(u32 index) return true; } +bool System::ShouldStartFullscreen() +{ + return Host::GetBoolSettingValue("Main", "StartFullscreen", false); +} + +bool System::ShouldStartPaused() +{ + return Host::GetBoolSettingValue("Main", "StartPaused", false); +} + void System::CheckForSettingsChanges(const Settings& old_settings) { if (IsValid() && @@ -5193,7 +5225,7 @@ bool System::StartMediaCapture(std::string path, bool capture_video, bool captur u32 capture_height = Host::GetUIntSettingValue("MediaCapture", "VideoHeight", Settings::DEFAULT_MEDIA_CAPTURE_VIDEO_HEIGHT); const GPUTexture::Format capture_format = - g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8; + g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : GPUTexture::Format::RGBA8; const float fps = System::GetThrottleFrequency(); if (capture_video) { @@ -5640,11 +5672,14 @@ bool System::PresentDisplay(bool explicit_present, u64 present_time) ImGuiManager::RenderOverlayWindows(); ImGuiManager::RenderDebugWindows(); - const GPUDevice::PresentResult pres = g_gpu ? g_gpu->PresentDisplay() : g_gpu_device->BeginPresent(); + const GPUDevice::PresentResult pres = + g_gpu_device->HasMainSwapChain() ? + (g_gpu ? g_gpu->PresentDisplay() : g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain())) : + GPUDevice::PresentResult::SkipPresent; if (pres == GPUDevice::PresentResult::OK) { - g_gpu_device->RenderImGui(); - g_gpu_device->EndPresent(explicit_present, present_time); + g_gpu_device->RenderImGui(g_gpu_device->GetMainSwapChain()); + g_gpu_device->EndPresent(g_gpu_device->GetMainSwapChain(), explicit_present, present_time); if (g_gpu_device->IsGPUTimingEnabled()) { @@ -5656,9 +5691,13 @@ bool System::PresentDisplay(bool explicit_present, u64 present_time) { if (pres == GPUDevice::PresentResult::DeviceLost) [[unlikely]] HandleHostGPUDeviceLost(); + else if (pres == GPUDevice::PresentResult::ExclusiveFullscreenLost) + HandleExclusiveFullscreenLost(); + else + g_gpu_device->FlushCommands(); // Still need to kick ImGui or it gets cranky. - ImGui::Render(); + ImGui::EndFrame(); } ImGuiManager::NewFrame(); diff --git a/src/duckstation-qt/displaywidget.cpp b/src/duckstation-qt/displaywidget.cpp index a4bd46bd8..6ffbe8c9e 100644 --- a/src/duckstation-qt/displaywidget.cpp +++ b/src/duckstation-qt/displaywidget.cpp @@ -58,9 +58,9 @@ int DisplayWidget::scaledWindowHeight() const static_cast(std::ceil(static_cast(height()) * QtUtils::GetDevicePixelRatioForWidget(this))), 1); } -std::optional DisplayWidget::getWindowInfo() +std::optional DisplayWidget::getWindowInfo(Error* error) { - std::optional ret(QtUtils::GetWindowInfoForWidget(this)); + std::optional ret(QtUtils::GetWindowInfoForWidget(this, error)); if (ret.has_value()) { m_last_window_width = ret->surface_width; diff --git a/src/duckstation-qt/displaywidget.h b/src/duckstation-qt/displaywidget.h index 62635df79..6efc141cc 100644 --- a/src/duckstation-qt/displaywidget.h +++ b/src/duckstation-qt/displaywidget.h @@ -11,6 +11,8 @@ #include #include +class Error; + class QCloseEvent; class DisplayWidget final : public QWidget @@ -26,7 +28,7 @@ public: int scaledWindowWidth() const; int scaledWindowHeight() const; - std::optional getWindowInfo(); + std::optional getWindowInfo(Error* error); void updateRelativeMode(bool enabled); void updateCursor(bool hidden); diff --git a/src/duckstation-qt/graphicssettingswidget.cpp b/src/duckstation-qt/graphicssettingswidget.cpp index a18900491..ea4b9ea27 100644 --- a/src/duckstation-qt/graphicssettingswidget.cpp +++ b/src/duckstation-qt/graphicssettingswidget.cpp @@ -852,9 +852,9 @@ void GraphicsSettingsWidget::populateGPUAdaptersAndResolutions(RenderAPI render_ m_ui.fullscreenMode->addItem(tr("Borderless Fullscreen"), QVariant(QString())); if (current_adapter) { - for (const std::string& mode_name : current_adapter->fullscreen_modes) + for (const GPUDevice::ExclusiveFullscreenMode& mode : current_adapter->fullscreen_modes) { - const QString qmodename = QString::fromStdString(mode_name); + const QString qmodename = QtUtils::StringViewToQString(mode.ToString()); m_ui.fullscreenMode->addItem(qmodename, QVariant(qmodename)); } } diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index a59bb0c68..7e98c9011 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -83,6 +83,7 @@ static bool s_use_central_widget = false; // UI thread VM validity. static bool s_system_valid = false; static bool s_system_paused = false; +static std::atomic_uint32_t s_system_locked{false}; static QString s_current_game_title; static QString s_current_game_serial; static QString s_current_game_path; @@ -220,30 +221,25 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr #endif -std::optional MainWindow::acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, - bool surfaceless, bool use_main_window_pos) +std::optional MainWindow::acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless, + bool use_main_window_pos, Error* error) { - DEV_LOG("acquireRenderWindow() recreate={} fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}", - recreate_window ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false", - surfaceless ? "true" : "false", use_main_window_pos ? "true" : "false"); + DEV_LOG("acquireRenderWindow() fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}", + fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false", + use_main_window_pos ? "true" : "false"); QWidget* container = m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget); const bool is_fullscreen = isRenderingFullscreen(); const bool is_rendering_to_main = isRenderingToMain(); const bool changing_surfaceless = (!m_display_widget != surfaceless); - if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && - !changing_surfaceless) - { - return m_display_widget ? m_display_widget->getWindowInfo() : WindowInfo(); - } // Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off. // .. except on Wayland, where everything tends to break if you don't recreate. const bool has_container = (m_display_container != nullptr); const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main); - if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && - has_container == needs_container && !needs_container && !changing_surfaceless) + if (m_display_created && !is_rendering_to_main && !render_to_main && has_container == needs_container && + !needs_container && !changing_surfaceless) { DEV_LOG("Toggling to {} without recreating surface", (fullscreen ? "fullscreen" : "windowed")); @@ -257,12 +253,11 @@ std::optional MainWindow::acquireRenderWindow(bool recreate_window, } else { + container->showNormal(); if (use_main_window_pos) container->setGeometry(geometry()); else restoreDisplayWindowGeometryFromConfig(); - - container->showNormal(); } updateDisplayWidgetCursor(); @@ -270,7 +265,7 @@ std::optional MainWindow::acquireRenderWindow(bool recreate_window, updateWindowState(); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - return m_display_widget->getWindowInfo(); + return m_display_widget->getWindowInfo(error); } destroyDisplayWidget(surfaceless); @@ -282,7 +277,7 @@ std::optional MainWindow::acquireRenderWindow(bool recreate_window, createDisplayWidget(fullscreen, render_to_main, use_main_window_pos); - std::optional wi = m_display_widget->getWindowInfo(); + std::optional wi = m_display_widget->getWindowInfo(error); if (!wi.has_value()) { QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget")); @@ -2852,11 +2847,13 @@ MainWindow::SystemLock MainWindow::pauseAndLockSystem() MainWindow::SystemLock::SystemLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen) : m_dialog_parent(dialog_parent), m_was_paused(was_paused), m_was_fullscreen(was_fullscreen) { + s_system_locked.fetch_add(1, std::memory_order_release); } MainWindow::SystemLock::SystemLock(SystemLock&& lock) : m_dialog_parent(lock.m_dialog_parent), m_was_paused(lock.m_was_paused), m_was_fullscreen(lock.m_was_fullscreen) { + s_system_locked.fetch_add(1, std::memory_order_release); lock.m_dialog_parent = nullptr; lock.m_was_paused = true; lock.m_was_fullscreen = false; @@ -2864,6 +2861,8 @@ MainWindow::SystemLock::SystemLock(SystemLock&& lock) MainWindow::SystemLock::~SystemLock() { + DebugAssert(s_system_locked.load(std::memory_order_relaxed) > 0); + s_system_locked.fetch_sub(1, std::memory_order_release); if (m_was_fullscreen) g_emu_thread->setFullscreen(true, true); if (!m_was_paused) @@ -2874,4 +2873,9 @@ void MainWindow::SystemLock::cancelResume() { m_was_paused = true; m_was_fullscreen = false; -} \ No newline at end of file +} + +bool QtHost::IsSystemLocked() +{ + return (s_system_locked.load(std::memory_order_acquire) > 0); +} diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 099b22cc8..20861c114 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -126,8 +126,8 @@ private Q_SLOTS: bool confirmMessage(const QString& title, const QString& message); void onStatusMessage(const QString& message); - std::optional acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, - bool surfaceless, bool use_main_window_pos); + std::optional acquireRenderWindow(bool fullscreen, bool render_to_main, bool surfaceless, + bool use_main_window_pos, Error* error); void displayResizeRequested(qint32 width, qint32 height); void releaseRenderWindow(); void focusDisplayWidget(); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 702f6a244..77c07e3b2 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -625,13 +625,6 @@ void EmuThread::loadSettings(SettingsInterface& si) // } -void EmuThread::setInitialState(std::optional override_fullscreen) -{ - m_is_fullscreen = override_fullscreen.value_or(Host::GetBaseBoolSettingValue("Main", "StartFullscreen", false)); - m_is_rendering_to_main = shouldRenderToMain(); - m_is_surfaceless = false; -} - void EmuThread::checkForSettingsChanges(const Settings& old_settings) { if (g_main_window) @@ -642,12 +635,16 @@ void EmuThread::checkForSettingsChanges(const Settings& old_settings) updatePerformanceCounters(); } - const bool render_to_main = shouldRenderToMain(); - if (m_is_rendering_to_main != render_to_main) + // don't mess with fullscreen while locked + if (!QtHost::IsSystemLocked()) { - m_is_rendering_to_main = render_to_main; - if (g_gpu_device) - g_gpu_device->UpdateWindow(); + const bool render_to_main = shouldRenderToMain(); + if (m_is_rendering_to_main != render_to_main && !m_is_fullscreen) + { + m_is_rendering_to_main = render_to_main; + if (g_gpu_device) + Host::UpdateDisplayWindow(m_is_fullscreen); + } } } @@ -780,16 +777,15 @@ void EmuThread::startFullscreenUI() // we want settings loaded so we choose the correct renderer // this also sorts out input sources. System::LoadSettings(false); - setInitialState(s_start_fullscreen_ui_fullscreen ? std::optional(true) : std::optional()); + m_is_rendering_to_main = shouldRenderToMain(); m_run_fullscreen_ui = true; Error error; - if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer), &error) || + if (!Host::CreateGPUDevice(Settings::GetRenderAPIForRenderer(g_settings.gpu_renderer), + s_start_fullscreen_ui_fullscreen, &error) || !FullscreenUI::Initialize()) { Host::ReportErrorAsync("Error", error.GetDescription()); - Host::ReleaseGPUDevice(); - Host::ReleaseRenderWindow(); m_run_fullscreen_ui = false; return; } @@ -825,7 +821,6 @@ void EmuThread::stopFullscreenUI() return; Host::ReleaseGPUDevice(); - Host::ReleaseRenderWindow(); } void EmuThread::bootSystem(std::shared_ptr params) @@ -841,7 +836,7 @@ void EmuThread::bootSystem(std::shared_ptr params) if (System::IsValidOrInitializing()) return; - setInitialState(params->override_fullscreen); + m_is_rendering_to_main = shouldRenderToMain(); Error error; if (!System::BootSystem(std::move(*params), &error)) @@ -974,7 +969,7 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main) m_is_fullscreen = fullscreen; m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain(); - Host::UpdateDisplayWindow(); + Host::UpdateDisplayWindow(fullscreen); } bool Host::IsFullscreen() @@ -984,6 +979,10 @@ bool Host::IsFullscreen() void Host::SetFullscreen(bool enabled) { + // don't mess with fullscreen while locked + if (QtHost::IsSystemLocked()) + return; + g_emu_thread->setFullscreen(enabled, true); } @@ -999,7 +998,7 @@ void EmuThread::setSurfaceless(bool surfaceless) return; m_is_surfaceless = surfaceless; - Host::UpdateDisplayWindow(); + Host::UpdateDisplayWindow(false); } void EmuThread::requestDisplaySize(float scale) @@ -1016,20 +1015,18 @@ void EmuThread::requestDisplaySize(float scale) System::RequestDisplaySize(scale); } -std::optional EmuThread::acquireRenderWindow(bool recreate_window) +std::optional EmuThread::acquireRenderWindow(bool fullscreen, bool exclusive_fullscreen, Error* error) { DebugAssert(g_gpu_device); - u32 fs_width, fs_height; - float fs_refresh_rate; - m_is_exclusive_fullscreen = (m_is_fullscreen && g_gpu_device->SupportsExclusiveFullscreen() && - GPUDevice::GetRequestedExclusiveFullscreenMode(&fs_width, &fs_height, &fs_refresh_rate)); - const bool window_fullscreen = m_is_fullscreen && !m_is_exclusive_fullscreen; - const bool render_to_main = !m_is_exclusive_fullscreen && !window_fullscreen && m_is_rendering_to_main; + m_is_fullscreen = fullscreen; + + const bool window_fullscreen = m_is_fullscreen && !exclusive_fullscreen; + const bool render_to_main = !fullscreen && m_is_rendering_to_main; const bool use_main_window_pos = shouldRenderToMain(); - return emit onAcquireRenderWindowRequested(recreate_window, window_fullscreen, render_to_main, m_is_surfaceless, - use_main_window_pos); + return emit onAcquireRenderWindowRequested(window_fullscreen, render_to_main, m_is_surfaceless, use_main_window_pos, + error); } void EmuThread::releaseRenderWindow() @@ -1811,11 +1808,11 @@ void EmuThread::run() m_event_loop->processEvents(QEventLoop::AllEvents); System::Internal::IdlePollUpdate(); - if (g_gpu_device) + if (g_gpu_device && g_gpu_device->HasMainSwapChain()) { System::PresentDisplay(false, 0); - if (!g_gpu_device->IsVSyncModeBlocking()) - g_gpu_device->ThrottlePresentation(); + if (!g_gpu_device->GetMainSwapChain()->IsVSyncModeBlocking()) + g_gpu_device->GetMainSwapChain()->ThrottlePresentation(); } } } @@ -2005,9 +2002,10 @@ void Host::CommitBaseSettingChanges() QtHost::QueueSettingsSave(); } -std::optional Host::AcquireRenderWindow(bool recreate_window) +std::optional Host::AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen, + Error* error) { - return g_emu_thread->acquireRenderWindow(recreate_window); + return g_emu_thread->acquireRenderWindow(fullscreen, exclusive_fullscreen, error); } void Host::ReleaseRenderWindow() diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 6a6c7ceea..a44e8de65 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -98,7 +98,7 @@ public: ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; } ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; } - std::optional acquireRenderWindow(bool recreate_window); + std::optional acquireRenderWindow(bool fullscreen, bool exclusive_fullscreen, Error* error); void connectDisplaySignals(DisplayWidget* widget); void releaseRenderWindow(); @@ -135,8 +135,8 @@ Q_SIGNALS: void systemPaused(); void systemResumed(); void gameListRefreshed(); - std::optional onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main, - bool surfaceless, bool use_main_window_pos); + std::optional onAcquireRenderWindowRequested(bool fullscreen, bool render_to_main, bool surfaceless, + bool use_main_window_pos, Error* error); void onResizeRenderWindowRequested(qint32 width, qint32 height); void onReleaseRenderWindowRequested(); void focusDisplayWidgetRequested(); @@ -220,7 +220,6 @@ private: void createBackgroundControllerPollTimer(); void destroyBackgroundControllerPollTimer(); - void setInitialState(std::optional override_fullscreen); void confirmActionIfMemoryCardBusy(const QString& action, bool cancel_resume_on_accept, std::function callback) const; @@ -233,8 +232,6 @@ private: bool m_run_fullscreen_ui = false; bool m_is_rendering_to_main = false; bool m_is_fullscreen = false; - bool m_is_exclusive_fullscreen = false; - bool m_lost_exclusive_fullscreen = false; bool m_is_surfaceless = false; bool m_save_state_on_shutdown = false; @@ -320,6 +317,9 @@ bool ShouldShowDebugOptions(); bool IsSystemValid(); bool IsSystemPaused(); +/// Returns true if any lock is in place. +bool IsSystemLocked(); + /// Accessors for game information. const QString& GetCurrentGameTitle(); const QString& GetCurrentGameSerial(); diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp index 9bf59f140..82a133953 100644 --- a/src/duckstation-qt/qtutils.cpp +++ b/src/duckstation-qt/qtutils.cpp @@ -7,6 +7,7 @@ #include "core/game_list.h" #include "core/system.h" +#include "common/error.h" #include "common/log.h" #include @@ -316,7 +317,7 @@ qreal QtUtils::GetDevicePixelRatioForWidget(const QWidget* widget) return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast(1); } -std::optional QtUtils::GetWindowInfoForWidget(QWidget* widget) +std::optional QtUtils::GetWindowInfoForWidget(QWidget* widget, Error* error) { WindowInfo wi; @@ -344,14 +345,14 @@ std::optional QtUtils::GetWindowInfoForWidget(QWidget* widget) } else { - qCritical() << "Unknown PNI platform " << platform_name; + Error::SetStringFmt(error, "Unknown PNI platform {}", platform_name.toStdString()); return std::nullopt; } #endif const qreal dpr = GetDevicePixelRatioForWidget(widget); - wi.surface_width = static_cast(static_cast(widget->width()) * dpr); - wi.surface_height = static_cast(static_cast(widget->height()) * dpr); + wi.surface_width = static_cast(static_cast(widget->width()) * dpr); + wi.surface_height = static_cast(static_cast(widget->height()) * dpr); wi.surface_scale = static_cast(dpr); // Query refresh rate, we need it for sync. diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h index 310f69d28..a16b80fd5 100644 --- a/src/duckstation-qt/qtutils.h +++ b/src/duckstation-qt/qtutils.h @@ -19,7 +19,7 @@ #include #include -class ByteStream; +class Error; class QComboBox; class QFrame; @@ -116,7 +116,7 @@ QIcon GetIconForCompatibility(GameDatabase::CompatibilityRating rating); qreal GetDevicePixelRatioForWidget(const QWidget* widget); /// Returns the common window info structure for a Qt widget. -std::optional GetWindowInfoForWidget(QWidget* widget); +std::optional GetWindowInfoForWidget(QWidget* widget, Error* error = nullptr); /// Saves a window's geometry to configuration. Returns false if the configuration was changed. bool SaveWindowGeometry(std::string_view window_name, QWidget* widget, bool auto_commit_changes = true); diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 38e1e9a19..bf70eb87a 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -110,7 +110,6 @@ if(ENABLE_OPENGL) opengl_context_wgl.cpp opengl_context_wgl.h ) - target_link_libraries(util PRIVATE "opengl32.lib") endif() if(LINUX OR BSD OR ANDROID) @@ -183,6 +182,9 @@ if(NOT ANDROID) sdl_input_source.cpp sdl_input_source.h ) + target_compile_definitions(util PUBLIC + ENABLE_SDL + ) target_link_libraries(util PUBLIC cubeb SDL2::SDL2 diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp index 70de26c04..35eabbf76 100644 --- a/src/util/d3d11_device.cpp +++ b/src/util/d3d11_device.cpp @@ -22,7 +22,7 @@ #include #include -LOG_CHANNEL(D3D11Device); +LOG_CHANNEL(GPUDevice); // We need to synchronize instance creation because of adapter enumeration from the UI thread. static std::mutex s_instance_mutex; @@ -45,7 +45,10 @@ void SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name) #endif } -D3D11Device::D3D11Device() = default; +D3D11Device::D3D11Device() +{ + m_render_api = RenderAPI::D3D11; +} D3D11Device::~D3D11Device() { @@ -53,13 +56,11 @@ D3D11Device::~D3D11Device() Assert(!m_device); } -bool D3D11Device::HasSurface() const -{ - return static_cast(m_swap_chain); -} - -bool D3D11Device::CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) +bool D3D11Device::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, + const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) { std::unique_lock lock(s_instance_mutex); @@ -125,17 +126,14 @@ bool D3D11Device::CreateDevice(std::string_view adapter, std::optional exc INFO_LOG("Max device feature level: {}", D3DCommon::GetFeatureLevelString(D3DCommon::GetRenderAPIVersionForFeatureLevel(m_max_feature_level))); - BOOL allow_tearing_supported = false; - hr = m_dxgi_factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, - sizeof(allow_tearing_supported)); - m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE); - SetFeatures(disabled_features); - if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()) + if (!wi.IsSurfaceless()) { - Error::SetStringView(error, "Failed to create swap chain"); - return false; + m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode, + exclusive_fullscreen_control, error); + if (!m_main_swap_chain) + return false; } if (!CreateBuffers()) @@ -152,6 +150,7 @@ void D3D11Device::DestroyDevice() std::unique_lock lock(s_instance_mutex); DestroyBuffers(); + m_main_swap_chain.reset(); m_context.Reset(); m_device.Reset(); } @@ -160,7 +159,6 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features) { const D3D_FEATURE_LEVEL feature_level = m_device->GetFeatureLevel(); - m_render_api = RenderAPI::D3D11; m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level); m_max_texture_size = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; m_max_multisamples = 1; @@ -202,96 +200,107 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features) } } -u32 D3D11Device::GetSwapChainBufferCount() const +D3D11SwapChain::D3D11SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode) + : GPUSwapChain(wi, vsync_mode, allow_present_throttle) { - // With vsync off, we only need two buffers. Same for blocking vsync. - // With triple buffering, we need three. - return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2; + if (fullscreen_mode) + InitializeExclusiveFullscreenMode(fullscreen_mode); } -bool D3D11Device::CreateSwapChain() +D3D11SwapChain::~D3D11SwapChain() { - if (m_window_info.type != WindowInfo::Type::Win32) - return false; + m_swap_chain_rtv.Reset(); + DestroySwapChain(); +} - const DXGI_FORMAT dxgi_format = D3DCommon::GetFormatMapping(s_swap_chain_format).resource_format; +bool D3D11SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode) +{ + const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format); const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle); RECT client_rc{}; GetClientRect(window_hwnd, &client_rc); - DXGI_MODE_DESC fullscreen_mode = {}; - ComPtr fullscreen_output; - if (Host::IsFullscreen()) - { - u32 fullscreen_width, fullscreen_height; - float fullscreen_refresh_rate; - m_is_exclusive_fullscreen = - GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) && - D3DCommon::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.Get(), client_rc, fullscreen_width, - fullscreen_height, fullscreen_refresh_rate, dxgi_format, - &fullscreen_mode, fullscreen_output.GetAddressOf()); + m_fullscreen_mode = D3DCommon::GetRequestedExclusiveFullscreenModeDesc( + D3D11Device::GetDXGIFactory(), client_rc, mode, fm.resource_format, m_fullscreen_output.GetAddressOf()); + return m_fullscreen_mode.has_value(); +} - // Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen. - if (m_vsync_mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen) - { - WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen."); - m_vsync_mode = GPUVSyncMode::FIFO; - } - } - else +u32 D3D11SwapChain::GetNewBufferCount(GPUVSyncMode vsync_mode) +{ + // With vsync off, we only need two buffers. Same for blocking vsync. + // With triple buffering, we need three. + return (vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2; +} + +bool D3D11SwapChain::CreateSwapChain(Error* error) +{ + const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format); + + const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle); + RECT client_rc{}; + GetClientRect(window_hwnd, &client_rc); + + // Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen. + if (IsExclusiveFullscreen() && m_vsync_mode == GPUVSyncMode::Mailbox) { - m_is_exclusive_fullscreen = false; + WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen."); + m_vsync_mode = GPUVSyncMode::FIFO; } m_using_flip_model_swap_chain = - !Host::GetBoolSettingValue("Display", "UseBlitSwapChain", false) || m_is_exclusive_fullscreen; + !Host::GetBoolSettingValue("Display", "UseBlitSwapChain", false) || IsExclusiveFullscreen(); + + IDXGIFactory5* const dxgi_factory = D3D11Device::GetDXGIFactory(); + ID3D11Device1* const d3d_device = D3D11Device::GetD3DDevice(); DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {}; swap_chain_desc.Width = static_cast(client_rc.right - client_rc.left); swap_chain_desc.Height = static_cast(client_rc.bottom - client_rc.top); - swap_chain_desc.Format = dxgi_format; + swap_chain_desc.Format = fm.resource_format; swap_chain_desc.SampleDesc.Count = 1; - swap_chain_desc.BufferCount = GetSwapChainBufferCount(); + swap_chain_desc.BufferCount = GetNewBufferCount(m_vsync_mode); swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD; - m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen); - if (m_using_allow_tearing) - swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; - HRESULT hr = S_OK; - if (m_is_exclusive_fullscreen) + if (IsExclusiveFullscreen()) { DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc; DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {}; fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; - fs_sd_desc.Width = fullscreen_mode.Width; - fs_sd_desc.Height = fullscreen_mode.Height; - fs_desc.RefreshRate = fullscreen_mode.RefreshRate; - fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering; - fs_desc.Scaling = fullscreen_mode.Scaling; + fs_sd_desc.Width = m_fullscreen_mode->Width; + fs_sd_desc.Height = m_fullscreen_mode->Height; + fs_desc.RefreshRate = m_fullscreen_mode->RefreshRate; + fs_desc.ScanlineOrdering = m_fullscreen_mode->ScanlineOrdering; + fs_desc.Scaling = m_fullscreen_mode->Scaling; fs_desc.Windowed = FALSE; VERBOSE_LOG("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height); - hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &fs_sd_desc, &fs_desc, - fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf()); + hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &fs_sd_desc, &fs_desc, m_fullscreen_output.Get(), + m_swap_chain.ReleaseAndGetAddressOf()); if (FAILED(hr)) { WARNING_LOG("Failed to create fullscreen swap chain, trying windowed."); - m_is_exclusive_fullscreen = false; - m_using_allow_tearing = m_allow_tearing_supported && m_using_flip_model_swap_chain; + m_fullscreen_output.Reset(); + m_fullscreen_mode.reset(); + m_using_allow_tearing = (m_using_flip_model_swap_chain && D3DCommon::SupportsAllowTearing(dxgi_factory)); } } - if (!m_is_exclusive_fullscreen) + if (!IsExclusiveFullscreen()) { VERBOSE_LOG("Creating a {}x{} {} windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height, m_using_flip_model_swap_chain ? "flip-discard" : "discard"); - hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, - m_swap_chain.ReleaseAndGetAddressOf()); + m_using_allow_tearing = (m_using_flip_model_swap_chain && !IsExclusiveFullscreen() && + D3DCommon::SupportsAllowTearing(D3D11Device::GetDXGIFactory())); + if (m_using_allow_tearing) + swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &swap_chain_desc, nullptr, nullptr, + m_swap_chain.ReleaseAndGetAddressOf()); } if (FAILED(hr) && m_using_flip_model_swap_chain) @@ -302,11 +311,11 @@ bool D3D11Device::CreateSwapChain() m_using_flip_model_swap_chain = false; m_using_allow_tearing = false; - hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, - m_swap_chain.ReleaseAndGetAddressOf()); + hr = dxgi_factory->CreateSwapChainForHwnd(d3d_device, window_hwnd, &swap_chain_desc, nullptr, nullptr, + m_swap_chain.ReleaseAndGetAddressOf()); if (FAILED(hr)) [[unlikely]] { - ERROR_LOG("CreateSwapChainForHwnd failed: 0x{:08X}", static_cast(hr)); + Error::SetHResult(error, "CreateSwapChainForHwnd() failed: ", hr); return false; } } @@ -319,25 +328,29 @@ bool D3D11Device::CreateSwapChain() WARNING_LOG("MakeWindowAssociation() to disable ALT+ENTER failed"); } - if (!CreateSwapChainRTV()) - { - DestroySwapChain(); - return false; - } - - // Render a frame as soon as possible to clear out whatever was previously being displayed. - m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), s_clear_color.data()); - m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0); return true; } -bool D3D11Device::CreateSwapChainRTV() +void D3D11SwapChain::DestroySwapChain() +{ + if (!m_swap_chain) + return; + + // switch out of fullscreen before destroying + BOOL is_fullscreen; + if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen) + m_swap_chain->SetFullscreenState(FALSE, nullptr); + + m_swap_chain.Reset(); +} + +bool D3D11SwapChain::CreateRTV(Error* error) { ComPtr backbuffer; HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.GetAddressOf())); if (FAILED(hr)) [[unlikely]] { - ERROR_LOG("GetBuffer for RTV failed: 0x{:08X}", static_cast(hr)); + Error::SetHResult(error, "GetBuffer() failed: ", hr); return false; } @@ -346,16 +359,17 @@ bool D3D11Device::CreateSwapChainRTV() CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(D3D11_RTV_DIMENSION_TEXTURE2D, backbuffer_desc.Format, 0, 0, backbuffer_desc.ArraySize); - hr = m_device->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, m_swap_chain_rtv.ReleaseAndGetAddressOf()); + hr = D3D11Device::GetD3DDevice()->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, + m_swap_chain_rtv.ReleaseAndGetAddressOf()); if (FAILED(hr)) [[unlikely]] { - ERROR_LOG("CreateRenderTargetView for swap chain failed: 0x{:08X}", static_cast(hr)); + Error::SetHResult(error, "CreateRenderTargetView(): ", hr); m_swap_chain_rtv.Reset(); return false; } - m_window_info.surface_width = backbuffer_desc.Width; - m_window_info.surface_height = backbuffer_desc.Height; + m_window_info.surface_width = static_cast(backbuffer_desc.Width); + m_window_info.surface_height = static_cast(backbuffer_desc.Height); m_window_info.surface_format = s_swap_chain_format; VERBOSE_LOG("Swap chain buffer size: {}x{}", m_window_info.surface_width, m_window_info.surface_height); @@ -374,65 +388,77 @@ bool D3D11Device::CreateSwapChainRTV() return true; } -void D3D11Device::DestroySwapChain() +bool D3D11SwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) { - if (!m_swap_chain) - return; - - m_swap_chain_rtv.Reset(); - - // switch out of fullscreen before destroying - BOOL is_fullscreen; - if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen) - m_swap_chain->SetFullscreenState(FALSE, nullptr); - - m_swap_chain.Reset(); - m_is_exclusive_fullscreen = false; -} - -bool D3D11Device::UpdateWindow() -{ - DestroySwapChain(); - - if (!AcquireWindow(false)) - return false; - - if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()) - { - ERROR_LOG("Failed to create swap chain on updated window"); - return false; - } - - return true; -} - -void D3D11Device::DestroySurface() -{ - DestroySwapChain(); -} - -void D3D11Device::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) -{ - if (!m_swap_chain || m_is_exclusive_fullscreen) - return; - - m_window_info.surface_scale = new_window_scale; - - if (m_window_info.surface_width == static_cast(new_window_width) && - m_window_info.surface_height == static_cast(new_window_height)) - { - return; - } + m_window_info.surface_scale = new_scale; + if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height) + return true; m_swap_chain_rtv.Reset(); HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0); if (FAILED(hr)) [[unlikely]] - ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast(hr)); + { + Error::SetHResult(error, "ResizeBuffers() failed: ", hr); + return false; + } - if (!CreateSwapChainRTV()) - Panic("Failed to recreate swap chain RTV after resize"); + return CreateRTV(error); +} + +bool D3D11SwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) +{ + m_allow_present_throttle = allow_present_throttle; + + // Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen. + if (mode == GPUVSyncMode::Mailbox && IsExclusiveFullscreen()) + { + WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen."); + mode = GPUVSyncMode::FIFO; + } + + if (m_vsync_mode == mode) + return true; + + const u32 old_buffer_count = GetNewBufferCount(m_vsync_mode); + const u32 new_buffer_count = GetNewBufferCount(mode); + m_vsync_mode = mode; + if (old_buffer_count == new_buffer_count) + return true; + + // Buffer count change => needs recreation. + m_swap_chain_rtv.Reset(); + DestroySwapChain(); + return CreateSwapChain(error) && CreateRTV(error); +} + +std::unique_ptr D3D11Device::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) +{ + std::unique_ptr ret; + if (wi.type != WindowInfo::Type::Win32) + { + Error::SetStringView(error, "Cannot create a swap chain on non-win32 window."); + return ret; + } + + ret = std::make_unique(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode); + if (ret->CreateSwapChain(error) && ret->CreateRTV(error)) + { + // Render a frame as soon as possible to clear out whatever was previously being displayed. + m_context->ClearRenderTargetView(ret->GetRTV(), s_clear_color.data()); + ret->GetSwapChain()->Present(0, ret->IsUsingAllowTearing() ? DXGI_PRESENT_ALLOW_TEARING : 0); + } + else + { + ret.reset(); + } + + return ret; } bool D3D11Device::SupportsExclusiveFullscreen() const @@ -471,9 +497,16 @@ std::string D3D11Device::GetDriverInfo() const return ret; } -void D3D11Device::ExecuteAndWaitForGPUIdle() +void D3D11Device::FlushCommands() { m_context->Flush(); + TrimTexturePool(); +} + +void D3D11Device::WaitForGPUIdle() +{ + m_context->Flush(); + TrimTexturePool(); } bool D3D11Device::CreateBuffers() @@ -610,63 +643,29 @@ void D3D11Device::InvalidateRenderTarget(GPUTexture* t) T->CommitClear(m_context.Get()); } -void D3D11Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) +GPUDevice::PresentResult D3D11Device::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) { - m_allow_present_throttle = allow_present_throttle; - - // Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen. - if (mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen) - { - WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen."); - mode = GPUVSyncMode::FIFO; - } - - if (m_vsync_mode == mode) - return; - - const u32 old_buffer_count = GetSwapChainBufferCount(); - m_vsync_mode = mode; - if (!m_swap_chain) - return; - - if (GetSwapChainBufferCount() != old_buffer_count) - { - DestroySwapChain(); - if (!CreateSwapChain()) - Panic("Failed to recreate swap chain after vsync change."); - } -} - -GPUDevice::PresentResult D3D11Device::BeginPresent(u32 clear_color) -{ - if (!m_swap_chain) - { - // Note: Really slow on Intel... - m_context->Flush(); - TrimTexturePool(); - return PresentResult::SkipPresent; - } + D3D11SwapChain* const SC = static_cast(swap_chain); // Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode. // This might get called repeatedly if it takes a while to switch back, that's the host's problem. BOOL is_fullscreen; - if (m_is_exclusive_fullscreen && - (FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen)) + if (SC->IsExclusiveFullscreen() && + (FAILED(SC->GetSwapChain()->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen)) { - Host::SetFullscreen(false); TrimTexturePool(); - return PresentResult::SkipPresent; + return PresentResult::ExclusiveFullscreenLost; } // When using vsync, the time here seems to include the time for the buffer to become available. // This blows our our GPU usage number considerably, so read the timestamp before the final blit // in this configuration. It does reduce accuracy a little, but better than seeing 100% all of // the time, when it's more like a couple of percent. - if (m_vsync_mode == GPUVSyncMode::FIFO && m_gpu_timing_enabled) + if (SC == m_main_swap_chain.get() && SC->GetVSyncMode() == GPUVSyncMode::FIFO && m_gpu_timing_enabled) PopTimestampQuery(); - m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), GSVector4::rgba32(clear_color).F32); - m_context->OMSetRenderTargets(1, m_swap_chain_rtv.GetAddressOf(), nullptr); + m_context->ClearRenderTargetView(SC->GetRTV(), GSVector4::rgba32(clear_color).F32); + m_context->OMSetRenderTargets(1, SC->GetRTVArray(), nullptr); s_stats.num_render_passes++; m_num_current_render_targets = 0; m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags; @@ -675,17 +674,19 @@ GPUDevice::PresentResult D3D11Device::BeginPresent(u32 clear_color) return PresentResult::OK; } -void D3D11Device::EndPresent(bool explicit_present, u64 present_time) +void D3D11Device::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) { + D3D11SwapChain* const SC = static_cast(swap_chain); DebugAssert(!explicit_present && present_time == 0); DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); - if (m_vsync_mode != GPUVSyncMode::FIFO && m_gpu_timing_enabled) + if (SC == m_main_swap_chain.get() && SC->GetVSyncMode() != GPUVSyncMode::FIFO && m_gpu_timing_enabled) PopTimestampQuery(); - const UINT sync_interval = static_cast(m_vsync_mode == GPUVSyncMode::FIFO); - const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0; - m_swap_chain->Present(sync_interval, flags); + const UINT sync_interval = static_cast(SC->GetVSyncMode() == GPUVSyncMode::FIFO); + const UINT flags = + (SC->GetVSyncMode() == GPUVSyncMode::Disabled && SC->IsUsingAllowTearing()) ? DXGI_PRESENT_ALLOW_TEARING : 0; + SC->GetSwapChain()->Present(sync_interval, flags); if (m_gpu_timing_enabled) KickTimestampQuery(); @@ -693,7 +694,7 @@ void D3D11Device::EndPresent(bool explicit_present, u64 present_time) TrimTexturePool(); } -void D3D11Device::SubmitPresent() +void D3D11Device::SubmitPresent(GPUSwapChain* swap_chain) { Panic("Not supported by this API."); } @@ -1124,4 +1125,4 @@ void D3D11Device::DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) void D3D11Device::DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) { Panic("Barriers are not supported"); -} \ No newline at end of file +} diff --git a/src/util/d3d11_device.h b/src/util/d3d11_device.h index 1a5632f5d..2fbb2dfa4 100644 --- a/src/util/d3d11_device.h +++ b/src/util/d3d11_device.h @@ -32,21 +32,23 @@ public: ~D3D11Device(); ALWAYS_INLINE static D3D11Device& GetInstance() { return *static_cast(g_gpu_device.get()); } - ALWAYS_INLINE static ID3D11Device* GetD3DDevice() { return GetInstance().m_device.Get(); } + ALWAYS_INLINE static ID3D11Device1* GetD3DDevice() { return GetInstance().m_device.Get(); } ALWAYS_INLINE static ID3D11DeviceContext1* GetD3DContext() { return GetInstance().m_context.Get(); } + ALWAYS_INLINE static IDXGIFactory5* GetDXGIFactory() { return GetInstance().m_dxgi_factory.Get(); } ALWAYS_INLINE static D3D_FEATURE_LEVEL GetMaxFeatureLevel() { return GetInstance().m_max_feature_level; } - bool HasSurface() const override; - - bool UpdateWindow() override; - void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; bool SupportsExclusiveFullscreen() const override; - void DestroySurface() override; std::string GetDriverInfo() const override; - void ExecuteAndWaitForGPUIdle() override; + void FlushCommands() override; + void WaitForGPUIdle() override; + std::unique_ptr CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) override; std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, GPUTexture::Format format, const void* data = nullptr, u32 data_stride = 0) override; @@ -97,21 +99,21 @@ public: void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override; - void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; - bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; - PresentResult BeginPresent(u32 clear_color) override; - void EndPresent(bool explicit_present, u64 present_time) override; - void SubmitPresent() override; + PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override; + void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override; + void SubmitPresent(GPUSwapChain* swap_chain) override; void UnbindPipeline(D3D11Pipeline* pl); void UnbindTexture(D3D11Texture* tex); protected: - bool CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) override; + bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi, + GPUVSyncMode vsync_mode, bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) override; void DestroyDevice() override; private: @@ -136,11 +138,6 @@ private: void SetFeatures(FeatureMask disabled_features); - u32 GetSwapChainBufferCount() const; - bool CreateSwapChain(); - bool CreateSwapChainRTV(); - void DestroySwapChain(); - bool CreateBuffers(); void DestroyBuffers(); @@ -161,8 +158,6 @@ private: ComPtr m_annotation; ComPtr m_dxgi_factory; - ComPtr m_swap_chain; - ComPtr m_swap_chain_rtv; RasterizationStateMap m_rasterization_states; DepthStateMap m_depth_states; @@ -170,10 +165,6 @@ private: InputLayoutMap m_input_layouts; D3D_FEATURE_LEVEL m_max_feature_level = D3D_FEATURE_LEVEL_10_0; - bool m_allow_tearing_supported = false; - bool m_using_flip_model_swap_chain = true; - bool m_using_allow_tearing = false; - bool m_is_exclusive_fullscreen = false; D3D11StreamBuffer m_vertex_buffer; D3D11StreamBuffer m_index_buffer; @@ -207,4 +198,45 @@ private: float m_accumulated_gpu_time = 0.0f; }; +class D3D11SwapChain : public GPUSwapChain +{ +public: + template + using ComPtr = Microsoft::WRL::ComPtr; + + friend D3D11Device; + + D3D11SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode); + ~D3D11SwapChain() override; + + ALWAYS_INLINE IDXGISwapChain1* GetSwapChain() const { return m_swap_chain.Get(); } + ALWAYS_INLINE ID3D11RenderTargetView* GetRTV() const { return m_swap_chain_rtv.Get(); } + ALWAYS_INLINE ID3D11RenderTargetView* const* GetRTVArray() const { return m_swap_chain_rtv.GetAddressOf(); } + ALWAYS_INLINE bool IsUsingAllowTearing() const { return m_using_allow_tearing; } + ALWAYS_INLINE bool IsExclusiveFullscreen() const { return m_fullscreen_mode.has_value(); } + + bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override; + bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override; + +private: + static u32 GetNewBufferCount(GPUVSyncMode vsync_mode); + + bool InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode); + + bool CreateSwapChain(Error* error); + bool CreateRTV(Error* error); + + void DestroySwapChain(); + + ComPtr m_swap_chain; + ComPtr m_swap_chain_rtv; + + ComPtr m_fullscreen_output; + std::optional m_fullscreen_mode; + + bool m_using_flip_model_swap_chain = true; + bool m_using_allow_tearing = false; +}; + void SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name); diff --git a/src/util/d3d11_stream_buffer.cpp b/src/util/d3d11_stream_buffer.cpp index 41b2df4f9..8a0179e36 100644 --- a/src/util/d3d11_stream_buffer.cpp +++ b/src/util/d3d11_stream_buffer.cpp @@ -9,7 +9,7 @@ #include "common/error.h" #include "common/log.h" -LOG_CHANNEL(D3D11Device); +LOG_CHANNEL(GPUDevice); D3D11StreamBuffer::D3D11StreamBuffer() { diff --git a/src/util/d3d11_texture.cpp b/src/util/d3d11_texture.cpp index 9e959b4d0..9615d95cb 100644 --- a/src/util/d3d11_texture.cpp +++ b/src/util/d3d11_texture.cpp @@ -13,7 +13,7 @@ #include -LOG_CHANNEL(D3D11Device); +LOG_CHANNEL(GPUDevice); std::unique_ptr D3D11Device::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, GPUTexture::Format format, diff --git a/src/util/d3d12_device.cpp b/src/util/d3d12_device.cpp index cfaa0949d..cd17a22e4 100644 --- a/src/util/d3d12_device.cpp +++ b/src/util/d3d12_device.cpp @@ -8,8 +8,6 @@ #include "d3d12_texture.h" #include "d3d_common.h" -#include "core/host.h" - #include "common/align.h" #include "common/assert.h" #include "common/bitutils.h" @@ -27,7 +25,7 @@ #include #include -LOG_CHANNEL(D3D12Device); +LOG_CHANNEL(GPUDevice); // Tweakables enum : u32 @@ -69,6 +67,8 @@ static u32 s_debug_scope_depth = 0; D3D12Device::D3D12Device() { + m_render_api = RenderAPI::D3D12; + #ifdef _DEBUG s_debug_scope_depth = 0; #endif @@ -117,8 +117,11 @@ D3D12Device::ComPtr D3D12Device::CreateRootSignature(const return rs; } -bool D3D12Device::CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) +bool D3D12Device::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, + const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) { std::unique_lock lock(s_instance_mutex); @@ -238,8 +241,13 @@ bool D3D12Device::CreateDevice(std::string_view adapter, std::optional exc if (!CreateCommandLists(error) || !CreateDescriptorHeaps(error)) return false; - if (!m_window_info.IsSurfaceless() && !CreateSwapChain(error)) - return false; + if (!wi.IsSurfaceless()) + { + m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode, + exclusive_fullscreen_control, error); + if (!m_main_swap_chain) + return false; + } if (!CreateRootSignatures(error) || !CreateBuffers(error)) return false; @@ -256,7 +264,9 @@ void D3D12Device::DestroyDevice() if (InRenderPass()) EndRenderPass(); - WaitForGPUIdle(); + WaitForAllFences(); + + m_main_swap_chain.reset(); DestroyDeferredObjects(m_current_fence_value); DestroySamplers(); @@ -264,7 +274,6 @@ void D3D12Device::DestroyDevice() DestroyBuffers(); DestroyDescriptorHeaps(); DestroyRootSignatures(); - DestroySwapChain(); DestroyCommandLists(); m_pipeline_library.Reset(); @@ -656,7 +665,7 @@ void D3D12Device::WaitForFence(u64 fence) DestroyDeferredObjects(m_completed_fence_value); } -void D3D12Device::WaitForGPUIdle() +void D3D12Device::WaitForAllFences() { u32 index = (m_current_command_list + 1) % NUM_COMMAND_LISTS; for (u32 i = 0; i < (NUM_COMMAND_LISTS - 1); i++) @@ -666,7 +675,16 @@ void D3D12Device::WaitForGPUIdle() } } -void D3D12Device::ExecuteAndWaitForGPUIdle() +void D3D12Device::FlushCommands() +{ + if (InRenderPass()) + EndRenderPass(); + + SubmitCommandList(false); + TrimTexturePool(); +} + +void D3D12Device::WaitForGPUIdle() { if (InRenderPass()) EndRenderPass(); @@ -790,54 +808,54 @@ void D3D12Device::DestroyDeferredObjects(u64 fence_value) } } -bool D3D12Device::HasSurface() const +D3D12SwapChain::D3D12SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode) + : GPUSwapChain(wi, vsync_mode, allow_present_throttle) { - return static_cast(m_swap_chain); + if (fullscreen_mode) + InitializeExclusiveFullscreenMode(fullscreen_mode); } -u32 D3D12Device::GetSwapChainBufferCount() const +D3D12SwapChain::~D3D12SwapChain() { - // With vsync off, we only need two buffers. Same for blocking vsync. - // With triple buffering, we need three. - return (m_vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2; + DestroyRTVs(); + DestroySwapChain(); } -bool D3D12Device::CreateSwapChain(Error* error) +bool D3D12SwapChain::InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode) { - if (m_window_info.type != WindowInfo::Type::Win32) - { - Error::SetStringView(error, "D3D12 expects a Win32 window."); - return false; - } - const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format); const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle); RECT client_rc{}; GetClientRect(window_hwnd, &client_rc); - DXGI_MODE_DESC fullscreen_mode = {}; - ComPtr fullscreen_output; - if (Host::IsFullscreen()) - { - u32 fullscreen_width, fullscreen_height; - float fullscreen_refresh_rate; - m_is_exclusive_fullscreen = - GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) && - D3DCommon::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.Get(), client_rc, fullscreen_width, - fullscreen_height, fullscreen_refresh_rate, fm.resource_format, - &fullscreen_mode, fullscreen_output.GetAddressOf()); + m_fullscreen_mode = + D3DCommon::GetRequestedExclusiveFullscreenModeDesc(D3D12Device::GetInstance().GetDXGIFactory(), client_rc, mode, + fm.resource_format, m_fullscreen_output.GetAddressOf()); + return m_fullscreen_mode.has_value(); +} - // Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen. - if (m_vsync_mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen) - { - WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen."); - m_vsync_mode = GPUVSyncMode::FIFO; - } - } - else +u32 D3D12SwapChain::GetNewBufferCount(GPUVSyncMode vsync_mode) +{ + // With vsync off, we only need two buffers. Same for blocking vsync. + // With triple buffering, we need three. + return (vsync_mode == GPUVSyncMode::Mailbox) ? 3 : 2; +} + +bool D3D12SwapChain::CreateSwapChain(D3D12Device& dev, Error* error) +{ + const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(s_swap_chain_format); + + const HWND window_hwnd = reinterpret_cast(m_window_info.window_handle); + RECT client_rc{}; + GetClientRect(window_hwnd, &client_rc); + + // Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen. + if (IsExclusiveFullscreen() && m_vsync_mode == GPUVSyncMode::Mailbox) { - m_is_exclusive_fullscreen = false; + WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen."); + m_vsync_mode = GPUVSyncMode::FIFO; } DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {}; @@ -845,45 +863,44 @@ bool D3D12Device::CreateSwapChain(Error* error) swap_chain_desc.Height = static_cast(client_rc.bottom - client_rc.top); swap_chain_desc.Format = fm.resource_format; swap_chain_desc.SampleDesc.Count = 1; - swap_chain_desc.BufferCount = GetSwapChainBufferCount(); + swap_chain_desc.BufferCount = GetNewBufferCount(m_vsync_mode); swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - m_using_allow_tearing = (m_allow_tearing_supported && !m_is_exclusive_fullscreen); - if (m_using_allow_tearing) - swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; - HRESULT hr = S_OK; - if (m_is_exclusive_fullscreen) + if (IsExclusiveFullscreen()) { DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc; DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {}; fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; - fs_sd_desc.Width = fullscreen_mode.Width; - fs_sd_desc.Height = fullscreen_mode.Height; - fs_desc.RefreshRate = fullscreen_mode.RefreshRate; - fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering; - fs_desc.Scaling = fullscreen_mode.Scaling; + fs_sd_desc.Width = m_fullscreen_mode->Width; + fs_sd_desc.Height = m_fullscreen_mode->Height; + fs_desc.RefreshRate = m_fullscreen_mode->RefreshRate; + fs_desc.ScanlineOrdering = m_fullscreen_mode->ScanlineOrdering; + fs_desc.Scaling = m_fullscreen_mode->Scaling; fs_desc.Windowed = FALSE; VERBOSE_LOG("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height); - hr = m_dxgi_factory->CreateSwapChainForHwnd(m_command_queue.Get(), window_hwnd, &fs_sd_desc, &fs_desc, - fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf()); + hr = dev.GetDXGIFactory()->CreateSwapChainForHwnd(dev.GetCommandQueue(), window_hwnd, &fs_sd_desc, &fs_desc, + m_fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf()); if (FAILED(hr)) { WARNING_LOG("Failed to create fullscreen swap chain, trying windowed."); - m_is_exclusive_fullscreen = false; - m_using_allow_tearing = m_allow_tearing_supported; + m_fullscreen_output.Reset(); + m_fullscreen_mode.reset(); } } - if (!m_is_exclusive_fullscreen) + if (!IsExclusiveFullscreen()) { VERBOSE_LOG("Creating a {}x{} windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height); - hr = m_dxgi_factory->CreateSwapChainForHwnd(m_command_queue.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, - m_swap_chain.ReleaseAndGetAddressOf()); + m_using_allow_tearing = D3DCommon::SupportsAllowTearing(dev.GetDXGIFactory()); + if (m_using_allow_tearing) + swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + hr = dev.GetDXGIFactory()->CreateSwapChainForHwnd(dev.GetCommandQueue(), window_hwnd, &swap_chain_desc, nullptr, + nullptr, m_swap_chain.ReleaseAndGetAddressOf()); if (FAILED(hr)) { Error::SetHResult(error, "CreateSwapChainForHwnd() failed: ", hr); @@ -891,22 +908,14 @@ bool D3D12Device::CreateSwapChain(Error* error) } } - hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES); + hr = dev.GetDXGIFactory()->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES); if (FAILED(hr)) WARNING_LOG("MakeWindowAssociation() to disable ALT+ENTER failed"); - if (!CreateSwapChainRTV(error)) - { - DestroySwapChain(); - return false; - } - - // Render a frame as soon as possible to clear out whatever was previously being displayed. - RenderBlankFrame(); return true; } -bool D3D12Device::CreateSwapChainRTV(Error* error) +bool D3D12SwapChain::CreateRTV(D3D12Device& dev, Error* error) { DXGI_SWAP_CHAIN_DESC swap_chain_desc; HRESULT hr = m_swap_chain->GetDesc(&swap_chain_desc); @@ -925,148 +934,162 @@ bool D3D12Device::CreateSwapChainRTV(Error* error) if (FAILED(hr)) { Error::SetHResult(error, "GetBuffer for RTV failed: ", hr); - DestroySwapChainRTVs(); + DestroyRTVs(); return false; } D3D12::SetObjectName(backbuffer.Get(), TinyString::from_format("Swap Chain Buffer #{}", i)); D3D12DescriptorHandle rtv; - if (!m_rtv_heap_manager.Allocate(&rtv)) + if (!dev.GetRTVHeapManager().Allocate(&rtv)) { Error::SetStringView(error, "Failed to allocate RTV handle."); - DestroySwapChainRTVs(); + DestroyRTVs(); return false; } - m_device->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, rtv); + dev.GetDevice()->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, rtv); m_swap_chain_buffers.emplace_back(std::move(backbuffer), rtv); } - m_window_info.surface_width = swap_chain_desc.BufferDesc.Width; - m_window_info.surface_height = swap_chain_desc.BufferDesc.Height; + m_window_info.surface_width = static_cast(swap_chain_desc.BufferDesc.Width); + m_window_info.surface_height = static_cast(swap_chain_desc.BufferDesc.Height); m_window_info.surface_format = s_swap_chain_format; VERBOSE_LOG("Swap chain buffer size: {}x{}", m_window_info.surface_width, m_window_info.surface_height); - if (m_window_info.type == WindowInfo::Type::Win32) + BOOL fullscreen = FALSE; + DXGI_SWAP_CHAIN_DESC desc; + if (SUCCEEDED(m_swap_chain->GetFullscreenState(&fullscreen, nullptr)) && fullscreen && + SUCCEEDED(m_swap_chain->GetDesc(&desc))) { - BOOL fullscreen = FALSE; - DXGI_SWAP_CHAIN_DESC desc; - if (SUCCEEDED(m_swap_chain->GetFullscreenState(&fullscreen, nullptr)) && fullscreen && - SUCCEEDED(m_swap_chain->GetDesc(&desc))) - { - m_window_info.surface_refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) / - static_cast(desc.BufferDesc.RefreshRate.Denominator); - } + m_window_info.surface_refresh_rate = static_cast(desc.BufferDesc.RefreshRate.Numerator) / + static_cast(desc.BufferDesc.RefreshRate.Denominator); } m_current_swap_chain_buffer = 0; return true; } -void D3D12Device::DestroySwapChainRTVs() +void D3D12SwapChain::DestroyRTVs() { + if (m_swap_chain_buffers.empty()) + return; + + D3D12Device& dev = D3D12Device::GetInstance(); + // Runtime gets cranky if we don't submit the current buffer... - if (InRenderPass()) - EndRenderPass(); - SubmitCommandList(true); + if (dev.InRenderPass()) + dev.EndRenderPass(); + dev.SubmitCommandList(true); for (auto it = m_swap_chain_buffers.rbegin(); it != m_swap_chain_buffers.rend(); ++it) { - m_rtv_heap_manager.Free(it->second.index); + dev.GetRTVHeapManager().Free(it->second.index); it->first.Reset(); } m_swap_chain_buffers.clear(); m_current_swap_chain_buffer = 0; } -void D3D12Device::DestroySwapChain() +void D3D12SwapChain::DestroySwapChain() { if (!m_swap_chain) return; - DestroySwapChainRTVs(); - // switch out of fullscreen before destroying BOOL is_fullscreen; if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen) m_swap_chain->SetFullscreenState(FALSE, nullptr); m_swap_chain.Reset(); - m_is_exclusive_fullscreen = false; } -void D3D12Device::RenderBlankFrame() +bool D3D12SwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) { - if (InRenderPass()) - EndRenderPass(); + m_allow_present_throttle = allow_present_throttle; - auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer]; - ID3D12GraphicsCommandList4* cmdlist = GetCommandList(); - m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast(m_swap_chain_buffers.size())); - D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON, - D3D12_RESOURCE_STATE_RENDER_TARGET); - cmdlist->ClearRenderTargetView(swap_chain_buf.second, GSVector4::cxpr(0.0f, 0.0f, 0.0f, 1.0f).F32, 0, nullptr); - D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_RENDER_TARGET, - D3D12_RESOURCE_STATE_PRESENT); - SubmitCommandList(false); - m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0); -} + // Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen. + if (mode == GPUVSyncMode::Mailbox && IsExclusiveFullscreen()) + { + WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen."); + mode = GPUVSyncMode::FIFO; + } -bool D3D12Device::UpdateWindow() -{ - WaitForGPUIdle(); - DestroySwapChain(); - - if (!AcquireWindow(false)) - return false; - - if (m_window_info.IsSurfaceless()) + if (m_vsync_mode == mode) return true; - Error error; - if (!CreateSwapChain(&error)) - { - ERROR_LOG("Failed to create swap chain on updated window: {}", error.GetDescription()); - return false; - } + const u32 old_buffer_count = GetNewBufferCount(m_vsync_mode); + const u32 new_buffer_count = GetNewBufferCount(mode); + m_vsync_mode = mode; + if (old_buffer_count == new_buffer_count) + return true; - RenderBlankFrame(); - return true; + // Buffer count change => needs recreation. + DestroyRTVs(); + DestroySwapChain(); + + D3D12Device& dev = D3D12Device::GetInstance(); + return CreateSwapChain(dev, error) && CreateRTV(dev, error); } -void D3D12Device::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) +bool D3D12SwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) { - if (!m_swap_chain) - return; + m_window_info.surface_scale = new_scale; + if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height) + return true; - m_window_info.surface_scale = new_window_scale; - - if (m_window_info.surface_width == static_cast(new_window_width) && - m_window_info.surface_height == static_cast(new_window_height)) - { - return; - } - - DestroySwapChainRTVs(); + DestroyRTVs(); HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0); if (FAILED(hr)) ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast(hr)); - Error error; - if (!CreateSwapChainRTV(&error)) - { - ERROR_LOG("Failed to recreate swap chain RTV after resize", error.GetDescription()); - Panic("Failed to recreate swap chain RTV after resize"); - } + return CreateRTV(D3D12Device::GetInstance(), error); } -void D3D12Device::DestroySurface() +std::unique_ptr D3D12Device::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) { - DestroySwapChainRTVs(); - DestroySwapChain(); + std::unique_ptr ret; + if (wi.type != WindowInfo::Type::Win32) + { + Error::SetStringView(error, "Cannot create a swap chain on non-win32 window."); + return ret; + } + + ret = std::make_unique(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode); + if (ret->CreateSwapChain(*this, error) && ret->CreateRTV(*this, error)) + { + // Render a frame as soon as possible to clear out whatever was previously being displayed. + RenderBlankFrame(ret.get()); + } + else + { + ret.reset(); + } + + return ret; +} + +void D3D12Device::RenderBlankFrame(D3D12SwapChain* swap_chain) +{ + if (InRenderPass()) + EndRenderPass(); + + const D3D12SwapChain::BufferPair& swap_chain_buf = swap_chain->GetCurrentBuffer(); + ID3D12GraphicsCommandList4* cmdlist = GetCommandList(); + D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_RENDER_TARGET); + cmdlist->ClearRenderTargetView(swap_chain_buf.second, GSVector4::cxpr(0.0f, 0.0f, 0.0f, 1.0f).F32, 0, nullptr); + D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PRESENT); + SubmitCommandList(false); + swap_chain->GetSwapChain()->Present(0, swap_chain->IsUsingAllowTearing() ? DXGI_PRESENT_ALLOW_TEARING : 0); + swap_chain->AdvanceBuffer(); } bool D3D12Device::SupportsTextureFormat(GPUTexture::Format format) const @@ -1105,79 +1128,77 @@ std::string D3D12Device::GetDriverInfo() const return ret; } -void D3D12Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) -{ - m_allow_present_throttle = allow_present_throttle; - - // Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen. - if (mode == GPUVSyncMode::Mailbox && m_is_exclusive_fullscreen) - { - WARNING_LOG("Using FIFO instead of Mailbox vsync due to exclusive fullscreen."); - mode = GPUVSyncMode::FIFO; - } - - if (m_vsync_mode == mode) - return; - - const u32 old_buffer_count = GetSwapChainBufferCount(); - m_vsync_mode = mode; - if (!m_swap_chain) - return; - - if (GetSwapChainBufferCount() != old_buffer_count) - { - DestroySwapChain(); - - Error error; - if (!CreateSwapChain(&error)) - { - ERROR_LOG("Failed to recreate swap chain after vsync change: {}", error.GetDescription()); - Panic("Failed to recreate swap chain after vsync change."); - } - } -} - -GPUDevice::PresentResult D3D12Device::BeginPresent(u32 clear_color) +GPUDevice::PresentResult D3D12Device::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) { + D3D12SwapChain* const SC = static_cast(swap_chain); if (InRenderPass()) EndRenderPass(); if (m_device_was_lost) [[unlikely]] return PresentResult::DeviceLost; - // If we're running surfaceless, kick the command buffer so we don't run out of descriptors. - if (!m_swap_chain) - { - SubmitCommandList(false); - TrimTexturePool(); - return PresentResult::SkipPresent; - } - // TODO: Check if the device was lost. // Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode. // This might get called repeatedly if it takes a while to switch back, that's the host's problem. BOOL is_fullscreen; - if (m_is_exclusive_fullscreen && - (FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen)) + if (SC->IsExclusiveFullscreen() && + (FAILED(SC->GetSwapChain()->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen)) { - Host::RunOnCPUThread([]() { Host::SetFullscreen(false); }); + FlushCommands(); TrimTexturePool(); - return PresentResult::SkipPresent; + return PresentResult::ExclusiveFullscreenLost; } - BeginSwapChainRenderPass(clear_color); + m_current_swap_chain = SC; + + const D3D12SwapChain::BufferPair& swap_chain_buf = SC->GetCurrentBuffer(); + ID3D12GraphicsCommandList4* const cmdlist = GetCommandList(); + + D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_RENDER_TARGET); + + // All textures should be in shader read only optimal already, but just in case.. + const u32 num_textures = GetActiveTexturesForLayout(m_current_pipeline_layout); + for (u32 i = 0; i < num_textures; i++) + { + if (m_current_textures[i]) + m_current_textures[i]->TransitionToState(cmdlist, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + } + + D3D12_RENDER_PASS_RENDER_TARGET_DESC rt_desc = {swap_chain_buf.second, + {D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, {}}, + {D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}}; + GSVector4::store(rt_desc.BeginningAccess.Clear.ClearValue.Color, GSVector4::rgba32(clear_color)); + cmdlist->BeginRenderPass(1, &rt_desc, nullptr, D3D12_RENDER_PASS_FLAG_NONE); + + std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets)); + m_num_current_render_targets = 0; + m_dirty_flags = + (m_dirty_flags & ~DIRTY_FLAG_RT_UAVS) | ((IsUsingROVRootSignature()) ? DIRTY_FLAG_PIPELINE_LAYOUT : 0); + m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags; + m_current_depth_target = nullptr; + m_in_render_pass = true; + s_stats.num_render_passes++; + + // Clear pipeline, it's likely incompatible. + m_current_pipeline = nullptr; + return PresentResult::OK; } -void D3D12Device::EndPresent(bool explicit_present, u64 present_time) +void D3D12Device::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) { + D3D12SwapChain* const SC = static_cast(swap_chain); DebugAssert(present_time == 0); DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target); EndRenderPass(); - const auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer]; - m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast(m_swap_chain_buffers.size())); + DebugAssert(SC == m_current_swap_chain); + m_current_swap_chain = nullptr; + + const D3D12SwapChain::BufferPair& swap_chain_buf = SC->GetCurrentBuffer(); + SC->AdvanceBuffer(); ID3D12GraphicsCommandList* cmdlist = GetCommandList(); D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_RENDER_TARGET, @@ -1187,18 +1208,19 @@ void D3D12Device::EndPresent(bool explicit_present, u64 present_time) TrimTexturePool(); if (!explicit_present) - SubmitPresent(); + SubmitPresent(swap_chain); } -void D3D12Device::SubmitPresent() +void D3D12Device::SubmitPresent(GPUSwapChain* swap_chain) { - DebugAssert(m_swap_chain); + D3D12SwapChain* const SC = static_cast(swap_chain); if (m_device_was_lost) [[unlikely]] return; - const UINT sync_interval = static_cast(m_vsync_mode == GPUVSyncMode::FIFO); - const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0; - m_swap_chain->Present(sync_interval, flags); + const UINT sync_interval = static_cast(SC->GetVSyncMode() == GPUVSyncMode::FIFO); + const UINT flags = + (SC->GetVSyncMode() == GPUVSyncMode::Disabled && SC->IsUsingAllowTearing()) ? DXGI_PRESENT_ALLOW_TEARING : 0; + SC->GetSwapChain()->Present(sync_interval, flags); } #ifdef _DEBUG @@ -1251,7 +1273,6 @@ void D3D12Device::InsertDebugMessage(const char* msg) void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features) { - m_render_api = RenderAPI::D3D12; m_render_api_version = D3DCommon::GetRenderAPIVersionForFeatureLevel(feature_level); m_max_texture_size = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; m_max_multisamples = 1; @@ -1286,11 +1307,6 @@ void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disab m_features.pipeline_cache = true; m_features.prefer_unused_textures = true; - BOOL allow_tearing_supported = false; - HRESULT hr = m_dxgi_factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, - sizeof(allow_tearing_supported)); - m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE); - m_features.raster_order_views = false; if (!(disabled_features & FEATURE_MASK_RASTER_ORDER_VIEWS)) { @@ -1841,7 +1857,7 @@ void D3D12Device::BeginRenderPass() else { // Re-rendering to swap chain. - const auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer]; + const auto& swap_chain_buf = m_current_swap_chain->GetCurrentBuffer(); rt_desc[0] = {swap_chain_buf.second, {D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE, {}}, {D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}}; @@ -1869,43 +1885,6 @@ void D3D12Device::BeginRenderPass() SetInitialPipelineState(); } -void D3D12Device::BeginSwapChainRenderPass(u32 clear_color) -{ - DebugAssert(!InRenderPass()); - - ID3D12GraphicsCommandList4* const cmdlist = GetCommandList(); - const auto& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer]; - - D3D12Texture::TransitionSubresourceToState(cmdlist, swap_chain_buf.first.Get(), 0, D3D12_RESOURCE_STATE_COMMON, - D3D12_RESOURCE_STATE_RENDER_TARGET); - - // All textures should be in shader read only optimal already, but just in case.. - const u32 num_textures = GetActiveTexturesForLayout(m_current_pipeline_layout); - for (u32 i = 0; i < num_textures; i++) - { - if (m_current_textures[i]) - m_current_textures[i]->TransitionToState(cmdlist, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); - } - - D3D12_RENDER_PASS_RENDER_TARGET_DESC rt_desc = {swap_chain_buf.second, - {D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, {}}, - {D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {}}}; - GSVector4::store(rt_desc.BeginningAccess.Clear.ClearValue.Color, GSVector4::rgba32(clear_color)); - cmdlist->BeginRenderPass(1, &rt_desc, nullptr, D3D12_RENDER_PASS_FLAG_NONE); - - std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets)); - m_num_current_render_targets = 0; - m_dirty_flags = - (m_dirty_flags & ~DIRTY_FLAG_RT_UAVS) | ((IsUsingROVRootSignature()) ? DIRTY_FLAG_PIPELINE_LAYOUT : 0); - m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags; - m_current_depth_target = nullptr; - m_in_render_pass = true; - s_stats.num_render_passes++; - - // Clear pipeline, it's likely incompatible. - m_current_pipeline = nullptr; -} - bool D3D12Device::InRenderPass() { return m_in_render_pass; diff --git a/src/util/d3d12_device.h b/src/util/d3d12_device.h index c78742236..e20bd525f 100644 --- a/src/util/d3d12_device.h +++ b/src/util/d3d12_device.h @@ -37,6 +37,8 @@ namespace D3D12MA { class Allocator; } +class D3D12SwapChain; + class D3D12Device final : public GPUDevice { public: @@ -58,17 +60,16 @@ public: D3D12Device(); ~D3D12Device() override; - bool HasSurface() const override; - - bool UpdateWindow() override; - void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; - - void DestroySurface() override; - std::string GetDriverInfo() const override; - void ExecuteAndWaitForGPUIdle() override; + void FlushCommands() override; + void WaitForGPUIdle() override; + std::unique_ptr CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) override; std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, GPUTexture::Format format, const void* data = nullptr, u32 data_stride = 0) override; @@ -119,23 +120,22 @@ public: void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override; - void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; - bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; - PresentResult BeginPresent(u32 clear_color) override; - void EndPresent(bool explicit_present, u64 present_time) override; - void SubmitPresent() override; + PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override; + void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override; + void SubmitPresent(GPUSwapChain* swap_chain) override; // Global state accessors ALWAYS_INLINE static D3D12Device& GetInstance() { return *static_cast(g_gpu_device.get()); } ALWAYS_INLINE IDXGIAdapter1* GetAdapter() const { return m_adapter.Get(); } ALWAYS_INLINE ID3D12Device1* GetDevice() const { return m_device.Get(); } ALWAYS_INLINE ID3D12CommandQueue* GetCommandQueue() const { return m_command_queue.Get(); } + ALWAYS_INLINE IDXGIFactory5* GetDXGIFactory() { return m_dxgi_factory.Get(); } ALWAYS_INLINE D3D12MA::Allocator* GetAllocator() const { return m_allocator.Get(); } - void WaitForGPUIdle(); + void WaitForAllFences(); // Descriptor manager access. D3D12DescriptorHeapManager& GetDescriptorHeapManager() { return m_descriptor_heap_manager; } @@ -173,6 +173,12 @@ public: // Also invokes callbacks for completion. void WaitForFence(u64 fence_counter); + // Ends a render pass if we're currently in one. + // When Bind() is next called, the pass will be restarted. + void BeginRenderPass(); + void EndRenderPass(); + bool InRenderPass(); + /// Ends any render pass, executes the command buffer, and invalidates cached state. void SubmitCommandList(bool wait_for_completion); void SubmitCommandList(bool wait_for_completion, const std::string_view reason); @@ -183,8 +189,10 @@ public: void UnbindTextureBuffer(D3D12TextureBuffer* buf); protected: - bool CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) override; + bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi, + GPUVSyncMode vsync_mode, bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) override; void DestroyDevice() override; bool ReadPipelineCache(DynamicHeapArray data, Error* error) override; @@ -232,12 +240,6 @@ private: void GetPipelineCacheHeader(PIPELINE_CACHE_HEADER* hdr); void SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disabled_features); - u32 GetSwapChainBufferCount() const; - bool CreateSwapChain(Error* error); - bool CreateSwapChainRTV(Error* error); - void DestroySwapChainRTVs(); - void DestroySwapChain(); - bool CreateCommandLists(Error* error); void DestroyCommandLists(); bool CreateRootSignatures(Error* error); @@ -252,7 +254,7 @@ private: void DestroySamplers(); void DestroyDeferredObjects(u64 fence_value); - void RenderBlankFrame(); + void RenderBlankFrame(D3D12SwapChain* swap_chain); void MoveToNextCommandList(); bool CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32 levels, u32 samples, DXGI_FORMAT format, @@ -280,13 +282,6 @@ private: bool UpdateParametersForLayout(u32 dirty); bool UpdateRootParameters(u32 dirty); - // Ends a render pass if we're currently in one. - // When Bind() is next called, the pass will be restarted. - void BeginRenderPass(); - void BeginSwapChainRenderPass(u32 clear_color); - void EndRenderPass(); - bool InRenderPass(); - ComPtr m_adapter; ComPtr m_device; ComPtr m_command_queue; @@ -299,15 +294,9 @@ private: std::array m_command_lists; u32 m_current_command_list = NUM_COMMAND_LISTS - 1; + bool m_device_was_lost = false; ComPtr m_dxgi_factory; - ComPtr m_swap_chain; - std::vector, D3D12DescriptorHandle>> m_swap_chain_buffers; - u32 m_current_swap_chain_buffer = 0; - bool m_allow_tearing_supported = false; - bool m_using_allow_tearing = false; - bool m_is_exclusive_fullscreen = false; - bool m_device_was_lost = false; D3D12DescriptorHeapManager m_descriptor_heap_manager; D3D12DescriptorHeapManager m_rtv_heap_manager; @@ -358,4 +347,52 @@ private: D3D12TextureBuffer* m_current_texture_buffer = nullptr; GSVector4i m_current_viewport = GSVector4i::cxpr(0, 0, 1, 1); GSVector4i m_current_scissor = {}; + + D3D12SwapChain* m_current_swap_chain = nullptr; +}; + +class D3D12SwapChain : public GPUSwapChain +{ +public: + template + using ComPtr = Microsoft::WRL::ComPtr; + + friend D3D12Device; + + using BufferPair = std::pair, D3D12DescriptorHandle>; + + D3D12SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + const GPUDevice::ExclusiveFullscreenMode* fullscreen_mode); + ~D3D12SwapChain() override; + + ALWAYS_INLINE IDXGISwapChain1* GetSwapChain() const { return m_swap_chain.Get(); } + ALWAYS_INLINE const BufferPair& GetCurrentBuffer() const { return m_swap_chain_buffers[m_current_swap_chain_buffer]; } + ALWAYS_INLINE bool IsUsingAllowTearing() const { return m_using_allow_tearing; } + ALWAYS_INLINE bool IsExclusiveFullscreen() const { return m_fullscreen_mode.has_value(); } + + void AdvanceBuffer() + { + m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast(m_swap_chain_buffers.size())); + } + bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override; + bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override; + +private: + static u32 GetNewBufferCount(GPUVSyncMode vsync_mode); + + bool InitializeExclusiveFullscreenMode(const GPUDevice::ExclusiveFullscreenMode* mode); + + bool CreateSwapChain(D3D12Device& dev, Error* error); + bool CreateRTV(D3D12Device& dev, Error* error); + + void DestroySwapChain(); + void DestroyRTVs(); + + ComPtr m_swap_chain; + std::vector m_swap_chain_buffers; + u32 m_current_swap_chain_buffer = 0; + bool m_using_allow_tearing = false; + + ComPtr m_fullscreen_output; + std::optional m_fullscreen_mode; }; diff --git a/src/util/d3d12_pipeline.cpp b/src/util/d3d12_pipeline.cpp index 2f364128e..c25a67c5c 100644 --- a/src/util/d3d12_pipeline.cpp +++ b/src/util/d3d12_pipeline.cpp @@ -14,7 +14,7 @@ #include -LOG_CHANNEL(D3D12Device); +LOG_CHANNEL(GPUDevice); D3D12Shader::D3D12Shader(GPUShaderStage stage, Bytecode bytecode) : GPUShader(stage), m_bytecode(std::move(bytecode)) { diff --git a/src/util/d3d12_stream_buffer.cpp b/src/util/d3d12_stream_buffer.cpp index 110a11f80..eb8858306 100644 --- a/src/util/d3d12_stream_buffer.cpp +++ b/src/util/d3d12_stream_buffer.cpp @@ -13,7 +13,7 @@ #include -LOG_CHANNEL(D3D12StreamBuffer); +LOG_CHANNEL(GPUDevice); D3D12StreamBuffer::D3D12StreamBuffer() = default; diff --git a/src/util/d3d12_texture.cpp b/src/util/d3d12_texture.cpp index fe128d934..789761935 100644 --- a/src/util/d3d12_texture.cpp +++ b/src/util/d3d12_texture.cpp @@ -15,7 +15,7 @@ #include "D3D12MemAlloc.h" -LOG_CHANNEL(D3D12Device); +LOG_CHANNEL(GPUDevice); D3D12Texture::D3D12Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format, DXGI_FORMAT dxgi_format, ComPtr resource, diff --git a/src/util/d3d_common.cpp b/src/util/d3d_common.cpp index 1aa00b3a4..61a3a944f 100644 --- a/src/util/d3d_common.cpp +++ b/src/util/d3d_common.cpp @@ -19,7 +19,7 @@ #include #include -LOG_CHANNEL(D3DCommon); +LOG_CHANNEL(GPUDevice); namespace D3DCommon { namespace { @@ -123,6 +123,14 @@ Microsoft::WRL::ComPtr D3DCommon::CreateFactory(bool debug, Error return factory; } +bool D3DCommon::SupportsAllowTearing(IDXGIFactory5* factory) +{ + BOOL allow_tearing_supported = false; + HRESULT hr = factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, + sizeof(allow_tearing_supported)); + return (SUCCEEDED(hr) && allow_tearing_supported == TRUE); +} + static std::string FixupDuplicateAdapterNames(const GPUDevice::AdapterInfoList& adapter_names, std::string adapter_name) { if (std::any_of(adapter_names.begin(), adapter_names.end(), @@ -183,9 +191,11 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList() { for (const DXGI_MODE_DESC& mode : dmodes) { - ai.fullscreen_modes.push_back(GPUDevice::GetFullscreenModeString( - mode.Width, mode.Height, - static_cast(mode.RefreshRate.Numerator) / static_cast(mode.RefreshRate.Denominator))); + ai.fullscreen_modes.push_back( + GPUDevice::ExclusiveFullscreenMode{.width = mode.Width, + .height = mode.Height, + .refresh_rate = static_cast(mode.RefreshRate.Numerator) / + static_cast(mode.RefreshRate.Denominator)}); } } else @@ -211,10 +221,13 @@ GPUDevice::AdapterInfoList D3DCommon::GetAdapterInfoList() return adapters; } -bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width, - u32 height, float refresh_rate, DXGI_FORMAT format, - DXGI_MODE_DESC* fullscreen_mode, IDXGIOutput** output) +std::optional +D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, + const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode, + DXGI_FORMAT format, IDXGIOutput** output) { + std::optional ret; + // We need to find which monitor the window is located on. const GSVector4i client_rc_vec(window_rect.left, window_rect.top, window_rect.right, window_rect.bottom); @@ -260,7 +273,7 @@ bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, if (!first_output) { ERROR_LOG("No DXGI output found. Can't use exclusive fullscreen."); - return false; + return ret; } WARNING_LOG("No DXGI output found for window, using first."); @@ -268,22 +281,25 @@ bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, } DXGI_MODE_DESC request_mode = {}; - request_mode.Width = width; - request_mode.Height = height; + request_mode.Width = requested_fullscreen_mode->width; + request_mode.Height = requested_fullscreen_mode->height; request_mode.Format = format; - request_mode.RefreshRate.Numerator = static_cast(std::floor(refresh_rate * 1000.0f)); + request_mode.RefreshRate.Numerator = static_cast(std::floor(requested_fullscreen_mode->refresh_rate * 1000.0f)); request_mode.RefreshRate.Denominator = 1000u; - if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, fullscreen_mode, nullptr)) || + ret = DXGI_MODE_DESC(); + + if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, &ret.value(), nullptr)) || request_mode.Format != format) { ERROR_LOG("Failed to find closest matching mode, hr={:08X}", static_cast(hr)); - return false; + ret.reset(); + return ret; } *output = intersecting_output.Get(); intersecting_output->AddRef(); - return true; + return ret; } Microsoft::WRL::ComPtr D3DCommon::GetAdapterByName(IDXGIFactory5* factory, std::string_view name) diff --git a/src/util/d3d_common.h b/src/util/d3d_common.h index f665ece89..2a2f1784a 100644 --- a/src/util/d3d_common.h +++ b/src/util/d3d_common.h @@ -35,14 +35,16 @@ D3D_FEATURE_LEVEL GetDeviceMaxFeatureLevel(IDXGIAdapter1* adapter); // create a dxgi factory Microsoft::WRL::ComPtr CreateFactory(bool debug, Error* error); +bool SupportsAllowTearing(IDXGIFactory5* factory); // returns a list of all adapter names GPUDevice::AdapterInfoList GetAdapterInfoList(); // returns the fullscreen mode to use for the specified dimensions -bool GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width, u32 height, - float refresh_rate, DXGI_FORMAT format, DXGI_MODE_DESC* fullscreen_mode, - IDXGIOutput** output); +std::optional +GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, + const GPUDevice::ExclusiveFullscreenMode* requested_fullscreen_mode, + DXGI_FORMAT format, IDXGIOutput** output); // get an adapter based on name Microsoft::WRL::ComPtr GetAdapterByName(IDXGIFactory5* factory, std::string_view name); diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 34052d6da..b3b90af66 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -3,8 +3,6 @@ #include "gpu_device.h" #include "compress_helpers.h" -#include "core/host.h" // TODO: Remove, needed for getting fullscreen mode. -#include "core/settings.h" // TODO: Remove, needed for dump directory. #include "gpu_framebuffer_manager.h" #include "shadergen.h" @@ -44,6 +42,7 @@ LOG_CHANNEL(GPUDevice); std::unique_ptr g_gpu_device; +static std::string s_shader_dump_path; static std::string s_pipeline_cache_path; static size_t s_pipeline_cache_size; static std::array s_pipeline_cache_hash; @@ -226,6 +225,49 @@ size_t GPUFramebufferManagerBase::KeyHash::operator()(const Key& key) const return XXH32(&key, sizeof(key), 0x1337); } +GPUSwapChain::GPUSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle) + : m_window_info(wi), m_vsync_mode(vsync_mode), m_allow_present_throttle(allow_present_throttle) +{ +} + +GPUSwapChain::~GPUSwapChain() = default; + +bool GPUSwapChain::ShouldSkipPresentingFrame() +{ + // Only needed with FIFO. But since we're so fast, we allow it always. + if (!m_allow_present_throttle) + return false; + + const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f; + const float throttle_period = 1.0f / throttle_rate; + + const u64 now = Common::Timer::GetCurrentValue(); + const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time); + if (diff < throttle_period) + return true; + + m_last_frame_displayed_time = now; + return false; +} + +void GPUSwapChain::ThrottlePresentation() +{ + const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f; + + const u64 sleep_period = Common::Timer::ConvertNanosecondsToValue(1e+9f / static_cast(throttle_rate)); + const u64 current_ts = Common::Timer::GetCurrentValue(); + + // Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to + // allow time for the actual rendering. + const u64 max_variance = sleep_period * 2; + if (static_cast(std::abs(static_cast(current_ts - m_last_frame_displayed_time))) > max_variance) + m_last_frame_displayed_time = current_ts + sleep_period; + else + m_last_frame_displayed_time += sleep_period; + + Common::Timer::SleepUntil(m_last_frame_displayed_time, false); +} + GPUDevice::GPUDevice() { ResetStatistics(); @@ -346,21 +388,18 @@ GPUDevice::AdapterInfoList GPUDevice::GetAdapterListForAPI(RenderAPI api) return ret; } -bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version, - bool debug_device, GPUVSyncMode vsync, bool allow_present_throttle, - std::optional exclusive_fullscreen_control, FeatureMask disabled_features, Error* error) +bool GPUDevice::Create(std::string_view adapter, FeatureMask disabled_features, std::string_view shader_dump_path, + std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device, + const WindowInfo& wi, GPUVSyncMode vsync, bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) { - m_vsync_mode = vsync; - m_allow_present_throttle = allow_present_throttle; m_debug_device = debug_device; + s_shader_dump_path = shader_dump_path; - if (!AcquireWindow(true)) - { - Error::SetStringView(error, "Failed to acquire window from host."); - return false; - } - - if (!CreateDevice(adapter, exclusive_fullscreen_control, disabled_features, error)) + INFO_LOG("Main render window is {}x{}.", wi.surface_width, wi.surface_height); + if (!CreateDeviceAndMainSwapChain(adapter, disabled_features, wi, vsync, allow_present_throttle, + exclusive_fullscreen_mode, exclusive_fullscreen_control, error)) { if (error && !error->IsValid()) error->SetStringView("Failed to create device."); @@ -383,14 +422,30 @@ bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_p void GPUDevice::Destroy() { + s_shader_dump_path = {}; + PurgeTexturePool(); - if (HasSurface()) - DestroySurface(); DestroyResources(); CloseShaderCache(); DestroyDevice(); } +bool GPUDevice::RecreateMainSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) +{ + + m_main_swap_chain.reset(); + m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode, + exclusive_fullscreen_control, error); + return static_cast(m_main_swap_chain); +} + +void GPUDevice::DestroyMainSwapChain() +{ + m_main_swap_chain.reset(); +} + bool GPUDevice::SupportsExclusiveFullscreen() const { return false; @@ -533,17 +588,6 @@ bool GPUDevice::GetPipelineCacheData(DynamicHeapArray* data, Error* error) return false; } -bool GPUDevice::AcquireWindow(bool recreate_window) -{ - std::optional wi = Host::AcquireRenderWindow(recreate_window); - if (!wi.has_value()) - return false; - - INFO_LOG("Render window is {}x{}.", wi->surface_width, wi->surface_height); - m_window_info = wi.value(); - return true; -} - bool GPUDevice::CreateResources(Error* error) { if (!(m_nearest_sampler = CreateSampler(GPUSampler::GetNearestConfig())) || @@ -587,7 +631,7 @@ bool GPUDevice::CreateResources(Error* error) plconfig.depth = GPUPipeline::DepthState::GetNoTestsState(); plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState(); plconfig.blend.write_mask = 0x7; - plconfig.SetTargetFormats(HasSurface() ? m_window_info.surface_format : GPUTexture::Format::RGBA8); + plconfig.SetTargetFormats(m_main_swap_chain ? m_main_swap_chain->GetFormat() : GPUTexture::Format::RGBA8); plconfig.samples = 1; plconfig.per_sample_shading = false; plconfig.render_pass_flags = GPUPipeline::NoRenderPassFlags; @@ -619,23 +663,23 @@ void GPUDevice::DestroyResources() m_shader_cache.Close(); } -void GPUDevice::RenderImGui() +void GPUDevice::RenderImGui(GPUSwapChain* swap_chain) { GL_SCOPE("RenderImGui"); ImGui::Render(); const ImDrawData* draw_data = ImGui::GetDrawData(); - if (draw_data->CmdListsCount == 0) + if (draw_data->CmdListsCount == 0 || !swap_chain) return; SetPipeline(m_imgui_pipeline.get()); - SetViewportAndScissor(0, 0, m_window_info.surface_width, m_window_info.surface_height); + SetViewportAndScissor(0, 0, swap_chain->GetWidth(), swap_chain->GetHeight()); const float L = 0.0f; - const float R = static_cast(m_window_info.surface_width); + const float R = static_cast(swap_chain->GetWidth()); const float T = 0.0f; - const float B = static_cast(m_window_info.surface_height); + const float B = static_cast(swap_chain->GetHeight()); const float ortho_projection[4][4] = { {2.0f / (R - L), 0.0f, 0.0f, 0.0f}, {0.0f, 2.0f / (T - B), 0.0f, 0.0f}, @@ -666,8 +710,7 @@ void GPUDevice::RenderImGui() if (flip) { const s32 height = static_cast(pcmd->ClipRect.w - pcmd->ClipRect.y); - const s32 flipped_y = - static_cast(m_window_info.surface_height) - static_cast(pcmd->ClipRect.y) - height; + const s32 flipped_y = static_cast(swap_chain->GetHeight()) - static_cast(pcmd->ClipRect.y) - height; SetScissor(static_cast(pcmd->ClipRect.x), flipped_y, static_cast(pcmd->ClipRect.z - pcmd->ClipRect.x), height); } @@ -789,69 +832,59 @@ std::unique_ptr GPUDevice::CreateShader(GPUShaderStage stage, GPUShad return shader; } -bool GPUDevice::GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate) +std::optional GPUDevice::ExclusiveFullscreenMode::Parse(std::string_view str) { - const std::string mode = Host::GetBaseStringSettingValue("GPU", "FullscreenMode", ""); - if (!mode.empty()) + std::optional ret; + std::string_view::size_type sep1 = str.find('x'); + if (sep1 != std::string_view::npos) { - const std::string_view mode_view = mode; - std::string_view::size_type sep1 = mode.find('x'); - if (sep1 != std::string_view::npos) - { - std::optional owidth = StringUtil::FromChars(mode_view.substr(0, sep1)); + std::optional owidth = StringUtil::FromChars(str.substr(0, sep1)); + sep1++; + + while (sep1 < str.length() && std::isspace(str[sep1])) sep1++; - while (sep1 < mode.length() && std::isspace(mode[sep1])) - sep1++; - - if (owidth.has_value() && sep1 < mode.length()) + if (owidth.has_value() && sep1 < str.length()) + { + std::string_view::size_type sep2 = str.find('@', sep1); + if (sep2 != std::string_view::npos) { - std::string_view::size_type sep2 = mode.find('@', sep1); - if (sep2 != std::string_view::npos) - { - std::optional oheight = StringUtil::FromChars(mode_view.substr(sep1, sep2 - sep1)); + std::optional oheight = StringUtil::FromChars(str.substr(sep1, sep2 - sep1)); + sep2++; + + while (sep2 < str.length() && std::isspace(str[sep2])) sep2++; - while (sep2 < mode.length() && std::isspace(mode[sep2])) - sep2++; - - if (oheight.has_value() && sep2 < mode.length()) + if (oheight.has_value() && sep2 < str.length()) + { + std::optional orefresh_rate = StringUtil::FromChars(str.substr(sep2)); + if (orefresh_rate.has_value()) { - std::optional orefresh_rate = StringUtil::FromChars(mode_view.substr(sep2)); - if (orefresh_rate.has_value()) - { - *width = owidth.value(); - *height = oheight.value(); - *refresh_rate = orefresh_rate.value(); - return true; - } + ret = ExclusiveFullscreenMode{ + .width = owidth.value(), .height = oheight.value(), .refresh_rate = orefresh_rate.value()}; } } } } } - *width = 0; - *height = 0; - *refresh_rate = 0; - return false; + return ret; } -std::string GPUDevice::GetFullscreenModeString(u32 width, u32 height, float refresh_rate) +TinyString GPUDevice::ExclusiveFullscreenMode::ToString() const { - return fmt::format("{} x {} @ {} hz", width, height, refresh_rate); -} - -std::string GPUDevice::GetShaderDumpPath(std::string_view name) -{ - return Path::Combine(EmuFolders::Dumps, name); + return TinyString::from_format("{} x {} @ {} hz", width, height, refresh_rate); } void GPUDevice::DumpBadShader(std::string_view code, std::string_view errors) { static u32 next_bad_shader_id = 0; - const std::string filename = GetShaderDumpPath(fmt::format("bad_shader_{}.txt", ++next_bad_shader_id)); + if (s_shader_dump_path.empty()) + return; + + const std::string filename = + Path::Combine(s_shader_dump_path, TinyString::from_format("bad_shader_{}.txt", ++next_bad_shader_id)); auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb"); if (fp) { @@ -1124,42 +1157,6 @@ bool GPUDevice::ResizeTexture(std::unique_ptr* tex, u32 new_width, u return true; } -bool GPUDevice::ShouldSkipPresentingFrame() -{ - // Only needed with FIFO. But since we're so fast, we allow it always. - if (!m_allow_present_throttle) - return false; - - const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f; - const float throttle_period = 1.0f / throttle_rate; - - const u64 now = Common::Timer::GetCurrentValue(); - const double diff = Common::Timer::ConvertValueToSeconds(now - m_last_frame_displayed_time); - if (diff < throttle_period) - return true; - - m_last_frame_displayed_time = now; - return false; -} - -void GPUDevice::ThrottlePresentation() -{ - const float throttle_rate = (m_window_info.surface_refresh_rate > 0.0f) ? m_window_info.surface_refresh_rate : 60.0f; - - const u64 sleep_period = Common::Timer::ConvertNanosecondsToValue(1e+9f / static_cast(throttle_rate)); - const u64 current_ts = Common::Timer::GetCurrentValue(); - - // Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to - // allow time for the actual rendering. - const u64 max_variance = sleep_period * 2; - if (static_cast(std::abs(static_cast(current_ts - m_last_frame_displayed_time))) > max_variance) - m_last_frame_displayed_time = current_ts + sleep_period; - else - m_last_frame_displayed_time += sleep_period; - - Common::Timer::SleepUntil(m_last_frame_displayed_time, false); -} - bool GPUDevice::SetGPUTimingEnabled(bool enabled) { return false; diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 7928605f9..309b4db39 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -453,6 +453,38 @@ protected: u32 m_current_position = 0; }; +class GPUSwapChain +{ +public: + GPUSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle); + virtual ~GPUSwapChain(); + + ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; } + ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; } + ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; } + ALWAYS_INLINE float GetScale() const { return m_window_info.surface_scale; } + ALWAYS_INLINE GPUTexture::Format GetFormat() const { return m_window_info.surface_format; } + + ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; } + ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode == GPUVSyncMode::FIFO); } + ALWAYS_INLINE bool IsPresentThrottleAllowed() const { return m_allow_present_throttle; } + + virtual bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) = 0; + virtual bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) = 0; + + bool ShouldSkipPresentingFrame(); + void ThrottlePresentation(); + +protected: + // TODO: Merge WindowInfo into this struct... + WindowInfo m_window_info; + + GPUVSyncMode m_vsync_mode = GPUVSyncMode::Disabled; + bool m_allow_present_throttle = false; + + u64 m_last_frame_displayed_time = 0; +}; + class GPUDevice { public: @@ -485,6 +517,7 @@ public: { OK, SkipPresent, + ExclusiveFullscreenLost, DeviceLost, }; @@ -521,10 +554,22 @@ public: u32 num_uploads; }; + // Parameters for exclusive fullscreen. + struct ExclusiveFullscreenMode + { + u32 width; + u32 height; + float refresh_rate; + + TinyString ToString() const; + + static std::optional Parse(std::string_view str); + }; + struct AdapterInfo { std::string name; - std::vector fullscreen_modes; + std::vector fullscreen_modes; u32 max_texture_size; u32 max_multisamples; bool supports_sample_shading; @@ -565,15 +610,6 @@ public: /// Returns a list of adapters for the given API. static AdapterInfoList GetAdapterListForAPI(RenderAPI api); - /// Parses a fullscreen mode into its components (width * height @ refresh hz) - static bool GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate); - - /// Converts a fullscreen mode to a string. - static std::string GetFullscreenModeString(u32 width, u32 height, float refresh_rate); - - /// Returns the directory bad shaders are saved to. - static std::string GetShaderDumpPath(std::string_view name); - /// Dumps out a shader that failed compilation. static void DumpBadShader(std::string_view code, std::string_view errors); @@ -600,35 +636,44 @@ public: ALWAYS_INLINE u32 GetMaxTextureSize() const { return m_max_texture_size; } ALWAYS_INLINE u32 GetMaxMultisamples() const { return m_max_multisamples; } - ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; } - ALWAYS_INLINE s32 GetWindowWidth() const { return static_cast(m_window_info.surface_width); } - ALWAYS_INLINE s32 GetWindowHeight() const { return static_cast(m_window_info.surface_height); } - ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; } - ALWAYS_INLINE GPUTexture::Format GetWindowFormat() const { return m_window_info.surface_format; } + ALWAYS_INLINE GPUSwapChain* GetMainSwapChain() const { return m_main_swap_chain.get(); } + ALWAYS_INLINE bool HasMainSwapChain() const { return static_cast(m_main_swap_chain); } + // ALWAYS_INLINE u32 GetMainSwapChainWidth() const { return m_main_swap_chain->GetWidth(); } + // ALWAYS_INLINE u32 GetMainSwapChainHeight() const { return m_main_swap_chain->GetHeight(); } + // ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; } + // ALWAYS_INLINE GPUTexture::Format GetWindowFormat() const { return m_window_info.surface_format; } ALWAYS_INLINE GPUSampler* GetLinearSampler() const { return m_linear_sampler.get(); } ALWAYS_INLINE GPUSampler* GetNearestSampler() const { return m_nearest_sampler.get(); } ALWAYS_INLINE bool IsGPUTimingEnabled() const { return m_gpu_timing_enabled; } - bool Create(std::string_view adapter, std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device, - GPUVSyncMode vsync, bool allow_present_throttle, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error); + bool Create(std::string_view adapter, FeatureMask disabled_features, std::string_view shader_dump_path, + std::string_view shader_cache_path, u32 shader_cache_version, bool debug_device, const WindowInfo& wi, + GPUVSyncMode vsync, bool allow_present_throttle, const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error); void Destroy(); - virtual bool HasSurface() const = 0; - virtual void DestroySurface() = 0; - virtual bool UpdateWindow() = 0; + virtual std::unique_ptr CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) = 0; + + bool RecreateMainSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error); + void DestroyMainSwapChain(); virtual bool SupportsExclusiveFullscreen() const; - /// Call when the window size changes externally to recreate any resources. - virtual void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) = 0; - virtual std::string GetDriverInfo() const = 0; + // Flushes current command buffer, but does not wait for completion. + virtual void FlushCommands() = 0; + // Executes current command buffer, waits for its completion, and destroys all pending resources. - virtual void ExecuteAndWaitForGPUIdle() = 0; + virtual void WaitForGPUIdle() = 0; virtual std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, GPUTexture::Format format, @@ -710,17 +755,12 @@ public: virtual void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) = 0; /// Returns false if the window was completely occluded. - virtual PresentResult BeginPresent(u32 clear_color = DEFAULT_CLEAR_COLOR) = 0; - virtual void EndPresent(bool explicit_submit, u64 submit_time = 0) = 0; - virtual void SubmitPresent() = 0; + virtual PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color = DEFAULT_CLEAR_COLOR) = 0; + virtual void EndPresent(GPUSwapChain* swap_chain, bool explicit_submit, u64 submit_time = 0) = 0; + virtual void SubmitPresent(GPUSwapChain* swap_chain) = 0; /// Renders ImGui screen elements. Call before EndPresent(). - void RenderImGui(); - - ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; } - ALWAYS_INLINE bool IsVSyncModeBlocking() const { return (m_vsync_mode == GPUVSyncMode::FIFO); } - ALWAYS_INLINE bool IsPresentThrottleAllowed() const { return m_allow_present_throttle; } - virtual void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) = 0; + void RenderImGui(GPUSwapChain* swap_chain); ALWAYS_INLINE bool IsDebugDevice() const { return m_debug_device; } ALWAYS_INLINE size_t GetVRAMUsage() const { return s_total_vram_usage; } @@ -730,8 +770,6 @@ public: static GSVector4i FlipToLowerLeft(GSVector4i rc, s32 target_height); bool ResizeTexture(std::unique_ptr* tex, u32 new_width, u32 new_height, GPUTexture::Type type, GPUTexture::Format format, bool preserve = true); - bool ShouldSkipPresentingFrame(); - void ThrottlePresentation(); virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0; @@ -745,8 +783,10 @@ public: static void ResetStatistics(); protected: - virtual bool CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) = 0; + virtual bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, + const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) = 0; virtual void DestroyDevice() = 0; std::string GetShaderCacheBaseName(std::string_view type) const; @@ -762,8 +802,6 @@ protected: std::string_view source, const char* entry_point, DynamicHeapArray* out_binary, Error* error) = 0; - bool AcquireWindow(bool recreate_window); - void TrimTexturePool(); bool CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, GPUShaderLanguage source_language, std::string_view source, @@ -784,8 +822,7 @@ protected: u32 m_max_texture_size = 0; u32 m_max_multisamples = 0; - WindowInfo m_window_info; - u64 m_last_frame_displayed_time = 0; + std::unique_ptr m_main_swap_chain; GPUShaderCache m_shader_cache; @@ -852,8 +889,6 @@ private: protected: static Statistics s_stats; - GPUVSyncMode m_vsync_mode = GPUVSyncMode::Disabled; - bool m_allow_present_throttle = false; bool m_gpu_timing_enabled = false; bool m_debug_device = false; }; @@ -865,21 +900,6 @@ ALWAYS_INLINE void GPUDevice::PooledTextureDeleter::operator()(GPUTexture* const g_gpu_device->RecycleTexture(std::unique_ptr(tex)); } -namespace Host { -/// Called when the core is creating a render device. -/// This could also be fullscreen transition. -std::optional AcquireRenderWindow(bool recreate_window); - -/// Called when the core is finished with a render window. -void ReleaseRenderWindow(); - -/// Returns true if the hosting application is currently fullscreen. -bool IsFullscreen(); - -/// Alters fullscreen state of hosting application. -void SetFullscreen(bool enabled); -} // namespace Host - // Macros for debug messages. #ifdef _DEBUG struct GLAutoPop diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index 360482743..b3cdd2c25 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -231,7 +231,8 @@ bool ImGuiManager::Initialize(float global_scale, Error* error) } s_global_prescale = global_scale; - s_global_scale = std::max(g_gpu_device->GetWindowScale() * global_scale, 1.0f); + s_global_scale = std::max( + (g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetScale() : 1.0f) * global_scale, 1.0f); s_scale_changed = false; ImGui::CreateContext(); @@ -250,8 +251,10 @@ bool ImGuiManager::Initialize(float global_scale, Error* error) io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; #endif - s_window_width = static_cast(g_gpu_device->GetWindowWidth()); - s_window_height = static_cast(g_gpu_device->GetWindowHeight()); + s_window_width = + g_gpu_device->HasMainSwapChain() ? static_cast(g_gpu_device->GetMainSwapChain()->GetWidth()) : 0.0f; + s_window_height = + g_gpu_device->HasMainSwapChain() ? static_cast(g_gpu_device->GetMainSwapChain()->GetHeight()) : 0.0f; io.DisplayFramebufferScale = ImVec2(1, 1); // We already scale things ourselves, this would double-apply scaling io.DisplaySize = ImVec2(s_window_width, s_window_height); @@ -316,7 +319,8 @@ void ImGuiManager::RequestScaleUpdate() void ImGuiManager::UpdateScale() { - const float window_scale = g_gpu_device ? g_gpu_device->GetWindowScale() : 1.0f; + const float window_scale = + (g_gpu_device && g_gpu_device->HasMainSwapChain()) ? g_gpu_device->GetMainSwapChain()->GetScale() : 1.0f; const float scale = std::max(window_scale * s_global_prescale, 1.0f); if ((!HasFullscreenFonts() || !ImGuiFullscreen::UpdateLayoutScale()) && scale == s_global_scale) diff --git a/src/util/input_manager.cpp b/src/util/input_manager.cpp index c98c2815c..0fca23857 100644 --- a/src/util/input_manager.cpp +++ b/src/util/input_manager.cpp @@ -607,11 +607,12 @@ static std::array(InputSourceType::Count)> s_input #ifdef _WIN32 "DInput", "XInput", -#endif -#ifndef __ANDROID__ - "SDL", "RawInput", -#else +#endif +#ifdef ENABLE_SDL + "SDL", +#endif +#ifdef __ANDROID__ "Android", #endif }}; @@ -640,14 +641,17 @@ bool InputManager::GetInputSourceDefaultEnabled(InputSourceType type) case InputSourceType::XInput: return false; -#endif -#ifndef __ANDROID__ - case InputSourceType::SDL: - return true; case InputSourceType::RawInput: return false; -#else +#endif + +#ifdef ENABLE_SDL + case InputSourceType::SDL: + return true; +#endif + +#ifdef __ANDROID__ case InputSourceType::Android: return true; #endif @@ -1229,7 +1233,7 @@ void InputManager::UpdatePointerCount() return; } -#ifndef __ANDROID__ +#ifdef _WIN32 InputSource* ris = GetInputSourceInterface(InputSourceType::RawInput); DebugAssert(ris); @@ -2048,9 +2052,10 @@ void InputManager::ReloadSources(SettingsInterface& si, std::unique_lock CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) override; std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, GPUTexture::Format format, const void* data = nullptr, u32 data_stride = 0) override; @@ -261,11 +278,9 @@ public: bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; - void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; - - PresentResult BeginPresent(u32 clear_color) override; - void EndPresent(bool explicit_submit, u64 present_time) override; - void SubmitPresent() override; + PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override; + void EndPresent(GPUSwapChain* swap_chain, bool explicit_submit, u64 present_time) override; + void SubmitPresent(GPUSwapChain* swap_chain) override; void WaitForFenceCounter(u64 counter); @@ -285,8 +300,10 @@ public: static void DeferRelease(u64 fence_counter, id obj); protected: - bool CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) override; + bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi, + GPUVSyncMode vsync_mode, bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) override; void DestroyDevice() override; bool OpenPipelineCache(const std::string& path, Error* error) override; bool CreatePipelineCache(const std::string& path, Error* error) override; @@ -315,8 +332,6 @@ private: }; static_assert(sizeof(ClearPipelineConfig) == 8); - ALWAYS_INLINE NSView* GetWindowView() const { return (__bridge NSView*)m_window_info.window_handle; } - void SetFeatures(FeatureMask disabled_features); bool LoadShaders(); @@ -340,15 +355,12 @@ private: void EndInlineUploading(); void EndAnyEncoding(); - GSVector4i ClampToFramebufferSize(const GSVector4i rc) const; void PreDrawCheck(); void SetInitialEncoderState(); void SetViewportInRenderEncoder(); void SetScissorInRenderEncoder(); - bool CreateLayer(); - void DestroyLayer(); - void RenderBlankFrame(); + void RenderBlankFrame(MetalSwapChain* swap_chain); bool CreateBuffers(); void DestroyBuffers(); @@ -358,10 +370,6 @@ private: id m_device; id m_queue; - CAMetalLayer* m_layer = nil; - id m_layer_drawable = nil; - MTLRenderPassDescriptor* m_layer_pass_desc = nil; - std::mutex m_fence_mutex; u64 m_current_fence_counter = 0; std::atomic m_completed_fence_counter{0}; @@ -402,10 +410,11 @@ private: id m_current_ssbo = nil; GSVector4i m_current_viewport = {}; GSVector4i m_current_scissor = {}; - - bool m_vsync_enabled = false; - bool m_pipeline_cache_modified = false; + GSVector4i m_current_framebuffer_size = {}; double m_accumulated_gpu_time = 0; double m_last_gpu_time_end = 0; + + id m_layer_drawable = nil; + bool m_pipeline_cache_modified = false; }; diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index 4ba9d4d28..fe30df994 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -21,7 +21,7 @@ #include #include -LOG_CHANNEL(MetalDevice); +LOG_CHANNEL(GPUDevice); // TODO: Disable hazard tracking and issue barriers explicitly. @@ -128,34 +128,160 @@ static void RunOnMainThread(F&& f) MetalDevice::MetalDevice() : m_current_viewport(0, 0, 1, 1), m_current_scissor(0, 0, 1, 1) { + m_render_api = RenderAPI::Metal; } MetalDevice::~MetalDevice() { - Assert(m_pipeline_archive == nil && m_layer == nil && m_device == nil); + Assert(m_pipeline_archive == nil && m_layer_drawable == nil && m_device == nil); } -bool MetalDevice::HasSurface() const +MetalSwapChain::MetalSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + CAMetalLayer* layer) + : GPUSwapChain(wi, vsync_mode, allow_present_throttle), m_layer(layer) { - return (m_layer != nil); } -void MetalDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) +MetalSwapChain::~MetalSwapChain() +{ + Destroy(true); +} + +void MetalSwapChain::Destroy(bool wait_for_gpu) +{ + if (!m_layer) + return; + + if (wait_for_gpu) + MetalDevice::GetInstance().WaitForGPUIdle(); + + RunOnMainThread([this]() { + NSView* view = (__bridge NSView*)m_window_info.window_handle; + [view setLayer:nil]; + [view setWantsLayer:FALSE]; + [m_layer release]; + m_layer = nullptr; + }); +} + +bool MetalSwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) +{ + @autoreleasepool + { + m_window_info.surface_scale = new_scale; + if (new_width == m_window_info.surface_width && new_height == m_window_info.surface_height) + { + return true; + } + + m_window_info.surface_width = new_width; + m_window_info.surface_height = new_height; + + [m_layer setDrawableSize:CGSizeMake(new_width, new_height)]; + return true; + } +} + +bool MetalSwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) { // Metal does not support mailbox mode. mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode; m_allow_present_throttle = allow_present_throttle; if (m_vsync_mode == mode) - return; + return true; m_vsync_mode = mode; if (m_layer != nil) [m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO]; + + return true; } -bool MetalDevice::CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) +std::unique_ptr MetalDevice::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) +{ + @autoreleasepool + { + CAMetalLayer* layer; + WindowInfo wi_copy(wi); + RunOnMainThread([this, &layer, &wi_copy, error]() { + @autoreleasepool + { + INFO_LOG("Creating a {}x{} Metal layer.", wi_copy.surface_width, wi_copy.surface_height); + layer = [CAMetalLayer layer]; // TODO: Does this need retain?? + if (layer == nil) + { + Error::SetStringView(error, "Failed to create metal layer."); + return; + } + + [layer setDevice:m_device]; + [layer setDrawableSize:CGSizeMake(static_cast(wi_copy.surface_width), + static_cast(wi_copy.surface_height))]; + + // Default should be BGRA8. + const MTLPixelFormat layer_fmt = [layer pixelFormat]; + wi_copy.surface_format = GetTextureFormatForMTLFormat(layer_fmt); + if (wi_copy.surface_format == GPUTexture::Format::Unknown) + { + ERROR_LOG("Invalid pixel format {} in layer, using BGRA8.", static_cast(layer_fmt)); + [layer setPixelFormat:MTLPixelFormatBGRA8Unorm]; + wi_copy.surface_format = GPUTexture::Format::BGRA8; + } + + VERBOSE_LOG("Metal layer pixel format is {}.", GPUTexture::GetFormatName(wi_copy.surface_format)); + + NSView* view = (__bridge NSView*)wi_copy.window_handle; + [view setWantsLayer:TRUE]; + [view setLayer:layer]; + } + }); + + if (!layer) + return {}; + + // Metal does not support mailbox mode. + vsync_mode = (vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : vsync_mode; + [layer setDisplaySyncEnabled:vsync_mode == GPUVSyncMode::FIFO]; + + // Clear it out ASAP. + std::unique_ptr swap_chain = + std::make_unique(wi_copy, vsync_mode, allow_present_throttle, layer); + RenderBlankFrame(swap_chain.get()); + return swap_chain; + } +} + +void MetalDevice::RenderBlankFrame(MetalSwapChain* swap_chain) +{ + @autoreleasepool + { + // has to be encoding, we don't "begin" a render pass here, so the inline encoder won't get flushed otherwise. + EndAnyEncoding(); + + id drawable = [[swap_chain->GetLayer() nextDrawable] retain]; + MTLRenderPassDescriptor* desc = [MTLRenderPassDescriptor renderPassDescriptor]; + desc.colorAttachments[0].loadAction = MTLLoadActionClear; + desc.colorAttachments[0].storeAction = MTLStoreActionStore; + desc.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0); + desc.colorAttachments[0].texture = [drawable texture]; + id encoder = [m_render_cmdbuf renderCommandEncoderWithDescriptor:desc]; + [encoder endEncoding]; + [m_render_cmdbuf presentDrawable:drawable]; + DeferRelease(drawable); + SubmitCommandBuffer(); + } +} + +bool MetalDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, + const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) { @autoreleasepool { @@ -199,15 +325,20 @@ bool MetalDevice::CreateDevice(std::string_view adapter, std::optional exc INFO_LOG("Metal Device: {}", [[m_device name] UTF8String]); SetFeatures(disabled_features); - - if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateLayer()) - { - Error::SetStringView(error, "Failed to create layer."); - return false; - } - CreateCommandBuffer(); - RenderBlankFrame(); + + if (!wi.IsSurfaceless()) + { + m_main_swap_chain = CreateSwapChain(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_mode, + exclusive_fullscreen_control, error); + if (!m_main_swap_chain) + { + Error::SetStringView(error, "Failed to create layer."); + return false; + } + + RenderBlankFrame(static_cast(m_main_swap_chain.get())); + } if (!LoadShaders()) { @@ -228,7 +359,6 @@ bool MetalDevice::CreateDevice(std::string_view adapter, std::optional exc void MetalDevice::SetFeatures(FeatureMask disabled_features) { // Set version to Metal 2.3, that's all we're using. Use SPIRV-Cross version encoding. - m_render_api = RenderAPI::Metal; m_render_api_version = 20300; m_max_texture_size = GetMetalMaxTextureSize(m_device); m_max_multisamples = GetMetalMaxMultisamples(m_device); @@ -416,6 +546,12 @@ void MetalDevice::DestroyDevice() m_render_cmdbuf = nil; } + if (m_main_swap_chain) + { + static_cast(m_main_swap_chain.get())->Destroy(false); + m_main_swap_chain.reset(); + } + DestroyBuffers(); for (auto& it : m_cleanup_objects) @@ -457,135 +593,6 @@ void MetalDevice::DestroyDevice() } } -bool MetalDevice::CreateLayer() -{ - @autoreleasepool - { - RunOnMainThread([this]() { - @autoreleasepool - { - INFO_LOG("Creating a {}x{} Metal layer.", m_window_info.surface_width, m_window_info.surface_height); - const auto size = - CGSizeMake(static_cast(m_window_info.surface_width), static_cast(m_window_info.surface_height)); - m_layer = [CAMetalLayer layer]; - [m_layer setDevice:m_device]; - [m_layer setDrawableSize:size]; - - // Default should be BGRA8. - const MTLPixelFormat layer_fmt = [m_layer pixelFormat]; - m_window_info.surface_format = GetTextureFormatForMTLFormat(layer_fmt); - if (m_window_info.surface_format == GPUTexture::Format::Unknown) - { - ERROR_LOG("Invalid pixel format {} in layer, using BGRA8.", static_cast(layer_fmt)); - [m_layer setPixelFormat:MTLPixelFormatBGRA8Unorm]; - m_window_info.surface_format = GPUTexture::Format::BGRA8; - } - - VERBOSE_LOG("Metal layer pixel format is {}.", GPUTexture::GetFormatName(m_window_info.surface_format)); - - NSView* view = GetWindowView(); - [view setWantsLayer:TRUE]; - [view setLayer:m_layer]; - } - }); - - // Metal does not support mailbox mode. - m_vsync_mode = (m_vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : m_vsync_mode; - [m_layer setDisplaySyncEnabled:m_vsync_mode == GPUVSyncMode::FIFO]; - - DebugAssert(m_layer_pass_desc == nil); - m_layer_pass_desc = [[MTLRenderPassDescriptor renderPassDescriptor] retain]; - m_layer_pass_desc.renderTargetWidth = m_window_info.surface_width; - m_layer_pass_desc.renderTargetHeight = m_window_info.surface_height; - m_layer_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear; - m_layer_pass_desc.colorAttachments[0].storeAction = MTLStoreActionStore; - m_layer_pass_desc.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0); - return true; - } -} - -void MetalDevice::DestroyLayer() -{ - if (m_layer == nil) - return; - - // Should wait for previous command buffers to finish, which might be rendering to drawables. - WaitForPreviousCommandBuffers(); - - [m_layer_pass_desc release]; - m_layer_pass_desc = nil; - m_window_info.surface_format = GPUTexture::Format::Unknown; - - RunOnMainThread([this]() { - NSView* view = GetWindowView(); - [view setLayer:nil]; - [view setWantsLayer:FALSE]; - [m_layer release]; - m_layer = nullptr; - }); -} - -void MetalDevice::RenderBlankFrame() -{ - DebugAssert(!InRenderPass()); - if (m_layer == nil) - return; - - @autoreleasepool - { - id drawable = [[m_layer nextDrawable] retain]; - m_layer_pass_desc.colorAttachments[0].texture = [drawable texture]; - id encoder = [m_render_cmdbuf renderCommandEncoderWithDescriptor:m_layer_pass_desc]; - [encoder endEncoding]; - [m_render_cmdbuf presentDrawable:drawable]; - DeferRelease(drawable); - SubmitCommandBuffer(); - } -} - -bool MetalDevice::UpdateWindow() -{ - if (InRenderPass()) - EndRenderPass(); - DestroyLayer(); - - if (!AcquireWindow(false)) - return false; - - if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateLayer()) - { - ERROR_LOG("Failed to create layer on updated window"); - return false; - } - - return true; -} - -void MetalDevice::DestroySurface() -{ - DestroyLayer(); -} - -void MetalDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) -{ - @autoreleasepool - { - m_window_info.surface_scale = new_window_scale; - if (static_cast(new_window_width) == m_window_info.surface_width && - static_cast(new_window_height) == m_window_info.surface_height) - { - return; - } - - m_window_info.surface_width = new_window_width; - m_window_info.surface_height = new_window_height; - - [m_layer setDrawableSize:CGSizeMake(new_window_width, new_window_height)]; - m_layer_pass_desc.renderTargetWidth = m_window_info.surface_width; - m_layer_pass_desc.renderTargetHeight = m_window_info.surface_height; - } -} - std::string MetalDevice::GetDriverInfo() const { @autoreleasepool @@ -2082,6 +2089,8 @@ void MetalDevice::BeginRenderPass() // Rendering to view, but we got interrupted... desc.colorAttachments[0].texture = [m_layer_drawable texture]; desc.colorAttachments[0].loadAction = MTLLoadActionLoad; + desc.renderTargetWidth = m_current_framebuffer_size.width(); + desc.renderTargetHeight = m_current_framebuffer_size.height(); } else { @@ -2157,6 +2166,10 @@ void MetalDevice::BeginRenderPass() break; } } + + MetalTexture* rt_or_ds = + (m_num_current_render_targets > 0) ? m_current_render_targets[0] : m_current_depth_target; + m_current_framebuffer_size = GSVector4i(0, 0, rt_or_ds->GetWidth(), rt_or_ds->GetHeight()); } m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:desc] retain]; @@ -2218,7 +2231,7 @@ void MetalDevice::SetInitialEncoderState() void MetalDevice::SetViewportInRenderEncoder() { - const GSVector4i rc = ClampToFramebufferSize(m_current_viewport); + const GSVector4i rc = m_current_viewport.rintersect(m_current_framebuffer_size); [m_render_encoder setViewport:(MTLViewport){static_cast(rc.left), static_cast(rc.top), static_cast(rc.width()), static_cast(rc.height()), 0.0, 1.0}]; @@ -2226,21 +2239,12 @@ void MetalDevice::SetViewportInRenderEncoder() void MetalDevice::SetScissorInRenderEncoder() { - const GSVector4i rc = ClampToFramebufferSize(m_current_scissor); + const GSVector4i rc = m_current_scissor.rintersect(m_current_framebuffer_size); [m_render_encoder setScissorRect:(MTLScissorRect){static_cast(rc.left), static_cast(rc.top), static_cast(rc.width()), static_cast(rc.height())}]; } -GSVector4i MetalDevice::ClampToFramebufferSize(const GSVector4i rc) const -{ - const MetalTexture* rt_or_ds = - (m_num_current_render_targets > 0) ? m_current_render_targets[0] : m_current_depth_target; - const s32 clamp_width = rt_or_ds ? rt_or_ds->GetWidth() : m_window_info.surface_width; - const s32 clamp_height = rt_or_ds ? rt_or_ds->GetHeight() : m_window_info.surface_height; - return rc.rintersect(GSVector4i(0, 0, clamp_width, clamp_height)); -} - void MetalDevice::PreDrawCheck() { if (!InRenderPass()) @@ -2413,35 +2417,35 @@ id MetalDevice::GetBlitEncoder(bool is_inline) } } -GPUDevice::PresentResult MetalDevice::BeginPresent(u32 clear_color) +GPUDevice::PresentResult MetalDevice::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) { @autoreleasepool { - if (m_layer == nil) - { - TrimTexturePool(); - return PresentResult::SkipPresent; - } - EndAnyEncoding(); - m_layer_drawable = [[m_layer nextDrawable] retain]; + m_layer_drawable = [[static_cast(swap_chain)->GetLayer() nextDrawable] retain]; if (m_layer_drawable == nil) { + WARNING_LOG("Failed to get drawable from layer."); + SubmitCommandBuffer(); TrimTexturePool(); return PresentResult::SkipPresent; } - SetViewportAndScissor(0, 0, m_window_info.surface_width, m_window_info.surface_height); + m_current_framebuffer_size = GSVector4i(0, 0, swap_chain->GetWidth(), swap_chain->GetHeight()); + SetViewportAndScissor(m_current_framebuffer_size); // Set up rendering to layer. const GSVector4 clear_color_v = GSVector4::rgba32(clear_color); id layer_texture = [m_layer_drawable texture]; - m_layer_pass_desc.colorAttachments[0].texture = layer_texture; - m_layer_pass_desc.colorAttachments[0].loadAction = MTLLoadActionClear; - m_layer_pass_desc.colorAttachments[0].clearColor = + MTLRenderPassDescriptor* desc = [MTLRenderPassDescriptor renderPassDescriptor]; + desc.colorAttachments[0].texture = layer_texture; + desc.colorAttachments[0].loadAction = MTLLoadActionClear; + desc.colorAttachments[0].clearColor = MTLClearColorMake(clear_color_v.r, clear_color_v.g, clear_color_v.g, clear_color_v.a); - m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:m_layer_pass_desc] retain]; + desc.renderTargetWidth = swap_chain->GetWidth(); + desc.renderTargetHeight = swap_chain->GetHeight(); + m_render_encoder = [[m_render_cmdbuf renderCommandEncoderWithDescriptor:desc] retain]; s_stats.num_render_passes++; std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets)); m_num_current_render_targets = 0; @@ -2454,7 +2458,7 @@ GPUDevice::PresentResult MetalDevice::BeginPresent(u32 clear_color) } } -void MetalDevice::EndPresent(bool explicit_present, u64 present_time) +void MetalDevice::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) { DebugAssert(!explicit_present); DebugAssert(m_num_current_render_targets == 0 && !m_current_depth_target); @@ -2480,7 +2484,7 @@ void MetalDevice::EndPresent(bool explicit_present, u64 present_time) TrimTexturePool(); } -void MetalDevice::SubmitPresent() +void MetalDevice::SubmitPresent(GPUSwapChain* swap_chainwel) { Panic("Not supported by this API."); } @@ -2585,12 +2589,18 @@ void MetalDevice::WaitForPreviousCommandBuffers() WaitForFenceCounter(m_current_fence_counter - 1); } -void MetalDevice::ExecuteAndWaitForGPUIdle() +void MetalDevice::WaitForGPUIdle() { SubmitCommandBuffer(true); CleanupObjects(); } +void MetalDevice::FlushCommands() +{ + SubmitCommandBuffer(); + TrimTexturePool(); +} + void MetalDevice::CleanupObjects() { const u64 counter = m_completed_fence_counter.load(std::memory_order_acquire); diff --git a/src/util/metal_layer.h b/src/util/metal_layer.h index 0dc6821d2..9d66473fb 100644 --- a/src/util/metal_layer.h +++ b/src/util/metal_layer.h @@ -1,12 +1,13 @@ // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 +class Error; struct WindowInfo; namespace CocoaTools { /// Creates metal layer on specified window surface. -bool CreateMetalLayer(WindowInfo* wi); +void* CreateMetalLayer(const WindowInfo& wi, Error* error); /// Destroys metal layer on specified window surface. -void DestroyMetalLayer(WindowInfo* wi); +void DestroyMetalLayer(const WindowInfo& wi, void* layer); } // namespace CocoaTools diff --git a/src/util/metal_stream_buffer.mm b/src/util/metal_stream_buffer.mm index 8fd138b6d..1bb958638 100644 --- a/src/util/metal_stream_buffer.mm +++ b/src/util/metal_stream_buffer.mm @@ -8,7 +8,7 @@ #include "common/assert.h" #include "common/log.h" -LOG_CHANNEL(MetalDevice); +LOG_CHANNEL(GPUDevice); MetalStreamBuffer::MetalStreamBuffer() = default; diff --git a/src/util/opengl_context.cpp b/src/util/opengl_context.cpp index 8a796d0dd..49b5b6927 100644 --- a/src/util/opengl_context.cpp +++ b/src/util/opengl_context.cpp @@ -32,7 +32,7 @@ #endif #endif -LOG_CHANNEL(OpenGLContext); +LOG_CHANNEL(GPUDevice); static bool ShouldPreferESContext() { @@ -105,13 +105,11 @@ static void DisableBrokenExtensions(const char* gl_vendor, const char* gl_render } } -OpenGLContext::OpenGLContext(const WindowInfo& wi) : m_wi(wi) -{ -} +OpenGLContext::OpenGLContext() = default; OpenGLContext::~OpenGLContext() = default; -std::unique_ptr OpenGLContext::Create(const WindowInfo& wi, Error* error) +std::unique_ptr OpenGLContext::Create(WindowInfo& wi, SurfaceHandle* surface, Error* error) { static constexpr std::array vlist = {{{Profile::Core, 4, 6}, {Profile::Core, 4, 5}, @@ -149,22 +147,22 @@ std::unique_ptr OpenGLContext::Create(const WindowInfo& wi, Error std::unique_ptr context; #if defined(_WIN32) && !defined(_M_ARM64) - context = OpenGLContextWGL::Create(wi, versions_to_try, error); + context = OpenGLContextWGL::Create(wi, surface, versions_to_try, error); #elif defined(__APPLE__) - context = OpenGLContextAGL::Create(wi, versions_to_try, error); + context = OpenGLContextAGL::Create(wi, surface, versions_to_try, error); #elif defined(__ANDROID__) - context = OpenGLContextEGLAndroid::Create(wi, versions_to_try, error); + context = OpenGLContextEGLAndroid::Create(wi, surface, versions_to_try, error); #else #if defined(ENABLE_X11) if (wi.type == WindowInfo::Type::X11) - context = OpenGLContextEGLX11::Create(wi, versions_to_try, error); + context = OpenGLContextEGLX11::Create(wi, surface, versions_to_try, error); #endif #if defined(ENABLE_WAYLAND) if (wi.type == WindowInfo::Type::Wayland) - context = OpenGLContextEGLWayland::Create(wi, versions_to_try, error); + context = OpenGLContextEGLWayland::Create(wi, surface, versions_to_try, error); #endif if (wi.type == WindowInfo::Type::Surfaceless) - context = OpenGLContextEGL::Create(wi, versions_to_try, error); + context = OpenGLContextEGL::Create(wi, surface, versions_to_try, error); #endif if (!context) diff --git a/src/util/opengl_context.h b/src/util/opengl_context.h index 0cb5cc3dc..82e002a8e 100644 --- a/src/util/opengl_context.h +++ b/src/util/opengl_context.h @@ -15,9 +15,12 @@ class Error; class OpenGLContext { public: - OpenGLContext(const WindowInfo& wi); + OpenGLContext(); virtual ~OpenGLContext(); + using SurfaceHandle = void*; + static constexpr SurfaceHandle MAIN_SURFACE = nullptr; + enum class Profile { NoProfile, @@ -32,26 +35,22 @@ public: int minor_version; }; - ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_wi; } ALWAYS_INLINE bool IsGLES() const { return (m_version.profile == Profile::ES); } - ALWAYS_INLINE u32 GetSurfaceWidth() const { return m_wi.surface_width; } - ALWAYS_INLINE u32 GetSurfaceHeight() const { return m_wi.surface_height; } - ALWAYS_INLINE GPUTexture::Format GetSurfaceFormat() const { return m_wi.surface_format; } virtual void* GetProcAddress(const char* name) = 0; - virtual bool ChangeSurface(const WindowInfo& new_wi) = 0; - virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) = 0; + virtual SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) = 0; + virtual void DestroySurface(SurfaceHandle handle) = 0; + virtual void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) = 0; virtual bool SwapBuffers() = 0; virtual bool IsCurrent() const = 0; - virtual bool MakeCurrent() = 0; + virtual bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) = 0; virtual bool DoneCurrent() = 0; virtual bool SupportsNegativeSwapInterval() const = 0; - virtual bool SetSwapInterval(s32 interval) = 0; - virtual std::unique_ptr CreateSharedContext(const WindowInfo& wi, Error* error) = 0; + virtual bool SetSwapInterval(s32 interval, Error* error = nullptr) = 0; + virtual std::unique_ptr CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) = 0; - static std::unique_ptr Create(const WindowInfo& wi, Error* error); + static std::unique_ptr Create(WindowInfo& wi, SurfaceHandle* surface, Error* error); protected: - WindowInfo m_wi; Version m_version = {}; }; diff --git a/src/util/opengl_context_agl.mm b/src/util/opengl_context_agl.mm index 766951ef7..82a26f31b 100644 --- a/src/util/opengl_context_agl.mm +++ b/src/util/opengl_context_agl.mm @@ -9,7 +9,7 @@ #include -LOG_CHANNEL(OpenGLContext); +LOG_CHANNEL(GPUDevice); OpenGLContextAGL::OpenGLContextAGL(const WindowInfo& wi) : OpenGLContext(wi) { diff --git a/src/util/opengl_context_egl.cpp b/src/util/opengl_context_egl.cpp index 6e2507965..e0caa726e 100644 --- a/src/util/opengl_context_egl.cpp +++ b/src/util/opengl_context_egl.cpp @@ -13,7 +13,7 @@ #include #include -LOG_CHANNEL(OpenGLContext); +LOG_CHANNEL(GPUDevice); static DynamicLibrary s_egl_library; static std::atomic_uint32_t s_egl_refcount = 0; @@ -67,34 +67,42 @@ static bool LoadGLADEGL(EGLDisplay display, Error* error) return true; } -OpenGLContextEGL::OpenGLContextEGL(const WindowInfo& wi) : OpenGLContext(wi) +OpenGLContextEGL::OpenGLContextEGL() : OpenGLContext() { LoadEGL(); } OpenGLContextEGL::~OpenGLContextEGL() { - DestroySurface(); - DestroyContext(); + if (m_context != EGL_NO_CONTEXT && eglGetCurrentContext() == m_context) + eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (m_pbuffer_surface != EGL_NO_SURFACE) + eglDestroySurface(m_display, m_pbuffer_surface); + + if (m_context != EGL_NO_CONTEXT) + eglDestroyContext(m_display, m_context); + UnloadEGL(); } -std::unique_ptr OpenGLContextEGL::Create(const WindowInfo& wi, std::span versions_to_try, - Error* error) +std::unique_ptr OpenGLContextEGL::Create(WindowInfo& wi, SurfaceHandle* surface, + std::span versions_to_try, Error* error) { - std::unique_ptr context = std::make_unique(wi); - if (!context->Initialize(versions_to_try, error)) + std::unique_ptr context = std::make_unique(); + if (!context->Initialize(wi, surface, versions_to_try, error)) return nullptr; return context; } -bool OpenGLContextEGL::Initialize(std::span versions_to_try, Error* error) +bool OpenGLContextEGL::Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span versions_to_try, + Error* error) { if (!LoadGLADEGL(EGL_NO_DISPLAY, error)) return false; - m_display = GetPlatformDisplay(error); + m_display = GetPlatformDisplay(wi, error); if (m_display == EGL_NO_DISPLAY) return false; @@ -117,7 +125,7 @@ bool OpenGLContextEGL::Initialize(std::span versions_to_try, Erro for (const Version& cv : versions_to_try) { - if (CreateContextAndSurface(cv, nullptr, true)) + if (CreateContextAndSurface(wi, surface, cv, nullptr, true, error)) return true; } @@ -125,24 +133,30 @@ bool OpenGLContextEGL::Initialize(std::span versions_to_try, Erro return false; } -EGLDisplay OpenGLContextEGL::GetPlatformDisplay(Error* error) +EGLDisplay OpenGLContextEGL::GetPlatformDisplay(const WindowInfo& wi, Error* error) { - EGLDisplay dpy = TryGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, "EGL_MESA_platform_surfaceless"); + EGLDisplay dpy = + TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_SURFACELESS_MESA, "EGL_MESA_platform_surfaceless"); if (dpy == EGL_NO_DISPLAY) - dpy = GetFallbackDisplay(error); + dpy = GetFallbackDisplay(wi.display_connection, error); return dpy; } -EGLSurface OpenGLContextEGL::CreatePlatformSurface(EGLConfig config, void* win, Error* error) +EGLSurface OpenGLContextEGL::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) { - EGLSurface surface = TryCreatePlatformSurface(config, win, error); - if (!surface) - surface = CreateFallbackSurface(config, win, error); + EGLSurface surface = TryCreatePlatformSurface(config, wi.window_handle, error); + if (surface == EGL_NO_SURFACE) + surface = CreateFallbackSurface(config, wi.window_handle, error); return surface; } -EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char* platform_ext) +bool OpenGLContextEGL::SupportsSurfaceless() const +{ + return GLAD_EGL_KHR_surfaceless_context; +} + +EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(void* display, EGLenum platform, const char* platform_ext) { const char* extensions_str = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); if (!extensions_str) @@ -160,7 +174,7 @@ EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char* (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); if (get_platform_display_ext) { - dpy = get_platform_display_ext(platform, m_wi.display_connection, nullptr); + dpy = get_platform_display_ext(platform, display, nullptr); m_use_ext_platform_base = (dpy != EGL_NO_DISPLAY); if (!m_use_ext_platform_base) { @@ -181,7 +195,7 @@ EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char* return dpy; } -EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* win, Error* error) +EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* window, Error* error) { EGLSurface surface = EGL_NO_SURFACE; if (m_use_ext_platform_base) @@ -190,7 +204,7 @@ EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* wi (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); if (create_platform_window_surface_ext) { - surface = create_platform_window_surface_ext(m_display, config, win, nullptr); + surface = create_platform_window_surface_ext(m_display, config, window, nullptr); if (surface == EGL_NO_SURFACE) { const EGLint err = eglGetError(); @@ -206,11 +220,11 @@ EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* wi return surface; } -EGLDisplay OpenGLContextEGL::GetFallbackDisplay(Error* error) +EGLDisplay OpenGLContextEGL::GetFallbackDisplay(void* display, Error* error) { WARNING_LOG("Using fallback eglGetDisplay() path."); - EGLDisplay dpy = eglGetDisplay(m_wi.display_connection); + EGLDisplay dpy = eglGetDisplay((EGLNativeDisplayType)display); if (dpy == EGL_NO_DISPLAY) { const EGLint err = eglGetError(); @@ -234,61 +248,56 @@ EGLSurface OpenGLContextEGL::CreateFallbackSurface(EGLConfig config, void* win, return surface; } +void OpenGLContextEGL::DestroyPlatformSurface(EGLSurface surface) +{ + eglDestroySurface(m_display, surface); +} + void* OpenGLContextEGL::GetProcAddress(const char* name) { return reinterpret_cast(eglGetProcAddress(name)); } -bool OpenGLContextEGL::ChangeSurface(const WindowInfo& new_wi) +OpenGLContext::SurfaceHandle OpenGLContextEGL::CreateSurface(WindowInfo& wi, Error* error /* = nullptr */) { - const bool was_current = (eglGetCurrentContext() == m_context); - if (was_current) - eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (m_surface != EGL_NO_SURFACE) + if (wi.IsSurfaceless()) [[unlikely]] { - eglDestroySurface(m_display, m_surface); - m_surface = EGL_NO_SURFACE; + Error::SetStringView(error, "Trying to create a surfaceless surface."); + return nullptr; } - m_wi = new_wi; - if (!CreateSurface()) - return false; + EGLSurface surface = CreatePlatformSurface(m_config, wi, error); + if (surface == EGL_NO_SURFACE) + return nullptr; - if (was_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context)) - { - ERROR_LOG("Failed to make context current again after surface change"); - return false; - } - - return true; + UpdateWindowInfoSize(wi, surface); + return (SurfaceHandle)surface; } -void OpenGLContextEGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) +void OpenGLContextEGL::DestroySurface(SurfaceHandle handle) { - if (new_surface_width == 0 && new_surface_height == 0) - { - EGLint surface_width, surface_height; - if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) && - eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height)) [[likely]] - { - m_wi.surface_width = static_cast(surface_width); - m_wi.surface_height = static_cast(surface_height); - return; - } - else - { - ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError()); - } - } + // pbuffer surface? + if (!handle) + return; - m_wi.surface_width = new_surface_width; - m_wi.surface_height = new_surface_height; + EGLSurface surface = (EGLSurface)handle; + if (eglGetCurrentSurface(EGL_DRAW) == surface) + eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + DestroyPlatformSurface(surface); +} + +void OpenGLContextEGL::ResizeSurface(WindowInfo& wi, SurfaceHandle handle) +{ + if (!handle) + return; + + UpdateWindowInfoSize(wi, (EGLSurface)handle); } bool OpenGLContextEGL::SwapBuffers() { - return eglSwapBuffers(m_display, m_surface); + return eglSwapBuffers(m_display, m_current_surface); } bool OpenGLContextEGL::IsCurrent() const @@ -296,20 +305,29 @@ bool OpenGLContextEGL::IsCurrent() const return m_context && eglGetCurrentContext() == m_context; } -bool OpenGLContextEGL::MakeCurrent() +bool OpenGLContextEGL::MakeCurrent(SurfaceHandle surface, Error* error /* = nullptr */) { - if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context)) [[unlikely]] + EGLSurface esurface = surface ? (EGLSurface)surface : GetSurfacelessSurface(); + if (esurface == m_current_surface) + return true; + + if (!eglMakeCurrent(m_display, esurface, esurface, m_context)) [[unlikely]] { - ERROR_LOG("eglMakeCurrent() failed: 0x{:X}", eglGetError()); + Error::SetStringFmt(error, "eglMakeCurrent() failed: 0x{:X}", eglGetError()); return false; } + m_current_surface = esurface; return true; } bool OpenGLContextEGL::DoneCurrent() { - return eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (!eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) + return false; + + m_current_surface = EGL_NO_SURFACE; + return true; } bool OpenGLContextEGL::SupportsNegativeSwapInterval() const @@ -317,17 +335,24 @@ bool OpenGLContextEGL::SupportsNegativeSwapInterval() const return m_supports_negative_swap_interval; } -bool OpenGLContextEGL::SetSwapInterval(s32 interval) +bool OpenGLContextEGL::SetSwapInterval(s32 interval, Error* error /* = nullptr */) { - return eglSwapInterval(m_display, interval); + if (!eglSwapInterval(m_display, interval)) + { + Error::SetStringFmt(error, "eglMakeCurrent() failed: 0x{:X}", eglGetError()); + return false; + } + + return true; } -std::unique_ptr OpenGLContextEGL::CreateSharedContext(const WindowInfo& wi, Error* error) +std::unique_ptr OpenGLContextEGL::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, + Error* error) { - std::unique_ptr context = std::make_unique(wi); + std::unique_ptr context = std::make_unique(); context->m_display = m_display; - if (!context->CreateContextAndSurface(m_version, m_context, false)) + if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error)) { Error::SetStringView(error, "Failed to create context/surface"); return nullptr; @@ -336,63 +361,33 @@ std::unique_ptr OpenGLContextEGL::CreateSharedContext(const Windo return context; } -bool OpenGLContextEGL::CreateSurface() +EGLSurface OpenGLContextEGL::GetSurfacelessSurface() { - if (m_wi.type == WindowInfo::Type::Surfaceless) - { - if (GLAD_EGL_KHR_surfaceless_context) - return true; - else - return CreatePBufferSurface(); - } - - Error error; - m_surface = CreatePlatformSurface(m_config, m_wi.window_handle, &error); - if (m_surface == EGL_NO_SURFACE) - { - ERROR_LOG("Failed to create platform surface: {}", error.GetDescription()); - return false; - } - - // Some implementations may require the size to be queried at runtime. - EGLint surface_width, surface_height; - if (eglQuerySurface(m_display, m_surface, EGL_WIDTH, &surface_width) && - eglQuerySurface(m_display, m_surface, EGL_HEIGHT, &surface_height)) - { - m_wi.surface_width = static_cast(surface_width); - m_wi.surface_height = static_cast(surface_height); - } - else - { - ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError()); - } - - m_wi.surface_format = GetSurfaceTextureFormat(); - - return true; + return SupportsSurfaceless() ? EGL_NO_SURFACE : GetPBufferSurface(nullptr); } -bool OpenGLContextEGL::CreatePBufferSurface() +EGLSurface OpenGLContextEGL::GetPBufferSurface(Error* error) { - const u32 width = std::max(m_wi.surface_width, 1); - const u32 height = std::max(m_wi.surface_height, 1); + if (m_pbuffer_surface) + return m_pbuffer_surface; - // TODO: Format EGLint attrib_list[] = { - EGL_WIDTH, static_cast(width), EGL_HEIGHT, static_cast(height), EGL_NONE, + EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, }; - m_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list); - if (!m_surface) [[unlikely]] + m_pbuffer_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list); + if (!m_pbuffer_surface) [[unlikely]] { - ERROR_LOG("eglCreatePbufferSurface() failed: {}", eglGetError()); - return false; + if (error) + error->SetStringFmt("eglCreatePbufferSurface() failed: {}", eglGetError()); + else + ERROR_LOG("eglCreatePbufferSurface() failed: {}", eglGetError()); + + return nullptr; } - m_wi.surface_format = GetSurfaceTextureFormat(); - - DEV_LOG("Created {}x{} pbuffer surface", width, height); - return true; + DEV_LOG("Created pbuffer surface"); + return m_pbuffer_surface; } bool OpenGLContextEGL::CheckConfigSurfaceFormat(EGLConfig config, GPUTexture::Format format) @@ -425,8 +420,21 @@ bool OpenGLContextEGL::CheckConfigSurfaceFormat(EGLConfig config, GPUTexture::Fo } } -GPUTexture::Format OpenGLContextEGL::GetSurfaceTextureFormat() const +void OpenGLContextEGL::UpdateWindowInfoSize(WindowInfo& wi, EGLSurface surface) const { + // Some implementations may require the size to be queried at runtime. + EGLint surface_width, surface_height; + if (eglQuerySurface(m_display, surface, EGL_WIDTH, &surface_width) && + eglQuerySurface(m_display, surface, EGL_HEIGHT, &surface_height)) + { + wi.surface_width = static_cast(surface_width); + wi.surface_height = static_cast(surface_height); + } + else + { + ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError()); + } + int red_size = 0, green_size = 0, blue_size = 0, alpha_size = 0; eglGetConfigAttrib(m_display, m_config, EGL_RED_SIZE, &red_size); eglGetConfigAttrib(m_display, m_config, EGL_GREEN_SIZE, &green_size); @@ -435,48 +443,25 @@ GPUTexture::Format OpenGLContextEGL::GetSurfaceTextureFormat() const if (red_size == 5 && green_size == 6 && blue_size == 5) { - return GPUTexture::Format::RGB565; + wi.surface_format = GPUTexture::Format::RGB565; } else if (red_size == 5 && green_size == 5 && blue_size == 5 && alpha_size == 1) { - return GPUTexture::Format::RGBA5551; + wi.surface_format = GPUTexture::Format::RGBA5551; } else if (red_size == 8 && green_size == 8 && blue_size == 8 && alpha_size == 8) { - return GPUTexture::Format::RGBA8; + wi.surface_format = GPUTexture::Format::RGBA8; } else { ERROR_LOG("Unknown surface format: R={}, G={}, B={}, A={}", red_size, green_size, blue_size, alpha_size); - return GPUTexture::Format::RGBA8; + wi.surface_format = GPUTexture::Format::RGBA8; } } -void OpenGLContextEGL::DestroyContext() -{ - if (eglGetCurrentContext() == m_context) - eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (m_context != EGL_NO_CONTEXT) - { - eglDestroyContext(m_display, m_context); - m_context = EGL_NO_CONTEXT; - } -} - -void OpenGLContextEGL::DestroySurface() -{ - if (eglGetCurrentSurface(EGL_DRAW) == m_surface) - eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (m_surface != EGL_NO_SURFACE) - { - eglDestroySurface(m_display, m_surface); - m_surface = EGL_NO_SURFACE; - } -} - -bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_context) +bool OpenGLContextEGL::CreateContext(bool surfaceless, GPUTexture::Format surface_format, const Version& version, + EGLContext share_context, Error* error) { DEV_LOG("Trying version {}.{} ({})", version.major_version, version.minor_version, version.profile == OpenGLContext::Profile::ES ? @@ -489,18 +474,14 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co ((version.major_version == 2) ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_ES_BIT)) : EGL_OPENGL_BIT, EGL_SURFACE_TYPE, - (m_wi.type != WindowInfo::Type::Surfaceless) ? EGL_WINDOW_BIT : 0, + surfaceless ? 0 : EGL_WINDOW_BIT, }; int nsurface_attribs = 4; - const GPUTexture::Format format = m_wi.surface_format; - if (format == GPUTexture::Format::Unknown) - { - WARNING_LOG("Surface format not specified, assuming RGBA8."); - m_wi.surface_format = GPUTexture::Format::RGBA8; - } + if (surface_format == GPUTexture::Format::Unknown) + surface_format = GPUTexture::Format::RGBA8; - switch (m_wi.surface_format) + switch (surface_format) { case GPUTexture::Format::RGBA8: surface_attribs[nsurface_attribs++] = EGL_RED_SIZE; @@ -522,11 +503,8 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co surface_attribs[nsurface_attribs++] = 5; break; - case GPUTexture::Format::Unknown: - break; - default: - UnreachableCode(); + Error::SetStringFmt(error, "Unsupported texture format {}", GPUTexture::GetFormatName(surface_format)); break; } @@ -536,14 +514,14 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co EGLint num_configs; if (!eglChooseConfig(m_display, surface_attribs, nullptr, 0, &num_configs) || num_configs == 0) { - ERROR_LOG("eglChooseConfig() failed: 0x{:x}", static_cast(eglGetError())); + Error::SetStringFmt(error, "eglChooseConfig() failed: 0x{:x}", static_cast(eglGetError())); return false; } std::vector configs(static_cast(num_configs)); if (!eglChooseConfig(m_display, surface_attribs, configs.data(), num_configs, &num_configs)) { - ERROR_LOG("eglChooseConfig() failed: 0x{:x}", static_cast(eglGetError())); + Error::SetStringFmt(error, "eglChooseConfig() failed: 0x{:x}", static_cast(eglGetError())); return false; } configs.resize(static_cast(num_configs)); @@ -551,7 +529,7 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co std::optional config; for (EGLConfig check_config : configs) { - if (CheckConfigSurfaceFormat(check_config, m_wi.surface_format)) + if (CheckConfigSurfaceFormat(check_config, surface_format)) { config = check_config; break; @@ -578,14 +556,15 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co if (!eglBindAPI((version.profile == Profile::ES) ? EGL_OPENGL_ES_API : EGL_OPENGL_API)) { - ERROR_LOG("eglBindAPI({}) failed", (version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API"); + Error::SetStringFmt(error, "eglBindAPI({}) failed", + (version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API"); return false; } m_context = eglCreateContext(m_display, config.value(), share_context, attribs); if (!m_context) { - ERROR_LOG("eglCreateContext() failed: 0x{:x}", static_cast(eglGetError())); + Error::SetStringFmt(error, "eglCreateContext() failed: 0x{:x}", static_cast(eglGetError())); return false; } @@ -611,30 +590,62 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co return true; } -bool OpenGLContextEGL::CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current) +bool OpenGLContextEGL::CreateContextAndSurface(WindowInfo& wi, SurfaceHandle* surface, const Version& version, + EGLContext share_context, bool make_current, Error* error) { - if (!CreateContext(version, share_context)) + if (!CreateContext(wi.IsSurfaceless(), wi.surface_format, version, share_context, error)) return false; - if (!CreateSurface()) + // create actual surface, need to handle surfaceless here + EGLSurface esurface; + if (wi.IsSurfaceless()) { - ERROR_LOG("Failed to create surface for context"); - eglDestroyContext(m_display, m_context); - m_context = EGL_NO_CONTEXT; - return false; + if (!SupportsSurfaceless()) + { + esurface = GetPBufferSurface(error); + if (esurface == EGL_NO_SURFACE) + { + ERROR_LOG("Failed to create pbuffer surface for context"); + eglDestroyContext(m_display, m_context); + m_context = EGL_NO_SURFACE; + return false; + } + } + else + { + esurface = EGL_NO_SURFACE; + } + + *surface = nullptr; + } + else + { + esurface = CreatePlatformSurface(m_config, wi, error); + if (esurface == EGL_NO_SURFACE) + { + ERROR_LOG("Failed to create surface for context"); + eglDestroyContext(m_display, m_context); + m_context = EGL_NO_SURFACE; + return false; + } + + UpdateWindowInfoSize(wi, esurface); + *surface = esurface; } - if (make_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context)) + if (make_current) { - ERROR_LOG("eglMakeCurrent() failed: 0x{:X}", eglGetError()); - if (m_surface != EGL_NO_SURFACE) + if (!eglMakeCurrent(m_display, esurface, esurface, m_context)) { - eglDestroySurface(m_display, m_surface); - m_surface = EGL_NO_SURFACE; + Error::SetStringFmt(error, "eglMakeCurrent() failed: 0x{:X}", eglGetError()); + if (esurface != EGL_NO_SURFACE && esurface != m_pbuffer_surface) + DestroyPlatformSurface(esurface); + eglDestroyContext(m_display, m_context); + m_context = EGL_NO_CONTEXT; + return false; } - eglDestroyContext(m_display, m_context); - m_context = EGL_NO_CONTEXT; - return false; + + m_current_surface = esurface; } return true; diff --git a/src/util/opengl_context_egl.h b/src/util/opengl_context_egl.h index 4a7fb98b0..f7421b532 100644 --- a/src/util/opengl_context_egl.h +++ b/src/util/opengl_context_egl.h @@ -10,48 +10,54 @@ class OpenGLContextEGL : public OpenGLContext { public: - OpenGLContextEGL(const WindowInfo& wi); + OpenGLContextEGL(); ~OpenGLContextEGL() override; - static std::unique_ptr Create(const WindowInfo& wi, std::span versions_to_try, - Error* error); + static std::unique_ptr Create(WindowInfo& wi, SurfaceHandle* surface, + std::span versions_to_try, Error* error); void* GetProcAddress(const char* name) override; - virtual bool ChangeSurface(const WindowInfo& new_wi) override; - virtual void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) override; + void DestroySurface(SurfaceHandle handle) override; + void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override; bool SwapBuffers() override; bool IsCurrent() const override; - bool MakeCurrent() override; + bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) override; bool DoneCurrent() override; bool SupportsNegativeSwapInterval() const override; - bool SetSwapInterval(s32 interval) override; - virtual std::unique_ptr CreateSharedContext(const WindowInfo& wi, Error* error) override; + bool SetSwapInterval(s32 interval, Error* error = nullptr) override; + std::unique_ptr CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override; protected: - virtual EGLDisplay GetPlatformDisplay(Error* error); - virtual EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error); + virtual EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error); + virtual EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error); + virtual void DestroyPlatformSurface(EGLSurface surface); - EGLDisplay TryGetPlatformDisplay(EGLenum platform, const char* platform_ext); + bool SupportsSurfaceless() const; + + EGLDisplay TryGetPlatformDisplay(void* display, EGLenum platform, const char* platform_ext); EGLSurface TryCreatePlatformSurface(EGLConfig config, void* window, Error* error); - EGLDisplay GetFallbackDisplay(Error* error); + EGLDisplay GetFallbackDisplay(void* display, Error* error); EGLSurface CreateFallbackSurface(EGLConfig config, void* window, Error* error); - bool Initialize(std::span versions_to_try, Error* error); - bool CreateContext(const Version& version, EGLContext share_context); - bool CreateContextAndSurface(const Version& version, EGLContext share_context, bool make_current); - bool CreateSurface(); - bool CreatePBufferSurface(); + bool Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span versions_to_try, Error* error); + bool CreateContext(bool surfaceless, GPUTexture::Format surface_format, const Version& version, + EGLContext share_context, Error* error); + bool CreateContextAndSurface(WindowInfo& wi, SurfaceHandle* surface, const Version& version, EGLContext share_context, + bool make_current, Error* error); + EGLSurface GetPBufferSurface(Error* error); + EGLSurface GetSurfacelessSurface(); bool CheckConfigSurfaceFormat(EGLConfig config, GPUTexture::Format format); - GPUTexture::Format GetSurfaceTextureFormat() const; - void DestroyContext(); - void DestroySurface(); + void UpdateWindowInfoSize(WindowInfo& wi, EGLSurface surface) const; EGLDisplay m_display = EGL_NO_DISPLAY; - EGLSurface m_surface = EGL_NO_SURFACE; EGLContext m_context = EGL_NO_CONTEXT; + EGLSurface m_current_surface = EGL_NO_SURFACE; EGLConfig m_config = {}; + EGLSurface m_pbuffer_surface = EGL_NO_SURFACE; + bool m_use_ext_platform_base = false; bool m_supports_negative_swap_interval = false; }; diff --git a/src/util/opengl_context_egl_wayland.cpp b/src/util/opengl_context_egl_wayland.cpp index e03e7cc40..7c6554ccb 100644 --- a/src/util/opengl_context_egl_wayland.cpp +++ b/src/util/opengl_context_egl_wayland.cpp @@ -3,91 +3,22 @@ #include "opengl_context_egl_wayland.h" +#include "common/assert.h" #include "common/error.h" #include static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1"; -OpenGLContextEGLWayland::OpenGLContextEGLWayland(const WindowInfo& wi) : OpenGLContextEGL(wi) -{ -} +OpenGLContextEGLWayland::OpenGLContextEGLWayland() = default; OpenGLContextEGLWayland::~OpenGLContextEGLWayland() { - if (m_wl_window) - m_wl_egl_window_destroy(m_wl_window); + AssertMsg(m_wl_window_map.empty(), "WL window map should be empty on destructor."); if (m_wl_module) dlclose(m_wl_module); } -std::unique_ptr OpenGLContextEGLWayland::Create(const WindowInfo& wi, - std::span versions_to_try, Error* error) -{ - std::unique_ptr context = std::make_unique(wi); - if (!context->LoadModule(error) || !context->Initialize(versions_to_try, error)) - return nullptr; - - return context; -} - -std::unique_ptr OpenGLContextEGLWayland::CreateSharedContext(const WindowInfo& wi, Error* error) -{ - std::unique_ptr context = std::make_unique(wi); - context->m_display = m_display; - - if (!context->LoadModule(error) || !context->CreateContextAndSurface(m_version, m_context, false)) - return nullptr; - - return context; -} - -void OpenGLContextEGLWayland::ResizeSurface(u32 new_surface_width, u32 new_surface_height) -{ - if (m_wl_window) - m_wl_egl_window_resize(m_wl_window, new_surface_width, new_surface_height, 0, 0); - - OpenGLContextEGL::ResizeSurface(new_surface_width, new_surface_height); -} - -EGLDisplay OpenGLContextEGLWayland::GetPlatformDisplay(Error* error) -{ - EGLDisplay dpy = TryGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, "EGL_EXT_platform_wayland"); - if (dpy == EGL_NO_DISPLAY) - dpy = GetFallbackDisplay(error); - - return dpy; -} - -EGLSurface OpenGLContextEGLWayland::CreatePlatformSurface(EGLConfig config, void* win, Error* error) -{ - if (m_wl_window) - { - m_wl_egl_window_destroy(m_wl_window); - m_wl_window = nullptr; - } - - m_wl_window = m_wl_egl_window_create(static_cast(win), m_wi.surface_width, m_wi.surface_height); - if (!m_wl_window) - { - Error::SetStringView(error, "wl_egl_window_create() failed"); - return EGL_NO_SURFACE; - } - - EGLSurface surface = TryCreatePlatformSurface(config, m_wl_window, error); - if (surface == EGL_NO_SURFACE) - { - surface = CreateFallbackSurface(config, m_wl_window, error); - if (surface == EGL_NO_SURFACE) - { - m_wl_egl_window_destroy(m_wl_window); - m_wl_window = nullptr; - } - } - - return surface; -} - bool OpenGLContextEGLWayland::LoadModule(Error* error) { m_wl_module = dlopen(WAYLAND_EGL_MODNAME, RTLD_NOW | RTLD_GLOBAL); @@ -112,3 +43,78 @@ bool OpenGLContextEGLWayland::LoadModule(Error* error) return true; } + +EGLDisplay OpenGLContextEGLWayland::GetPlatformDisplay(const WindowInfo& wi, Error* error) +{ + EGLDisplay dpy = TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_WAYLAND_KHR, "EGL_EXT_platform_wayland"); + if (dpy == EGL_NO_DISPLAY) + dpy = GetFallbackDisplay(wi.display_connection, error); + + return dpy; +} + +EGLSurface OpenGLContextEGLWayland::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) +{ + struct wl_egl_window* wl_window = + m_wl_egl_window_create(static_cast(wi.window_handle), wi.surface_width, wi.surface_height); + if (!wl_window) + { + Error::SetStringView(error, "wl_egl_window_create() failed"); + return EGL_NO_SURFACE; + } + + EGLSurface surface = TryCreatePlatformSurface(config, wl_window, error); + if (surface == EGL_NO_SURFACE) + { + surface = CreateFallbackSurface(config, wl_window, error); + if (surface == EGL_NO_SURFACE) + { + m_wl_egl_window_destroy(wl_window); + return nullptr; + } + } + + m_wl_window_map.emplace(surface, wl_window); + return surface; +} + +void OpenGLContextEGLWayland::ResizeSurface(WindowInfo& wi, SurfaceHandle handle) +{ + const auto it = m_wl_window_map.find((EGLSurface)handle); + AssertMsg(it != m_wl_window_map.end(), "Missing WL window"); + m_wl_egl_window_resize(it->second, wi.surface_width, wi.surface_height, 0, 0); + + OpenGLContextEGL::ResizeSurface(wi, handle); +} + +void OpenGLContextEGLWayland::DestroyPlatformSurface(EGLSurface surface) +{ + const auto it = m_wl_window_map.find((EGLSurface)surface); + AssertMsg(it != m_wl_window_map.end(), "Missing WL window"); + m_wl_egl_window_destroy(it->second); + m_wl_window_map.erase(it); + + OpenGLContextEGL::DestroyPlatformSurface(surface); +} + +std::unique_ptr OpenGLContextEGLWayland::Create(WindowInfo& wi, SurfaceHandle* surface, + std::span versions_to_try, Error* error) +{ + std::unique_ptr context = std::make_unique(); + if (!context->LoadModule(error) || !context->Initialize(wi, surface, versions_to_try, error)) + return nullptr; + + return context; +} + +std::unique_ptr OpenGLContextEGLWayland::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, + Error* error) +{ + std::unique_ptr context = std::make_unique(); + context->m_display = m_display; + + if (!context->LoadModule(error) || !context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error)) + return nullptr; + + return context; +} diff --git a/src/util/opengl_context_egl_wayland.h b/src/util/opengl_context_egl_wayland.h index e249c8b2f..f98bf7eb1 100644 --- a/src/util/opengl_context_egl_wayland.h +++ b/src/util/opengl_context_egl_wayland.h @@ -10,26 +10,32 @@ class OpenGLContextEGLWayland final : public OpenGLContextEGL { public: - OpenGLContextEGLWayland(const WindowInfo& wi); + OpenGLContextEGLWayland(); ~OpenGLContextEGLWayland() override; - static std::unique_ptr Create(const WindowInfo& wi, std::span versions_to_try, - Error* error); + static std::unique_ptr Create(WindowInfo& wi, SurfaceHandle* surface, + std::span versions_to_try, Error* error); - std::unique_ptr CreateSharedContext(const WindowInfo& wi, Error* error) override; - void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + std::unique_ptr CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override; + + void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override; protected: - EGLDisplay GetPlatformDisplay(Error* error) override; - EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error) override; + EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error) override; + EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) override; + void DestroyPlatformSurface(EGLSurface surface) override; private: - bool LoadModule(Error* error); + // Truely awful, I hate this so much, and all this work for a bloody windowing system where + // we can't even position windows to begin with, which makes multi-window kinda pointless... + using WLWindowMap = std::unordered_map; - wl_egl_window* m_wl_window = nullptr; + bool LoadModule(Error* error); void* m_wl_module = nullptr; wl_egl_window* (*m_wl_egl_window_create)(struct wl_surface* surface, int width, int height); void (*m_wl_egl_window_destroy)(struct wl_egl_window* egl_window); void (*m_wl_egl_window_resize)(struct wl_egl_window* egl_window, int width, int height, int dx, int dy); + + WLWindowMap m_wl_window_map; }; diff --git a/src/util/opengl_context_egl_x11.cpp b/src/util/opengl_context_egl_x11.cpp index a80dd4aa6..bc4b53108 100644 --- a/src/util/opengl_context_egl_x11.cpp +++ b/src/util/opengl_context_egl_x11.cpp @@ -5,49 +5,49 @@ #include "common/error.h" -OpenGLContextEGLX11::OpenGLContextEGLX11(const WindowInfo& wi) : OpenGLContextEGL(wi) -{ -} +OpenGLContextEGLX11::OpenGLContextEGLX11() = default; OpenGLContextEGLX11::~OpenGLContextEGLX11() = default; -std::unique_ptr OpenGLContextEGLX11::Create(const WindowInfo& wi, +std::unique_ptr OpenGLContextEGLX11::Create(WindowInfo& wi, SurfaceHandle* surface, std::span versions_to_try, Error* error) { - std::unique_ptr context = std::make_unique(wi); - if (!context->Initialize(versions_to_try, error)) + std::unique_ptr context = std::make_unique(); + if (!context->Initialize(wi, surface, versions_to_try, error)) return nullptr; return context; } -std::unique_ptr OpenGLContextEGLX11::CreateSharedContext(const WindowInfo& wi, Error* error) +std::unique_ptr OpenGLContextEGLX11::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, + Error* error) { - std::unique_ptr context = std::make_unique(wi); + std::unique_ptr context = std::make_unique(); context->m_display = m_display; - if (!context->CreateContextAndSurface(m_version, m_context, false)) + if (!context->CreateContextAndSurface(wi, surface, m_version, m_context, false, error)) return nullptr; return context; } -EGLDisplay OpenGLContextEGLX11::GetPlatformDisplay(Error* error) +EGLDisplay OpenGLContextEGLX11::GetPlatformDisplay(const WindowInfo& wi, Error* error) { - EGLDisplay dpy = TryGetPlatformDisplay(EGL_PLATFORM_X11_KHR, "EGL_EXT_platform_x11"); + EGLDisplay dpy = TryGetPlatformDisplay(wi.display_connection, EGL_PLATFORM_X11_KHR, "EGL_EXT_platform_x11"); if (dpy == EGL_NO_DISPLAY) - dpy = GetFallbackDisplay(error); + dpy = GetFallbackDisplay(wi.display_connection, error); return dpy; } -EGLSurface OpenGLContextEGLX11::CreatePlatformSurface(EGLConfig config, void* win, Error* error) +EGLSurface OpenGLContextEGLX11::CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) { // This is hideous.. the EXT version requires a pointer to the window, whereas the base // version requires the window itself, casted to void*... + void* win = wi.window_handle; EGLSurface surface = TryCreatePlatformSurface(config, &win, error); if (surface == EGL_NO_SURFACE) - surface = CreateFallbackSurface(config, win, error); + surface = CreateFallbackSurface(config, wi.window_handle, error); return surface; -} \ No newline at end of file +} diff --git a/src/util/opengl_context_egl_x11.h b/src/util/opengl_context_egl_x11.h index 6ca1dd27f..c37044de4 100644 --- a/src/util/opengl_context_egl_x11.h +++ b/src/util/opengl_context_egl_x11.h @@ -8,15 +8,15 @@ class OpenGLContextEGLX11 final : public OpenGLContextEGL { public: - OpenGLContextEGLX11(const WindowInfo& wi); + OpenGLContextEGLX11(); ~OpenGLContextEGLX11() override; - static std::unique_ptr Create(const WindowInfo& wi, std::span versions_to_try, - Error* error); + static std::unique_ptr Create(WindowInfo& wi, SurfaceHandle* surface, + std::span versions_to_try, Error* error); - std::unique_ptr CreateSharedContext(const WindowInfo& wi, Error* error) override; + std::unique_ptr CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override; protected: - EGLDisplay GetPlatformDisplay(Error* error) override; - EGLSurface CreatePlatformSurface(EGLConfig config, void* win, Error* error) override; + EGLDisplay GetPlatformDisplay(const WindowInfo& wi, Error* error) override; + EGLSurface CreatePlatformSurface(EGLConfig config, const WindowInfo& wi, Error* error) override; }; diff --git a/src/util/opengl_context_wgl.cpp b/src/util/opengl_context_wgl.cpp index 5c7035cca..22eba28d5 100644 --- a/src/util/opengl_context_wgl.cpp +++ b/src/util/opengl_context_wgl.cpp @@ -5,77 +5,134 @@ #include "opengl_loader.h" #include "common/assert.h" +#include "common/dynamic_library.h" #include "common/error.h" #include "common/log.h" #include "common/scoped_guard.h" -LOG_CHANNEL(GL::OpenGLContext); +LOG_CHANNEL(GPUDevice); #ifdef __clang__ #pragma clang diagnostic ignored "-Wmicrosoft-cast" #endif -static void* GetProcAddressCallback(const char* name) +namespace dyn_libs { + +#define OPENGL_FUNCTIONS(X) \ + X(wglCreateContext) \ + X(wglDeleteContext) \ + X(wglGetCurrentContext) \ + X(wglGetCurrentDC) \ + X(wglGetProcAddress) \ + X(wglMakeCurrent) \ + X(wglShareLists) + +static bool LoadOpenGLLibrary(Error* error); +static void CloseOpenGLLibrary(); +static void* GetProcAddressCallback(const char* name); + +static DynamicLibrary s_opengl_library; + +#define DECLARE_OPENGL_FUNCTION(F) static decltype(&::F) F; +OPENGL_FUNCTIONS(DECLARE_OPENGL_FUNCTION) +#undef DECLARE_OPENGL_FUNCTION +} // namespace dyn_libs + +bool dyn_libs::LoadOpenGLLibrary(Error* error) { - void* addr = wglGetProcAddress(name); + if (s_opengl_library.IsOpen()) + return true; + else if (!s_opengl_library.Open("opengl32.dll", error)) + return false; + + bool result = true; +#define RESOLVE_OPENGL_FUNCTION(F) result = result && s_opengl_library.GetSymbol(#F, &F); + OPENGL_FUNCTIONS(RESOLVE_OPENGL_FUNCTION); +#undef RESOLVE_OPENGL_FUNCTION + + if (!result) + { + CloseOpenGLLibrary(); + Error::SetStringView(error, "One or more required functions from opengl32.dll is missing."); + return false; + } + + std::atexit(&CloseOpenGLLibrary); + return true; +} + +void dyn_libs::CloseOpenGLLibrary() +{ +#define CLOSE_OPENGL_FUNCTION(F) F = nullptr; + OPENGL_FUNCTIONS(CLOSE_OPENGL_FUNCTION); +#undef CLOSE_OPENGL_FUNCTION + + s_opengl_library.Close(); +} + +#undef OPENGL_FUNCTIONS + +void* dyn_libs::GetProcAddressCallback(const char* name) +{ + void* addr = dyn_libs::wglGetProcAddress(name); if (addr) return addr; // try opengl32.dll - return ::GetProcAddress(GetModuleHandleA("opengl32.dll"), name); + return s_opengl_library.GetSymbolAddress(name); } -static bool ReloadWGL(HDC dc) +static bool ReloadWGL(HDC dc, Error* error) { - if (!gladLoadWGL(dc, [](const char* name) { return (GLADapiproc)wglGetProcAddress(name); })) + if (!gladLoadWGL(dc, [](const char* name) { return (GLADapiproc)dyn_libs::wglGetProcAddress(name); })) { - ERROR_LOG("Loading GLAD WGL functions failed"); + Error::SetStringView(error, "Loading GLAD WGL functions failed"); return false; } return true; } -OpenGLContextWGL::OpenGLContextWGL(const WindowInfo& wi) : OpenGLContext(wi) -{ -} +OpenGLContextWGL::OpenGLContextWGL() = default; OpenGLContextWGL::~OpenGLContextWGL() { - if (wglGetCurrentContext() == m_rc) - wglMakeCurrent(m_dc, nullptr); - if (m_rc) - wglDeleteContext(m_rc); + { + if (dyn_libs::wglGetCurrentContext() == m_rc) + dyn_libs::wglMakeCurrent(nullptr, nullptr); - ReleaseDC(); + dyn_libs::wglDeleteContext(m_rc); + } + + if (m_pbuffer) + { + wglReleasePbufferDCARB(m_pbuffer, m_pbuffer_dc); + wglDestroyPbufferARB(m_pbuffer); + DeleteDC(m_dummy_dc); + DestroyWindow(m_dummy_window); + } } -std::unique_ptr OpenGLContextWGL::Create(const WindowInfo& wi, std::span versions_to_try, - Error* error) +std::unique_ptr OpenGLContextWGL::Create(WindowInfo& wi, SurfaceHandle* surface, + std::span versions_to_try, Error* error) { - std::unique_ptr context = std::make_unique(wi); - if (!context->Initialize(versions_to_try, error)) - return nullptr; + std::unique_ptr context = std::make_unique(); + if (!dyn_libs::LoadOpenGLLibrary(error) || !context->Initialize(wi, surface, versions_to_try, error)) + context.reset(); return context; } -bool OpenGLContextWGL::Initialize(std::span versions_to_try, Error* error) +bool OpenGLContextWGL::Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span versions_to_try, + Error* error) { - if (m_wi.type == WindowInfo::Type::Win32) - { - if (!InitializeDC(error)) - return false; - } - else - { - if (!CreatePBuffer(error)) - return false; - } + const HDC hdc = wi.IsSurfaceless() ? GetPBufferDC(error) : CreateDCAndSetPixelFormat(wi, error); + if (!hdc) + return false; // Everything including core/ES requires a dummy profile to load the WGL extensions. - if (!CreateAnyContext(nullptr, true, error)) + if (!CreateAnyContext(hdc, nullptr, true, error)) return false; for (const Version& cv : versions_to_try) @@ -84,11 +141,13 @@ bool OpenGLContextWGL::Initialize(std::span versions_to_try, Erro { // we already have the dummy context, so just use that m_version = cv; + *surface = hdc; return true; } - else if (CreateVersionContext(cv, nullptr, true, error)) + else if (CreateVersionContext(cv, hdc, nullptr, true, error)) { m_version = cv; + *surface = hdc; return true; } } @@ -99,65 +158,76 @@ bool OpenGLContextWGL::Initialize(std::span versions_to_try, Erro void* OpenGLContextWGL::GetProcAddress(const char* name) { - return GetProcAddressCallback(name); + return dyn_libs::GetProcAddressCallback(name); } -bool OpenGLContextWGL::ChangeSurface(const WindowInfo& new_wi) +OpenGLContext::SurfaceHandle OpenGLContextWGL::CreateSurface(WindowInfo& wi, Error* error /*= nullptr*/) { - const bool was_current = (wglGetCurrentContext() == m_rc); - Error error; - - ReleaseDC(); - - m_wi = new_wi; - if (!InitializeDC(&error)) + if (wi.IsSurfaceless()) [[unlikely]] { - ERROR_LOG("Failed to change surface: {}", error.GetDescription()); - return false; + Error::SetStringView(error, "Trying to create a surfaceless surface."); + return nullptr; } - if (was_current && !wglMakeCurrent(m_dc, m_rc)) - { - error.SetWin32(GetLastError()); - ERROR_LOG("Failed to make context current again after surface change: {}", error.GetDescription()); - return false; - } - - return true; + return CreateDCAndSetPixelFormat(wi, error); } -void OpenGLContextWGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surface_height /*= 0*/) +void OpenGLContextWGL::DestroySurface(SurfaceHandle handle) +{ + // pbuffer/surfaceless? + if (!handle) + return; + + // current buffer? switch to pbuffer first + if (dyn_libs::wglGetCurrentDC() == static_cast(handle)) + MakeCurrent(nullptr); + + DeleteDC(static_cast(handle)); +} + +void OpenGLContextWGL::ResizeSurface(WindowInfo& wi, SurfaceHandle handle) { RECT client_rc = {}; - GetClientRect(GetHWND(), &client_rc); - m_wi.surface_width = static_cast(client_rc.right - client_rc.left); - m_wi.surface_height = static_cast(client_rc.bottom - client_rc.top); + GetClientRect(static_cast(wi.window_handle), &client_rc); + wi.surface_width = static_cast(client_rc.right - client_rc.left); + wi.surface_height = static_cast(client_rc.bottom - client_rc.top); } bool OpenGLContextWGL::SwapBuffers() { - return ::SwapBuffers(m_dc); + return ::SwapBuffers(m_current_dc); } bool OpenGLContextWGL::IsCurrent() const { - return (m_rc && wglGetCurrentContext() == m_rc); + return (m_rc && dyn_libs::wglGetCurrentContext() == m_rc); } -bool OpenGLContextWGL::MakeCurrent() +bool OpenGLContextWGL::MakeCurrent(SurfaceHandle surface, Error* error /* = nullptr */) { - if (!wglMakeCurrent(m_dc, m_rc)) + const HDC new_dc = surface ? static_cast(surface) : GetPBufferDC(error); + if (!new_dc) + return false; + else if (m_current_dc == new_dc) + return true; + + if (!dyn_libs::wglMakeCurrent(new_dc, m_rc)) { ERROR_LOG("wglMakeCurrent() failed: {}", GetLastError()); return false; } + m_current_dc = new_dc; return true; } bool OpenGLContextWGL::DoneCurrent() { - return wglMakeCurrent(m_dc, nullptr); + if (!dyn_libs::wglMakeCurrent(m_current_dc, nullptr)) + return false; + + m_current_dc = nullptr; + return true; } bool OpenGLContextWGL::SupportsNegativeSwapInterval() const @@ -165,45 +235,55 @@ bool OpenGLContextWGL::SupportsNegativeSwapInterval() const return GLAD_WGL_EXT_swap_control && GLAD_WGL_EXT_swap_control_tear; } -bool OpenGLContextWGL::SetSwapInterval(s32 interval) +bool OpenGLContextWGL::SetSwapInterval(s32 interval, Error* error) { if (!GLAD_WGL_EXT_swap_control) + { + Error::SetStringView(error, "WGL_EXT_swap_control is not supported."); return false; + } - return wglSwapIntervalEXT(interval); + if (!wglSwapIntervalEXT(interval)) + { + Error::SetWin32(error, "wglSwapIntervalEXT() failed: ", GetLastError()); + return false; + } + + return true; } -std::unique_ptr OpenGLContextWGL::CreateSharedContext(const WindowInfo& wi, Error* error) +std::unique_ptr OpenGLContextWGL::CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, + Error* error) { - std::unique_ptr context = std::make_unique(wi); - if (wi.type == WindowInfo::Type::Win32) - { - if (!context->InitializeDC(error)) - return nullptr; - } - else - { - if (!context->CreatePBuffer(error)) - return nullptr; - } + std::unique_ptr context = std::make_unique(); + const HDC hdc = wi.IsSurfaceless() ? context->GetPBufferDC(error) : context->CreateDCAndSetPixelFormat(wi, error); + if (!hdc) + return nullptr; if (m_version.profile == Profile::NoProfile) { - if (!context->CreateAnyContext(m_rc, false, error)) + if (!context->CreateAnyContext(hdc, m_rc, false, error)) return nullptr; } else { - if (!context->CreateVersionContext(m_version, m_rc, false, error)) + if (!context->CreateVersionContext(m_version, hdc, m_rc, false, error)) return nullptr; } context->m_version = m_version; + *surface = wi.IsSurfaceless() ? hdc : nullptr; return context; } -HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error) +HDC OpenGLContextWGL::CreateDCAndSetPixelFormat(WindowInfo& wi, Error* error) { + if (wi.type != WindowInfo::Type::Win32) + { + Error::SetStringFmt(error, "Unknown window info type {}", static_cast(wi.type)); + return NULL; + } + PIXELFORMATDESCRIPTOR pfd = {}; pfd.nSize = sizeof(pfd); pfd.nVersion = 1; @@ -215,6 +295,7 @@ HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error) pfd.cBlueBits = 8; pfd.cColorBits = 24; + const HWND hwnd = static_cast(wi.window_handle); HDC hDC = ::GetDC(hwnd); if (!hDC) { @@ -228,7 +309,7 @@ HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error) if (pf == 0) { Error::SetWin32(error, "ChoosePixelFormat() failed: ", GetLastError()); - ::ReleaseDC(hwnd, hDC); + DeleteDC(hDC); return {}; } @@ -238,63 +319,22 @@ HDC OpenGLContextWGL::GetDCAndSetPixelFormat(HWND hwnd, Error* error) if (!SetPixelFormat(hDC, m_pixel_format.value(), &pfd)) { Error::SetWin32(error, "SetPixelFormat() failed: ", GetLastError()); - ::ReleaseDC(hwnd, hDC); + DeleteDC(hDC); return {}; } - m_wi.surface_format = GPUTexture::Format::RGBA8; + wi.surface_format = GPUTexture::Format::RGBA8; return hDC; } -bool OpenGLContextWGL::InitializeDC(Error* error) -{ - if (m_wi.type == WindowInfo::Type::Win32) - { - m_dc = GetDCAndSetPixelFormat(GetHWND(), error); - if (!m_dc) - return false; - - return true; - } - else if (m_wi.type == WindowInfo::Type::Surfaceless) - { - return CreatePBuffer(error); - } - else - { - Error::SetStringFmt(error, "Unknown window info type {}", static_cast(m_wi.type)); - return false; - } -} - -void OpenGLContextWGL::ReleaseDC() -{ - if (m_pbuffer) - { - wglReleasePbufferDCARB(m_pbuffer, m_dc); - m_dc = {}; - - wglDestroyPbufferARB(m_pbuffer); - m_pbuffer = {}; - - ::ReleaseDC(m_dummy_window, m_dummy_dc); - m_dummy_dc = {}; - - DestroyWindow(m_dummy_window); - m_dummy_window = {}; - } - else if (m_dc) - { - ::ReleaseDC(GetHWND(), m_dc); - m_dc = {}; - } -} - -bool OpenGLContextWGL::CreatePBuffer(Error* error) +HDC OpenGLContextWGL::GetPBufferDC(Error* error) { static bool window_class_registered = false; static const wchar_t* window_class_name = L"ContextWGLPBuffer"; + if (m_pbuffer_dc) + return m_pbuffer_dc; + if (!window_class_registered) { WNDCLASSEXW wc = {}; @@ -314,24 +354,28 @@ bool OpenGLContextWGL::CreatePBuffer(Error* error) if (!RegisterClassExW(&wc)) { Error::SetStringView(error, "(ContextWGL::CreatePBuffer) RegisterClassExW() failed"); - return false; + return NULL; } window_class_registered = true; } + Assert(m_dummy_window == NULL); + Assert(m_pbuffer == NULL); + HWND hwnd = CreateWindowExW(0, window_class_name, window_class_name, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); if (!hwnd) { Error::SetStringView(error, "(ContextWGL::CreatePBuffer) CreateWindowEx() failed"); - return false; + return NULL; } ScopedGuard hwnd_guard([hwnd]() { DestroyWindow(hwnd); }); - HDC hdc = GetDCAndSetPixelFormat(hwnd, error); + WindowInfo wi = {.type = WindowInfo::Type::Win32, .window_handle = hwnd}; + HDC hdc = CreateDCAndSetPixelFormat(wi, error); if (!hdc) - return false; + return NULL; ScopedGuard hdc_guard([hdc, hwnd]() { ::ReleaseDC(hwnd, hdc); }); @@ -341,25 +385,28 @@ bool OpenGLContextWGL::CreatePBuffer(Error* error) ScopedGuard temp_rc_guard([&temp_rc, hdc]() { if (temp_rc) { - wglMakeCurrent(hdc, nullptr); - wglDeleteContext(temp_rc); + dyn_libs::wglMakeCurrent(hdc, nullptr); + dyn_libs::wglDeleteContext(temp_rc); } }); if (!GLAD_WGL_ARB_pbuffer) { // we're probably running completely surfaceless... need a temporary context. - temp_rc = wglCreateContext(hdc); - if (!temp_rc || !wglMakeCurrent(hdc, temp_rc)) + temp_rc = dyn_libs::wglCreateContext(hdc); + if (!temp_rc || !dyn_libs::wglMakeCurrent(hdc, temp_rc)) { Error::SetStringView(error, "Failed to create temporary context to load WGL for pbuffer."); - return false; + return NULL; } - if (!ReloadWGL(hdc) || !GLAD_WGL_ARB_pbuffer) + if (!ReloadWGL(hdc, error)) + return NULL; + + if (!GLAD_WGL_ARB_pbuffer) { Error::SetStringView(error, "Missing WGL_ARB_pbuffer"); - return false; + return NULL; } } @@ -368,32 +415,33 @@ bool OpenGLContextWGL::CreatePBuffer(Error* error) if (!pbuffer) { Error::SetStringView(error, "(ContextWGL::CreatePBuffer) wglCreatePbufferARB() failed"); - return false; + return NULL; } ScopedGuard pbuffer_guard([pbuffer]() { wglDestroyPbufferARB(pbuffer); }); - m_dc = wglGetPbufferDCARB(pbuffer); - if (!m_dc) + HDC dc = wglGetPbufferDCARB(pbuffer); + if (!dc) { Error::SetStringView(error, "(ContextWGL::CreatePbuffer) wglGetPbufferDCARB() failed"); - return false; + return NULL; } m_dummy_window = hwnd; m_dummy_dc = hdc; m_pbuffer = pbuffer; + m_pbuffer_dc = dc; temp_rc_guard.Run(); pbuffer_guard.Cancel(); hdc_guard.Cancel(); hwnd_guard.Cancel(); - return true; + return dc; } -bool OpenGLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current, Error* error) +bool OpenGLContextWGL::CreateAnyContext(HDC hdc, HGLRC share_context, bool make_current, Error* error) { - m_rc = wglCreateContext(m_dc); + m_rc = dyn_libs::wglCreateContext(hdc); if (!m_rc) { Error::SetWin32(error, "wglCreateContext() failed: ", GetLastError()); @@ -402,21 +450,23 @@ bool OpenGLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current, if (make_current) { - if (!wglMakeCurrent(m_dc, m_rc)) + if (!dyn_libs::wglMakeCurrent(hdc, m_rc)) { Error::SetWin32(error, "wglMakeCurrent() failed: ", GetLastError()); return false; } + m_current_dc = hdc; + // re-init glad-wgl - if (!gladLoadWGL(m_dc, [](const char* name) { return (GLADapiproc)wglGetProcAddress(name); })) + if (!ReloadWGL(m_current_dc, error)) { Error::SetStringView(error, "Loading GLAD WGL functions failed"); return false; } } - if (share_context && !wglShareLists(share_context, m_rc)) + if (share_context && !dyn_libs::wglShareLists(share_context, m_rc)) { Error::SetWin32(error, "wglShareLists() failed: ", GetLastError()); return false; @@ -425,7 +475,7 @@ bool OpenGLContextWGL::CreateAnyContext(HGLRC share_context, bool make_current, return true; } -bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_context, bool make_current, +bool OpenGLContextWGL::CreateVersionContext(const Version& version, HDC hdc, HGLRC share_context, bool make_current, Error* error) { // we need create context attribs @@ -454,7 +504,7 @@ bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_ 0, 0}; - new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs); + new_rc = wglCreateContextAttribsARB(hdc, share_context, attribs); } else if (version.profile == Profile::ES) { @@ -475,7 +525,7 @@ bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_ 0, 0}; - new_rc = wglCreateContextAttribsARB(m_dc, share_context, attribs); + new_rc = wglCreateContextAttribsARB(hdc, share_context, attribs); } else { @@ -489,18 +539,20 @@ bool OpenGLContextWGL::CreateVersionContext(const Version& version, HGLRC share_ // destroy and swap contexts if (m_rc) { - if (!wglMakeCurrent(m_dc, make_current ? new_rc : nullptr)) + if (!dyn_libs::wglMakeCurrent(hdc, make_current ? new_rc : nullptr)) { Error::SetWin32(error, "wglMakeCurrent() failed: ", GetLastError()); - wglDeleteContext(new_rc); + dyn_libs::wglDeleteContext(new_rc); return false; } + m_current_dc = hdc; + // re-init glad-wgl - if (make_current && !ReloadWGL(m_dc)) + if (make_current && !ReloadWGL(hdc, error)) return false; - wglDeleteContext(m_rc); + dyn_libs::wglDeleteContext(m_rc); } m_rc = new_rc; diff --git a/src/util/opengl_context_wgl.h b/src/util/opengl_context_wgl.h index 53ac5bddf..a6a38b463 100644 --- a/src/util/opengl_context_wgl.h +++ b/src/util/opengl_context_wgl.h @@ -15,36 +15,34 @@ class OpenGLContextWGL final : public OpenGLContext { public: - OpenGLContextWGL(const WindowInfo& wi); + OpenGLContextWGL(); ~OpenGLContextWGL() override; - static std::unique_ptr Create(const WindowInfo& wi, std::span versions_to_try, - Error* error); + static std::unique_ptr Create(WindowInfo& wi, SurfaceHandle* surface, + std::span versions_to_try, Error* error); void* GetProcAddress(const char* name) override; - bool ChangeSurface(const WindowInfo& new_wi) override; - void ResizeSurface(u32 new_surface_width = 0, u32 new_surface_height = 0) override; + SurfaceHandle CreateSurface(WindowInfo& wi, Error* error = nullptr) override; + void DestroySurface(SurfaceHandle handle) override; + void ResizeSurface(WindowInfo& wi, SurfaceHandle handle) override; bool SwapBuffers() override; bool IsCurrent() const override; - bool MakeCurrent() override; + bool MakeCurrent(SurfaceHandle surface, Error* error = nullptr) override; bool DoneCurrent() override; bool SupportsNegativeSwapInterval() const override; - bool SetSwapInterval(s32 interval) override; - std::unique_ptr CreateSharedContext(const WindowInfo& wi, Error* error) override; + bool SetSwapInterval(s32 interval, Error* error = nullptr) override; + std::unique_ptr CreateSharedContext(WindowInfo& wi, SurfaceHandle* surface, Error* error) override; private: - ALWAYS_INLINE HWND GetHWND() const { return static_cast(m_wi.window_handle); } + bool Initialize(WindowInfo& wi, SurfaceHandle* surface, std::span versions_to_try, Error* error); - HDC GetDCAndSetPixelFormat(HWND hwnd, Error* error); + bool CreateAnyContext(HDC hdc, HGLRC share_context, bool make_current, Error* error); + bool CreateVersionContext(const Version& version, HDC hdc, HGLRC share_context, bool make_current, Error* error); - bool Initialize(std::span versions_to_try, Error* error); - bool InitializeDC(Error* error); - void ReleaseDC(); - bool CreatePBuffer(Error* error); - bool CreateAnyContext(HGLRC share_context, bool make_current, Error* error); - bool CreateVersionContext(const Version& version, HGLRC share_context, bool make_current, Error* error); + HDC CreateDCAndSetPixelFormat(WindowInfo& wi, Error* error); + HDC GetPBufferDC(Error* error); - HDC m_dc = {}; + HDC m_current_dc = {}; HGLRC m_rc = {}; // Can't change pixel format once it's set for a RC. @@ -54,4 +52,5 @@ private: HWND m_dummy_window = {}; HDC m_dummy_dc = {}; HPBUFFERARB m_pbuffer = {}; + HDC m_pbuffer_dc = {}; }; diff --git a/src/util/opengl_device.cpp b/src/util/opengl_device.cpp index 767182538..3f69790f0 100644 --- a/src/util/opengl_device.cpp +++ b/src/util/opengl_device.cpp @@ -19,13 +19,16 @@ #include #include -LOG_CHANNEL(OpenGLDevice); +LOG_CHANNEL(GPUDevice); static constexpr const std::array s_draw_buffers = { {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3}}; OpenGLDevice::OpenGLDevice() { + // Could change to GLES later. + m_render_api = RenderAPI::OpenGL; + // Something which won't be matched.. std::memset(&m_last_rasterization_state, 0xFF, sizeof(m_last_rasterization_state)); std::memset(&m_last_depth_state, 0xFF, sizeof(m_last_depth_state)); @@ -238,19 +241,6 @@ void OpenGLDevice::InsertDebugMessage(const char* msg) #endif } -void OpenGLDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) -{ - // OpenGL does not support Mailbox. - mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode; - m_allow_present_throttle = allow_present_throttle; - - if (m_vsync_mode == mode) - return; - - m_vsync_mode = mode; - SetSwapInterval(); -} - static void GLAD_API_PTR GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { @@ -271,15 +261,15 @@ static void GLAD_API_PTR GLDebugCallback(GLenum source, GLenum type, GLuint id, } } -bool OpenGLDevice::HasSurface() const +bool OpenGLDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, + const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) { - return m_window_info.type != WindowInfo::Type::Surfaceless; -} - -bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) -{ - m_gl_context = OpenGLContext::Create(m_window_info, error); + WindowInfo wi_copy(wi); + OpenGLContext::SurfaceHandle wi_surface; + m_gl_context = OpenGLContext::Create(wi_copy, &wi_surface, error); if (!m_gl_context) { ERROR_LOG("Failed to create any GL context"); @@ -287,9 +277,11 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional ex return false; } +#if 0 // Is this needed? m_window_info = m_gl_context->GetWindowInfo(); - m_vsync_mode = (m_vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : m_vsync_mode; + m_vsync_mode = ; +#endif const bool opengl_is_available = ((!m_gl_context->IsGLES() && (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) || @@ -303,10 +295,6 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional ex return false; } - SetSwapInterval(); - if (HasSurface()) - RenderBlankFrame(); - if (m_debug_device && GLAD_GL_KHR_debug) { if (m_gl_context->IsGLES()) @@ -326,6 +314,21 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, std::optional ex glObjectLabel = nullptr; } + // create main swap chain + if (!wi_copy.IsSurfaceless()) + { + // OpenGL does not support mailbox. + m_main_swap_chain = std::make_unique( + wi_copy, (vsync_mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : vsync_mode, allow_present_throttle, + wi_surface); + + Error swap_interval_error; + if (!OpenGLSwapChain::SetSwapInterval(m_gl_context.get(), m_main_swap_chain->GetVSyncMode(), &swap_interval_error)) + WARNING_LOG("Failed to set swap interval on main swap chain: {}", swap_interval_error.GetDescription()); + + RenderBlankFrame(); + } + if (!CheckFeatures(disabled_features)) return false; @@ -532,50 +535,101 @@ void OpenGLDevice::DestroyDevice() DestroyBuffers(); m_gl_context->DoneCurrent(); + m_main_swap_chain.reset(); m_gl_context.reset(); } -bool OpenGLDevice::UpdateWindow() +OpenGLSwapChain::OpenGLSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + OpenGLContext::SurfaceHandle surface_handle) + : GPUSwapChain(wi, vsync_mode, allow_present_throttle), m_surface_handle(surface_handle) { - Assert(m_gl_context); +} - DestroySurface(); +OpenGLSwapChain::~OpenGLSwapChain() +{ + OpenGLDevice::GetContext()->DestroySurface(m_surface_handle); +} - if (!AcquireWindow(false)) - return false; - - if (!m_gl_context->ChangeSurface(m_window_info)) - { - ERROR_LOG("Failed to change surface"); - return false; - } - - m_window_info = m_gl_context->GetWindowInfo(); - - if (m_window_info.type != WindowInfo::Type::Surfaceless) - { - // reset vsync rate, since it (usually) gets lost - SetSwapInterval(); - RenderBlankFrame(); - } +bool OpenGLSwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) +{ + m_window_info.surface_scale = new_scale; + if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height) + return true; + OpenGLDevice::GetContext()->ResizeSurface(m_window_info, m_surface_handle); return true; } -void OpenGLDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) +bool OpenGLSwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) { - if (m_window_info.IsSurfaceless()) - return; + // OpenGL does not support Mailbox. + mode = (mode == GPUVSyncMode::Mailbox) ? GPUVSyncMode::FIFO : mode; + m_allow_present_throttle = allow_present_throttle; - m_window_info.surface_scale = new_window_scale; - if (m_window_info.surface_width == static_cast(new_window_width) && - m_window_info.surface_height == static_cast(new_window_height)) + if (m_vsync_mode == mode) + return true; + + const bool is_main_swap_chain = (g_gpu_device->GetMainSwapChain() == this); + + OpenGLContext* ctx = OpenGLDevice::GetContext(); + if (!is_main_swap_chain && !ctx->MakeCurrent(m_surface_handle)) + return false; + + const bool result = SetSwapInterval(ctx, mode, error); + + if (!is_main_swap_chain) + ctx->MakeCurrent(static_cast(g_gpu_device->GetMainSwapChain())->m_surface_handle); + + if (!result) + return false; + + m_vsync_mode = mode; + return true; +} + +bool OpenGLSwapChain::SetSwapInterval(OpenGLContext* ctx, GPUVSyncMode mode, Error* error) +{ + // Window framebuffer has to be bound to call SetSwapInterval. + const s32 interval = static_cast(mode == GPUVSyncMode::FIFO); + GLint current_fbo = 0; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + const bool result = ctx->SetSwapInterval(interval); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); + return result; +} + +std::unique_ptr OpenGLDevice::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) +{ + if (wi.IsSurfaceless()) { - return; + Error::SetStringView(error, "Trying to create a surfaceless swap chain."); + return {}; } - m_gl_context->ResizeSurface(static_cast(new_window_width), static_cast(new_window_height)); - m_window_info = m_gl_context->GetWindowInfo(); + WindowInfo wi_copy(wi); + const OpenGLContext::SurfaceHandle surface_handle = m_gl_context->CreateSurface(wi_copy, error); + if (!surface_handle || !m_gl_context->MakeCurrent(surface_handle, error)) + return {}; + + Error swap_interval_error; + if (!OpenGLSwapChain::SetSwapInterval(m_gl_context.get(), vsync_mode, &swap_interval_error)) + WARNING_LOG("Failed to set swap interval on new swap chain: {}", swap_interval_error.GetDescription()); + + RenderBlankFrame(); + + // only bother switching back if we actually have a main swap chain, avoids a couple of + // SetCurrent() calls when we're switching to and from fullscreen. + if (m_main_swap_chain) + m_gl_context->MakeCurrent(static_cast(m_main_swap_chain.get())->GetSurfaceHandle()); + + return std::make_unique(wi_copy, vsync_mode, allow_present_throttle, surface_handle); } std::string OpenGLDevice::GetDriverInfo() const @@ -588,29 +642,18 @@ std::string OpenGLDevice::GetDriverInfo() const gl_shading_language_version); } -void OpenGLDevice::ExecuteAndWaitForGPUIdle() +void OpenGLDevice::FlushCommands() +{ + glFlush(); + TrimTexturePool(); +} + +void OpenGLDevice::WaitForGPUIdle() { // Could be glFinish(), but I'm afraid for mobile drivers... glFlush(); } -void OpenGLDevice::SetSwapInterval() -{ - if (m_window_info.type == WindowInfo::Type::Surfaceless) - return; - - // Window framebuffer has to be bound to call SetSwapInterval. - const s32 interval = static_cast(m_vsync_mode == GPUVSyncMode::FIFO); - GLint current_fbo = 0; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - - if (!m_gl_context->SetSwapInterval(interval)) - WARNING_LOG("Failed to set swap interval to {}", interval); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); -} - void OpenGLDevice::RenderBlankFrame() { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); @@ -675,16 +718,6 @@ void OpenGLDevice::DestroyFramebuffer(GLuint fbo) glDeleteFramebuffers(1, &fbo); } -void OpenGLDevice::DestroySurface() -{ - if (!m_gl_context) - return; - - m_window_info.SetSurfaceless(); - if (!m_gl_context->ChangeSurface(m_window_info)) - ERROR_LOG("Failed to switch to surfaceless"); -} - bool OpenGLDevice::CreateBuffers() { if (!(m_vertex_buffer = OpenGLStreamBuffer::Create(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE)) || @@ -742,14 +775,9 @@ void OpenGLDevice::DestroyBuffers() m_vertex_buffer.reset(); } -GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color) +GPUDevice::PresentResult OpenGLDevice::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) { - if (m_window_info.type == WindowInfo::Type::Surfaceless) - { - glFlush(); - TrimTexturePool(); - return PresentResult::SkipPresent; - } + m_gl_context->MakeCurrent(static_cast(swap_chain)->GetSurfaceHandle()); glBindFramebuffer(GL_FRAMEBUFFER, 0); glDisable(GL_SCISSOR_TEST); @@ -764,7 +792,8 @@ GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color) std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets)); m_current_depth_target = nullptr; - const GSVector4i window_rc = GSVector4i(0, 0, m_window_info.surface_width, m_window_info.surface_height); + const GSVector4i window_rc = + GSVector4i(0, 0, static_cast(swap_chain->GetWidth()), static_cast(swap_chain->GetHeight())); m_last_viewport = window_rc; m_last_scissor = window_rc; UpdateViewport(); @@ -772,23 +801,23 @@ GPUDevice::PresentResult OpenGLDevice::BeginPresent(u32 clear_color) return PresentResult::OK; } -void OpenGLDevice::EndPresent(bool explicit_present, u64 present_time) +void OpenGLDevice::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) { DebugAssert(!explicit_present && present_time == 0); DebugAssert(m_current_fbo == 0); - if (m_gpu_timing_enabled) + if (swap_chain == m_main_swap_chain.get() && m_gpu_timing_enabled) PopTimestampQuery(); m_gl_context->SwapBuffers(); - if (m_gpu_timing_enabled) + if (swap_chain == m_main_swap_chain.get() && m_gpu_timing_enabled) KickTimestampQuery(); TrimTexturePool(); } -void OpenGLDevice::SubmitPresent() +void OpenGLDevice::SubmitPresent(GPUSwapChain* swap_chain) { Panic("Not supported by this API."); } diff --git a/src/util/opengl_device.h b/src/util/opengl_device.h index 616e55a03..e499c5931 100644 --- a/src/util/opengl_device.h +++ b/src/util/opengl_device.h @@ -36,20 +36,21 @@ public: return GetInstance().m_texture_stream_buffer.get(); } ALWAYS_INLINE static bool IsGLES() { return GetInstance().m_gl_context->IsGLES(); } + ALWAYS_INLINE static OpenGLContext* GetContext() { return GetInstance().m_gl_context.get(); } static void BindUpdateTextureUnit(); static bool ShouldUsePBOsForDownloads(); static void SetErrorObject(Error* errptr, std::string_view prefix, GLenum glerr); - bool HasSurface() const override; - void DestroySurface() override; - - bool UpdateWindow() override; - void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; - std::string GetDriverInfo() const override; - void ExecuteAndWaitForGPUIdle() override; + void FlushCommands() override; + void WaitForGPUIdle() override; + std::unique_ptr CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) override; std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, GPUTexture::Format format, const void* data = nullptr, u32 data_stride = 0) override; @@ -100,11 +101,9 @@ public: void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override; void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override; - void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; - - PresentResult BeginPresent(u32 clear_color) override; - void EndPresent(bool explicit_present, u64 present_time) override; - void SubmitPresent() override; + PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override; + void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override; + void SubmitPresent(GPUSwapChain* swap_chain) override; bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; @@ -131,9 +130,13 @@ public: void UnbindSampler(GLuint id); void UnbindPipeline(const OpenGLPipeline* pl); + void RenderBlankFrame(); + protected: - bool CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) override; + bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi, + GPUVSyncMode vsync_mode, bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) override; void DestroyDevice() override; bool OpenPipelineCache(const std::string& path, Error* error) override; @@ -154,9 +157,6 @@ private: bool CreateBuffers(); void DestroyBuffers(); - void SetSwapInterval(); - void RenderBlankFrame(); - s32 IsRenderTargetBound(const GPUTexture* tex) const; static GLuint CreateFramebuffer(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds, u32 flags); static void DestroyFramebuffer(GLuint fbo); @@ -230,3 +230,21 @@ private: bool m_disable_pbo = false; bool m_disable_async_download = false; }; + +class OpenGLSwapChain : public GPUSwapChain +{ +public: + OpenGLSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + OpenGLContext::SurfaceHandle surface_handle); + ~OpenGLSwapChain() override; + + ALWAYS_INLINE OpenGLContext::SurfaceHandle GetSurfaceHandle() const { return m_surface_handle; } + + bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override; + bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override; + + static bool SetSwapInterval(OpenGLContext* ctx, GPUVSyncMode mode, Error* error); + +private: + OpenGLContext::SurfaceHandle m_surface_handle; +}; diff --git a/src/util/opengl_pipeline.cpp b/src/util/opengl_pipeline.cpp index e84ae59eb..b112c70d6 100644 --- a/src/util/opengl_pipeline.cpp +++ b/src/util/opengl_pipeline.cpp @@ -21,7 +21,7 @@ #include -LOG_CHANNEL(OpenGLDevice); +LOG_CHANNEL(GPUDevice); struct PipelineDiskCacheFooter { diff --git a/src/util/opengl_texture.cpp b/src/util/opengl_texture.cpp index 2007303cf..72c9f3130 100644 --- a/src/util/opengl_texture.cpp +++ b/src/util/opengl_texture.cpp @@ -15,7 +15,7 @@ #include #include -LOG_CHANNEL(OpenGLDevice); +LOG_CHANNEL(GPUDevice); // Looking across a range of GPUs, the optimal copy alignment for Vulkan drivers seems // to be between 1 (AMD/NV) and 64 (Intel). So, we'll go with 64 here. diff --git a/src/util/platform_misc_mac.mm b/src/util/platform_misc_mac.mm index 019f23930..43da74413 100644 --- a/src/util/platform_misc_mac.mm +++ b/src/util/platform_misc_mac.mm @@ -13,6 +13,7 @@ #include "platform_misc.h" #include "window_info.h" +#include "common/error.h" #include "common/log.h" #include "common/small_string.h" @@ -87,49 +88,45 @@ bool PlatformMisc::PlaySoundAsync(const char* path) return result; } -bool CocoaTools::CreateMetalLayer(WindowInfo* wi) +void* CocoaTools::CreateMetalLayer(const WindowInfo& wi, Error* error) { // Punt off to main thread if we're not calling from it already. if (![NSThread isMainThread]) { - bool ret; - dispatch_sync(dispatch_get_main_queue(), [&ret, wi]() { ret = CreateMetalLayer(wi); }); + void* ret; + dispatch_sync(dispatch_get_main_queue(), [&ret, &wi, error]() { ret = CreateMetalLayer(wi, error); }); return ret; } CAMetalLayer* layer = [CAMetalLayer layer]; if (layer == nil) { - ERROR_LOG("Failed to create CAMetalLayer"); - return false; + Error::SetStringView(error, "Failed to create CAMetalLayer"); + return nullptr; } - NSView* view = (__bridge NSView*)wi->window_handle; + NSView* view = (__bridge NSView*)wi.window_handle; [view setWantsLayer:TRUE]; [view setLayer:layer]; [layer setContentsScale:[[[view window] screen] backingScaleFactor]]; - wi->surface_handle = (__bridge void*)layer; - return true; + return (__bridge void*)layer; } -void CocoaTools::DestroyMetalLayer(WindowInfo* wi) +void CocoaTools::DestroyMetalLayer(const WindowInfo& wi, void* layer) { - if (!wi->surface_handle) - return; - // Punt off to main thread if we're not calling from it already. if (![NSThread isMainThread]) { - dispatch_sync(dispatch_get_main_queue(), [wi]() { DestroyMetalLayer(wi); }); + dispatch_sync(dispatch_get_main_queue(), [&wi, layer]() { DestroyMetalLayer(wi, layer); }); return; } - NSView* view = (__bridge NSView*)wi->window_handle; - CAMetalLayer* layer = (__bridge CAMetalLayer*)wi->surface_handle; + NSView* view = (__bridge NSView*)wi.window_handle; + CAMetalLayer* clayer = (__bridge CAMetalLayer*)layer; [view setLayer:nil]; [view setWantsLayer:NO]; - [layer release]; + [clayer release]; } std::optional CocoaTools::GetViewRefreshRate(const WindowInfo& wi) @@ -137,7 +134,7 @@ std::optional CocoaTools::GetViewRefreshRate(const WindowInfo& wi) if (![NSThread isMainThread]) { std::optional ret; - dispatch_sync(dispatch_get_main_queue(), [&ret, wi]{ ret = GetViewRefreshRate(wi); }); + dispatch_sync(dispatch_get_main_queue(), [&ret, wi] { ret = GetViewRefreshRate(wi); }); return ret; } diff --git a/src/util/postprocessing.cpp b/src/util/postprocessing.cpp index 082f10664..dda931162 100644 --- a/src/util/postprocessing.cpp +++ b/src/util/postprocessing.cpp @@ -437,10 +437,10 @@ void PostProcessing::Chain::LoadStages() DEV_LOG("Loaded {} post-processing stages.", stage_count); // precompile shaders - if (!IsInternalChain() && g_gpu_device && g_gpu_device->GetWindowFormat() != GPUTexture::Format::Unknown) + if (!IsInternalChain() && g_gpu_device && g_gpu_device->HasMainSwapChain()) { - CheckTargets(g_gpu_device->GetWindowFormat(), g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), - &progress); + CheckTargets(g_gpu_device->GetMainSwapChain()->GetFormat(), g_gpu_device->GetMainSwapChain()->GetWidth(), + g_gpu_device->GetMainSwapChain()->GetHeight(), &progress); } // must be down here, because we need to compile first, triggered by CheckTargets() diff --git a/src/util/postprocessing_shader_fx.cpp b/src/util/postprocessing_shader_fx.cpp index faddafab8..9f1ba1528 100644 --- a/src/util/postprocessing_shader_fx.cpp +++ b/src/util/postprocessing_shader_fx.cpp @@ -333,9 +333,9 @@ bool PostProcessing::ReShadeFXShader::LoadFromString(std::string name, std::stri // TODO: This could use spv, it's probably fastest. const auto& [cg, cg_language] = CreateRFXCodegen(); - if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetWindowWidth(), - only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetWindowHeight(), cg.get(), std::move(code), - error)) + if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetMainSwapChain()->GetWidth(), + only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetMainSwapChain()->GetHeight(), cg.get(), + std::move(code), error)) { return false; } @@ -1762,7 +1762,8 @@ GPUDevice::PresentResult PostProcessing::ReShadeFXShader::Apply(GPUTexture* inpu if (pass.render_targets.size() == 1 && pass.render_targets[0] == OUTPUT_COLOR_TEXTURE && !final_target) { // Special case: drawing to final buffer. - if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(); pres != GPUDevice::PresentResult::OK) + const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain()); + if (pres != GPUDevice::PresentResult::OK) { GL_POP(); return pres; diff --git a/src/util/postprocessing_shader_glsl.cpp b/src/util/postprocessing_shader_glsl.cpp index ad344df35..95ecb5423 100644 --- a/src/util/postprocessing_shader_glsl.cpp +++ b/src/util/postprocessing_shader_glsl.cpp @@ -177,7 +177,8 @@ GPUDevice::PresentResult PostProcessing::GLSLShader::Apply(GPUTexture* input_col // Assumes final stage has been cleared already. if (!final_target) { - if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(); pres != GPUDevice::PresentResult::OK) + const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(g_gpu_device->GetMainSwapChain()); + if (pres != GPUDevice::PresentResult::OK) return pres; } else diff --git a/src/util/util.props b/src/util/util.props index eb7c15cd9..e984ee4e4 100644 --- a/src/util/util.props +++ b/src/util/util.props @@ -4,7 +4,7 @@ - %(PreprocessorDefinitions);CPUINFO_SHARED=1;ENABLE_VULKAN=1 + %(PreprocessorDefinitions);CPUINFO_SHARED=1;ENABLE_VULKAN=1;ENABLE_SDL=1 %(PreprocessorDefinitions);ENABLE_OPENGL=1 %(AdditionalIncludeDirectories);$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\kissfft\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\simpleini\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\d3d12ma\include;$(SolutionDir)dep\vulkan\include;$(SolutionDir)dep\ffmpeg\include %(AdditionalIncludeDirectories);$(SolutionDir)dep\glad\include @@ -14,7 +14,6 @@ %(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib - %(AdditionalDependencies);opengl32.lib diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp index 1bc9636ec..0891c7960 100644 --- a/src/util/vulkan_device.cpp +++ b/src/util/vulkan_device.cpp @@ -27,7 +27,7 @@ #include #include -LOG_CHANNEL(VulkanDevice); +LOG_CHANNEL(GPUDevice); // TODO: VK_KHR_display. @@ -110,6 +110,8 @@ static std::mutex s_instance_mutex; VulkanDevice::VulkanDevice() { + m_render_api = RenderAPI::Vulkan; + #ifdef _DEBUG s_debug_scope_depth = 0; #endif @@ -637,14 +639,6 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay enabled_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics; device_info.pEnabledFeatures = &enabled_features; - // Enable debug layer on debug builds - if (enable_validation_layer) - { - static const char* layer_names[] = {"VK_LAYER_LUNARG_standard_validation"}; - device_info.enabledLayerCount = 1; - device_info.ppEnabledLayerNames = layer_names; - } - VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT rasterization_order_access_feature = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT, nullptr, VK_TRUE, VK_FALSE, VK_FALSE}; @@ -1266,11 +1260,6 @@ void VulkanDevice::WaitForFenceCounter(u64 fence_counter) WaitForCommandBufferCompletion(index); } -void VulkanDevice::WaitForGPUIdle() -{ - vkDeviceWaitIdle(m_device); -} - float VulkanDevice::GetAndResetAccumulatedGPUTime() { const float time = m_accumulated_gpu_time; @@ -1445,9 +1434,15 @@ void VulkanDevice::QueuePresent(VulkanSwapChain* present_swap_chain) { // VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain. if (res == VK_ERROR_OUT_OF_DATE_KHR) - ResizeWindow(0, 0, m_window_info.surface_scale); + { + Error error; + if (!present_swap_chain->ResizeBuffers(0, 0, present_swap_chain->GetScale(), &error)) [[unlikely]] + WARNING_LOG("Failed to reszie swap chain: {}", error.GetDescription()); + } else + { LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: "); + } return; } @@ -1889,13 +1884,11 @@ bool VulkanDevice::IsSuitableDefaultRenderer() #endif } -bool VulkanDevice::HasSurface() const -{ - return static_cast(m_swap_chain); -} - -bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) +bool VulkanDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, + const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) { std::unique_lock lock(s_instance_mutex); bool enable_debug_utils = m_debug_device; @@ -1908,7 +1901,7 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional ex return false; } - m_instance = CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer); + m_instance = CreateVulkanInstance(wi, &m_optional_extensions, enable_debug_utils, enable_validation_layer); if (m_instance == VK_NULL_HANDLE) { if (enable_debug_utils || enable_validation_layer) @@ -1916,8 +1909,7 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional ex // Try again without the validation layer. enable_debug_utils = false; enable_validation_layer = false; - m_instance = - CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer); + m_instance = CreateVulkanInstance(wi, &m_optional_extensions, enable_debug_utils, enable_validation_layer); if (m_instance == VK_NULL_HANDLE) { Error::SetStringView(error, "Failed to create Vulkan instance. Does your GPU and/or driver support Vulkan?"); @@ -1983,21 +1975,21 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional ex if (enable_debug_utils) EnableDebugUtils(); - VkSurfaceKHR surface = VK_NULL_HANDLE; - ScopedGuard surface_cleanup = [this, &surface]() { - if (surface != VK_NULL_HANDLE) - vkDestroySurfaceKHR(m_instance, surface, nullptr); - }; - if (m_window_info.type != WindowInfo::Type::Surfaceless) + std::unique_ptr swap_chain; + if (!wi.IsSurfaceless()) { - surface = VulkanSwapChain::CreateVulkanSurface(m_instance, m_physical_device, &m_window_info); - if (surface == VK_NULL_HANDLE) + swap_chain = + std::make_unique(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_control); + if (!swap_chain->CreateSurface(m_instance, m_physical_device, error)) return false; } // Attempt to create the device. - if (!CreateDevice(surface, enable_validation_layer, disabled_features, error)) + if (!CreateDevice(swap_chain ? swap_chain->GetSurface() : VK_NULL_HANDLE, enable_validation_layer, disabled_features, + error)) + { return false; + } // And critical resources. if (!CreateAllocator() || !CreatePersistentDescriptorPool() || !CreateCommandBuffers() || !CreatePipelineLayouts()) @@ -2005,26 +1997,16 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, std::optional ex m_exclusive_fullscreen_control = exclusive_fullscreen_control; - if (surface != VK_NULL_HANDLE) + if (swap_chain) { - VkPresentModeKHR present_mode; - if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) || - !(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control))) - { - Error::SetStringView(error, "Failed to create swap chain"); + // Render a frame as soon as possible to clear out whatever was previously being displayed. + if (!swap_chain->CreateSwapChain(*this, error) || !swap_chain->CreateSwapChainImages(*this, error)) return false; - } - // NOTE: This is assigned afterwards, because some platforms can modify the window info (e.g. Metal). - m_window_info = m_swap_chain->GetWindowInfo(); + RenderBlankFrame(swap_chain.get()); + m_main_swap_chain = std::move(swap_chain); } - surface_cleanup.Cancel(); - - // Render a frame as soon as possible to clear out whatever was previously being displayed. - if (m_window_info.type != WindowInfo::Type::Surfaceless) - RenderBlankFrame(); - if (!CreateNullTexture()) { Error::SetStringView(error, "Failed to create dummy texture"); @@ -2049,9 +2031,14 @@ void VulkanDevice::DestroyDevice() // Don't both submitting the current command buffer, just toss it. if (m_device != VK_NULL_HANDLE) - WaitForGPUIdle(); + vkDeviceWaitIdle(m_device); - m_swap_chain.reset(); + if (m_main_swap_chain) + { + // Explicit swap chain destroy, we don't want to execute the current cmdbuffer. + static_cast(m_main_swap_chain.get())->Destroy(*this, false); + m_main_swap_chain.reset(); + } if (m_null_texture) { @@ -2208,73 +2195,27 @@ bool VulkanDevice::GetPipelineCacheData(DynamicHeapArray* data, Error* error return true; } -bool VulkanDevice::UpdateWindow() +std::unique_ptr VulkanDevice::CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) { - DestroySurface(); - - if (!AcquireWindow(false)) - return false; - - if (m_window_info.IsSurfaceless()) - return true; - - // make sure previous frames are presented - if (InRenderPass()) - EndRenderPass(); - SubmitCommandBuffer(false); - WaitForGPUIdle(); - - VkSurfaceKHR surface = VulkanSwapChain::CreateVulkanSurface(m_instance, m_physical_device, &m_window_info); - if (surface == VK_NULL_HANDLE) + std::unique_ptr swap_chain = + std::make_unique(wi, vsync_mode, allow_present_throttle, exclusive_fullscreen_control); + if (swap_chain->CreateSurface(m_instance, m_physical_device, error) && swap_chain->CreateSwapChain(*this, error) && + swap_chain->CreateSwapChainImages(*this, error)) { - ERROR_LOG("Failed to create new surface for swap chain"); - return false; + if (InRenderPass()) + EndRenderPass(); + RenderBlankFrame(swap_chain.get()); + } + else + { + swap_chain.reset(); } - VkPresentModeKHR present_mode; - if (!VulkanSwapChain::SelectPresentMode(surface, &m_vsync_mode, &present_mode) || - !(m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, present_mode, m_exclusive_fullscreen_control))) - { - ERROR_LOG("Failed to create swap chain"); - VulkanSwapChain::DestroyVulkanSurface(m_instance, &m_window_info, surface); - return false; - } - - m_window_info = m_swap_chain->GetWindowInfo(); - RenderBlankFrame(); - return true; -} - -void VulkanDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) -{ - if (!m_swap_chain) - return; - - if (m_swap_chain->GetWidth() == static_cast(new_window_width) && - m_swap_chain->GetHeight() == static_cast(new_window_height)) - { - // skip unnecessary resizes - m_window_info.surface_scale = new_window_scale; - return; - } - - // make sure previous frames are presented - WaitForGPUIdle(); - - if (!m_swap_chain->ResizeSwapChain(new_window_width, new_window_height, new_window_scale)) - { - // AcquireNextImage() will fail, and we'll recreate the surface. - ERROR_LOG("Failed to resize swap chain. Next present will fail."); - return; - } - - m_window_info = m_swap_chain->GetWindowInfo(); -} - -void VulkanDevice::DestroySurface() -{ - WaitForGPUIdle(); - m_swap_chain.reset(); + return swap_chain; } bool VulkanDevice::SupportsTextureFormat(GPUTexture::Format format) const @@ -2308,7 +2249,16 @@ std::string VulkanDevice::GetDriverInfo() const return ret; } -void VulkanDevice::ExecuteAndWaitForGPUIdle() +void VulkanDevice::FlushCommands() +{ + if (InRenderPass()) + EndRenderPass(); + + SubmitCommandBuffer(false); + TrimTexturePool(); +} + +void VulkanDevice::WaitForGPUIdle() { if (InRenderPass()) EndRenderPass(); @@ -2316,39 +2266,7 @@ void VulkanDevice::ExecuteAndWaitForGPUIdle() SubmitCommandBuffer(true); } -void VulkanDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) -{ - m_allow_present_throttle = allow_present_throttle; - if (!m_swap_chain) - { - // For when it is re-created. - m_vsync_mode = mode; - return; - } - - VkPresentModeKHR present_mode; - if (!VulkanSwapChain::SelectPresentMode(m_swap_chain->GetSurface(), &mode, &present_mode)) - { - ERROR_LOG("Ignoring vsync mode change."); - return; - } - - // Actually changed? If using a fallback, it might not have. - if (m_vsync_mode == mode) - return; - - m_vsync_mode = mode; - - // This swap chain should not be used by the current buffer, thus safe to destroy. - WaitForGPUIdle(); - if (!m_swap_chain->SetPresentMode(present_mode)) - { - Panic("Failed to update swap chain present mode."); - m_swap_chain.reset(); - } -} - -GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color) +GPUDevice::PresentResult VulkanDevice::BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) { if (InRenderPass()) EndRenderPass(); @@ -2356,37 +2274,30 @@ GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color) if (m_device_was_lost) [[unlikely]] return PresentResult::DeviceLost; - // If we're running surfaceless, kick the command buffer so we don't run out of descriptors. - if (!m_swap_chain) - { - SubmitCommandBuffer(false); - TrimTexturePool(); - return PresentResult::SkipPresent; - } - - VkResult res = m_swap_chain->AcquireNextImage(); + VulkanSwapChain* const SC = static_cast(swap_chain); + VkResult res = SC->AcquireNextImage(); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: "); - m_swap_chain->ReleaseCurrentImage(); + SC->ReleaseCurrentImage(); if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) { - ResizeWindow(0, 0, m_window_info.surface_scale); - res = m_swap_chain->AcquireNextImage(); + Error error; + if (!SC->ResizeBuffers(0, 0, SC->GetScale(), &error)) [[unlikely]] + WARNING_LOG("Failed to resize buffers: {}", error.GetDescription()); + else + res = SC->AcquireNextImage(); } else if (res == VK_ERROR_SURFACE_LOST_KHR) { WARNING_LOG("Surface lost, attempting to recreate"); - if (!m_swap_chain->RecreateSurface(m_window_info)) - { - ERROR_LOG("Failed to recreate surface after loss"); - SubmitCommandBuffer(false); - TrimTexturePool(); - return PresentResult::SkipPresent; - } - res = m_swap_chain->AcquireNextImage(); + Error error; + if (!SC->RecreateSurface(&error)) + ERROR_LOG("Failed to recreate surface after loss: {}", error.GetDescription()); + else + res = SC->AcquireNextImage(); } // This can happen when multiple resize events happen in quick succession. @@ -2400,33 +2311,38 @@ GPUDevice::PresentResult VulkanDevice::BeginPresent(u32 clear_color) } } - BeginSwapChainRenderPass(clear_color); + BeginSwapChainRenderPass(SC, clear_color); return PresentResult::OK; } -void VulkanDevice::EndPresent(bool explicit_present, u64 present_time) +void VulkanDevice::EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) { + VulkanSwapChain* const SC = static_cast(swap_chain); + DebugAssert(present_time == 0); DebugAssert(InRenderPass() && m_num_current_render_targets == 0 && !m_current_depth_target); EndRenderPass(); + DebugAssert(SC == m_current_swap_chain); + m_current_swap_chain = nullptr; + VkCommandBuffer cmdbuf = GetCurrentCommandBuffer(); - VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, m_swap_chain->GetCurrentImage(), GPUTexture::Type::RenderTarget, - 0, 1, 0, 1, VulkanTexture::Layout::ColorAttachment, + VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, SC->GetCurrentImage(), GPUTexture::Type::RenderTarget, 0, 1, 0, + 1, VulkanTexture::Layout::ColorAttachment, VulkanTexture::Layout::PresentSrc); - EndAndSubmitCommandBuffer(m_swap_chain.get(), explicit_present); + EndAndSubmitCommandBuffer(SC, explicit_present); MoveToNextCommandBuffer(); InvalidateCachedState(); TrimTexturePool(); } -void VulkanDevice::SubmitPresent() +void VulkanDevice::SubmitPresent(GPUSwapChain* swap_chain) { - DebugAssert(m_swap_chain); + DebugAssert(swap_chain); if (m_device_was_lost) [[unlikely]] return; - QueuePresent(m_swap_chain.get()); + QueuePresent(static_cast(swap_chain)); } #ifdef _DEBUG @@ -2515,7 +2431,6 @@ u32 VulkanDevice::GetMaxMultisamples(VkPhysicalDevice physical_device, const VkP void VulkanDevice::SetFeatures(FeatureMask disabled_features, const VkPhysicalDeviceFeatures& vk_features) { const u32 store_api_version = std::min(m_device_properties.apiVersion, VK_API_VERSION_1_1); - m_render_api = RenderAPI::Vulkan; m_render_api_version = (VK_API_VERSION_MAJOR(store_api_version) * 100u) + (VK_API_VERSION_MINOR(store_api_version) * 10u) + (VK_API_VERSION_PATCH(store_api_version)); m_max_texture_size = @@ -3076,9 +2991,9 @@ void VulkanDevice::DestroyPersistentDescriptorSets() FreePersistentDescriptorSet(m_ubo_descriptor_set); } -void VulkanDevice::RenderBlankFrame() +void VulkanDevice::RenderBlankFrame(VulkanSwapChain* swap_chain) { - VkResult res = m_swap_chain->AcquireNextImage(); + VkResult res = swap_chain->AcquireNextImage(); if (res != VK_SUCCESS) { ERROR_LOG("Failed to acquire image for blank frame present"); @@ -3087,7 +3002,7 @@ void VulkanDevice::RenderBlankFrame() VkCommandBuffer cmdbuf = GetCurrentCommandBuffer(); - const VkImage image = m_swap_chain->GetCurrentImage(); + const VkImage image = swap_chain->GetCurrentImage(); static constexpr VkImageSubresourceRange srr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; static constexpr VkClearColorValue clear_color = {{0.0f, 0.0f, 0.0f, 1.0f}}; VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1, @@ -3096,7 +3011,7 @@ void VulkanDevice::RenderBlankFrame() VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1, VulkanTexture::Layout::TransferDst, VulkanTexture::Layout::PresentSrc); - EndAndSubmitCommandBuffer(m_swap_chain.get(), false); + EndAndSubmitCommandBuffer(swap_chain, false); MoveToNextCommandBuffer(); InvalidateCachedState(); @@ -3363,7 +3278,7 @@ void VulkanDevice::BeginRenderPass() VkRenderingAttachmentInfo& ai = attachments[0]; ai.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; ai.pNext = nullptr; - ai.imageView = m_swap_chain->GetCurrentImageView(); + ai.imageView = m_current_swap_chain->GetCurrentImageView(); ai.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; ai.resolveMode = VK_RESOLVE_MODE_NONE_KHR; ai.resolveImageView = VK_NULL_HANDLE; @@ -3373,7 +3288,7 @@ void VulkanDevice::BeginRenderPass() ri.colorAttachmentCount = 1; ri.pColorAttachments = attachments.data(); - ri.renderArea = {{}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}}; + ri.renderArea = {{}, {m_current_swap_chain->GetWidth(), m_current_swap_chain->GetHeight()}}; } m_current_render_pass = DYNAMIC_RENDERING_RENDER_PASS; @@ -3434,10 +3349,10 @@ void VulkanDevice::BeginRenderPass() else { // Re-rendering to swap chain. - bi.framebuffer = m_swap_chain->GetCurrentFramebuffer(); + bi.framebuffer = m_current_swap_chain->GetCurrentFramebuffer(); bi.renderPass = m_current_render_pass = - GetSwapChainRenderPass(m_swap_chain->GetWindowInfo().surface_format, VK_ATTACHMENT_LOAD_OP_LOAD); - bi.renderArea.extent = {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}; + GetSwapChainRenderPass(m_current_swap_chain->GetFormat(), VK_ATTACHMENT_LOAD_OP_LOAD); + bi.renderArea.extent = {m_current_swap_chain->GetWidth(), m_current_swap_chain->GetHeight()}; } DebugAssert(m_current_render_pass); @@ -3451,12 +3366,12 @@ void VulkanDevice::BeginRenderPass() SetInitialPipelineState(); } -void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color) +void VulkanDevice::BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 clear_color) { DebugAssert(!InRenderPass()); const VkCommandBuffer cmdbuf = GetCurrentCommandBuffer(); - const VkImage swap_chain_image = m_swap_chain->GetCurrentImage(); + const VkImage swap_chain_image = swap_chain->GetCurrentImage(); // Swap chain images start in undefined VulkanTexture::TransitionSubresourcesToLayout(cmdbuf, swap_chain_image, GPUTexture::Type::RenderTarget, 0, 1, 0, 1, @@ -3477,7 +3392,7 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color) { VkRenderingAttachmentInfo ai = {VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR, nullptr, - m_swap_chain->GetCurrentImageView(), + swap_chain->GetCurrentImageView(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_RESOLVE_MODE_NONE_KHR, VK_NULL_HANDLE, @@ -3489,7 +3404,7 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color) const VkRenderingInfoKHR ri = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR, nullptr, 0u, - {{}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}}, + {{}, {swap_chain->GetWidth(), swap_chain->GetHeight()}}, 1u, 0u, 1u, @@ -3503,14 +3418,14 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color) else { m_current_render_pass = - GetSwapChainRenderPass(m_swap_chain->GetWindowInfo().surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR); + GetSwapChainRenderPass(swap_chain->GetWindowInfo().surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR); DebugAssert(m_current_render_pass); const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, m_current_render_pass, - m_swap_chain->GetCurrentFramebuffer(), - {{0, 0}, {m_swap_chain->GetWidth(), m_swap_chain->GetHeight()}}, + swap_chain->GetCurrentFramebuffer(), + {{0, 0}, {swap_chain->GetWidth(), swap_chain->GetHeight()}}, 1u, &clear_value}; vkCmdBeginRenderPass(GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE); @@ -3526,6 +3441,7 @@ void VulkanDevice::BeginSwapChainRenderPass(u32 clear_color) std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets)); m_current_depth_target = nullptr; m_current_framebuffer = VK_NULL_HANDLE; + m_current_swap_chain = swap_chain; } bool VulkanDevice::InRenderPass() diff --git a/src/util/vulkan_device.h b/src/util/vulkan_device.h index 702ae1afe..e32d7496a 100644 --- a/src/util/vulkan_device.h +++ b/src/util/vulkan_device.h @@ -75,16 +75,16 @@ public: static GPUList EnumerateGPUs(); static AdapterInfoList GetAdapterList(); - bool HasSurface() const override; - - bool UpdateWindow() override; - void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override; - void DestroySurface() override; - std::string GetDriverInfo() const override; - void ExecuteAndWaitForGPUIdle() override; + void FlushCommands() override; + void WaitForGPUIdle() override; + std::unique_ptr CreateSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, + bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, + Error* error) override; std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, GPUTexture::Format format, const void* data = nullptr, u32 data_stride = 0) override; @@ -138,11 +138,9 @@ public: bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; - void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; - - PresentResult BeginPresent(u32 clear_color) override; - void EndPresent(bool explicit_present, u64 present_time) override; - void SubmitPresent() override; + PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override; + void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override; + void SubmitPresent(GPUSwapChain* swap_chain) override; // Global state accessors ALWAYS_INLINE static VulkanDevice& GetInstance() { return *static_cast(g_gpu_device.get()); } @@ -167,7 +165,7 @@ public: return static_cast(m_device_properties.limits.optimalBufferCopyRowPitchAlignment); } - void WaitForGPUIdle(); + void WaitForAllFences(); // Creates a simple render pass. VkRenderPass GetRenderPass(const GPUPipeline::GraphicsConfig& config); @@ -219,19 +217,26 @@ public: // Also invokes callbacks for completion. void WaitForFenceCounter(u64 fence_counter); + // Ends a render pass if we're currently in one. + // When Bind() is next called, the pass will be restarted. + void BeginRenderPass(); + void EndRenderPass(); + bool InRenderPass(); + /// Ends any render pass, executes the command buffer, and invalidates cached state. void SubmitCommandBuffer(bool wait_for_completion); void SubmitCommandBuffer(bool wait_for_completion, const std::string_view reason); void SubmitCommandBufferAndRestartRenderPass(const std::string_view reason); - void UnbindFramebuffer(VulkanTexture* tex); void UnbindPipeline(VulkanPipeline* pl); void UnbindTexture(VulkanTexture* tex); void UnbindTextureBuffer(VulkanTextureBuffer* buf); protected: - bool CreateDevice(std::string_view adapter, std::optional exclusive_fullscreen_control, - FeatureMask disabled_features, Error* error) override; + bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi, + GPUVSyncMode vsync_mode, bool allow_present_throttle, + const ExclusiveFullscreenMode* exclusive_fullscreen_mode, + std::optional exclusive_fullscreen_control, Error* error) override; void DestroyDevice() override; bool ReadPipelineCache(DynamicHeapArray data, Error* error) override; @@ -351,7 +356,7 @@ private: VkSampler GetSampler(const GPUSampler::Config& config); void DestroySamplers(); - void RenderBlankFrame(); + void RenderBlankFrame(VulkanSwapChain* swap_chain); bool TryImportHostMemory(void* data, size_t data_size, VkBufferUsageFlags buffer_usage, VkDeviceMemory* out_memory, VkBuffer* out_buffer, VkDeviceSize* out_offset); @@ -371,12 +376,7 @@ private: bool UpdateDescriptorSetsForLayout(u32 dirty); bool UpdateDescriptorSets(u32 dirty); - // Ends a render pass if we're currently in one. - // When Bind() is next called, the pass will be restarted. - void BeginRenderPass(); - void BeginSwapChainRenderPass(u32 clear_color); - void EndRenderPass(); - bool InRenderPass(); + void BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 clear_color); VkRenderPass CreateCachedRenderPass(RenderPassCacheKey key); static VkFramebuffer CreateFramebuffer(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds, u32 flags); @@ -427,7 +427,6 @@ private: OptionalExtensions m_optional_extensions = {}; std::optional m_exclusive_fullscreen_control; - std::unique_ptr m_swap_chain; std::unique_ptr m_null_texture; VkDescriptorSetLayout m_ubo_ds_layout = VK_NULL_HANDLE; @@ -468,4 +467,5 @@ private: VulkanTextureBuffer* m_current_texture_buffer = nullptr; GSVector4i m_current_viewport = GSVector4i::cxpr(0, 0, 1, 1); GSVector4i m_current_scissor = GSVector4i::cxpr(0, 0, 1, 1); + VulkanSwapChain* m_current_swap_chain = nullptr; }; diff --git a/src/util/vulkan_loader.cpp b/src/util/vulkan_loader.cpp index 0f949d8a7..a59c90ce1 100644 --- a/src/util/vulkan_loader.cpp +++ b/src/util/vulkan_loader.cpp @@ -16,7 +16,7 @@ #include #include -LOG_CHANNEL(VulkanDevice); +LOG_CHANNEL(GPUDevice); extern "C" { diff --git a/src/util/vulkan_pipeline.cpp b/src/util/vulkan_pipeline.cpp index f24aa8d69..52db0d766 100644 --- a/src/util/vulkan_pipeline.cpp +++ b/src/util/vulkan_pipeline.cpp @@ -11,7 +11,7 @@ #include "common/heap_array.h" #include "common/log.h" -LOG_CHANNEL(VulkanDevice); +LOG_CHANNEL(GPUDevice); VulkanShader::VulkanShader(GPUShaderStage stage, VkShaderModule mod) : GPUShader(stage), m_module(mod) { diff --git a/src/util/vulkan_stream_buffer.cpp b/src/util/vulkan_stream_buffer.cpp index 04065dc61..64630c0bd 100644 --- a/src/util/vulkan_stream_buffer.cpp +++ b/src/util/vulkan_stream_buffer.cpp @@ -9,7 +9,8 @@ #include "common/assert.h" #include "common/bitutils.h" #include "common/log.h" -LOG_CHANNEL(VulkanDevice); + +LOG_CHANNEL(GPUDevice); VulkanStreamBuffer::VulkanStreamBuffer() = default; diff --git a/src/util/vulkan_swap_chain.cpp b/src/util/vulkan_swap_chain.cpp index c45514aef..52fea0072 100644 --- a/src/util/vulkan_swap_chain.cpp +++ b/src/util/vulkan_swap_chain.cpp @@ -6,6 +6,7 @@ #include "vulkan_device.h" #include "common/assert.h" +#include "common/error.h" #include "common/log.h" #include @@ -20,9 +21,7 @@ #include "util/metal_layer.h" #endif -LOG_CHANNEL(VulkanDevice); - -static_assert(VulkanSwapChain::NUM_SEMAPHORES == (VulkanDevice::NUM_COMMAND_BUFFERS + 1)); +LOG_CHANNEL(GPUDevice); static VkFormat GetLinearFormat(VkFormat format) { @@ -72,170 +71,142 @@ static const char* PresentModeToString(VkPresentModeKHR mode) } } -VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode, +VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, std::optional exclusive_fullscreen_control) - : m_window_info(wi), m_surface(surface), m_present_mode(present_mode), - m_exclusive_fullscreen_control(exclusive_fullscreen_control) + : GPUSwapChain(wi, vsync_mode, allow_present_throttle), m_exclusive_fullscreen_control(exclusive_fullscreen_control) { + static_assert(NUM_SEMAPHORES == (VulkanDevice::NUM_COMMAND_BUFFERS + 1)); } VulkanSwapChain::~VulkanSwapChain() { - DestroySwapChainImages(); - DestroySwapChain(); - DestroySurface(); + Destroy(VulkanDevice::GetInstance(), true); } -VkSurfaceKHR VulkanSwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi) +bool VulkanSwapChain::CreateSurface(VkInstance instance, VkPhysicalDevice physical_device, Error* error) { #if defined(VK_USE_PLATFORM_WIN32_KHR) - if (wi->type == WindowInfo::Type::Win32) + if (m_window_info.type == WindowInfo::Type::Win32) { - VkWin32SurfaceCreateInfoKHR surface_create_info = { - VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkWin32SurfaceCreateFlagsKHR flags - nullptr, // HINSTANCE hinstance - reinterpret_cast(wi->window_handle) // HWND hwnd - }; - - VkSurfaceKHR surface; - VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface); + const VkWin32SurfaceCreateInfoKHR surface_create_info = {.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, + .hwnd = static_cast(m_window_info.window_handle)}; + const VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: "); - return VK_NULL_HANDLE; + Vulkan::SetErrorObject(error, "vkCreateWin32SurfaceKHR() failed: ", res); + return false; } - return surface; + return true; } #endif #if defined(VK_USE_PLATFORM_METAL_EXT) - if (wi->type == WindowInfo::Type::MacOS) + if (m_window_info.type == WindowInfo::Type::MacOS) { - // TODO: FIXME - if (!wi->surface_handle && !CocoaTools::CreateMetalLayer(wi)) - return VK_NULL_HANDLE; + m_metal_layer = CocoaTools::CreateMetalLayer(m_window_info, error); + if (!m_metal_layer) + return false; - VkMetalSurfaceCreateInfoEXT surface_create_info = {VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0, - static_cast(wi->surface_handle)}; - - VkSurfaceKHR surface; - VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface); + const VkMetalSurfaceCreateInfoEXT surface_create_info = {.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, + .pLayer = static_cast(m_metal_layer)}; + const VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: "); - return VK_NULL_HANDLE; + Vulkan::SetErrorObject(error, "vkCreateMetalSurfaceEXT failed: ", res); + return false; } - return surface; + return true; } #endif #if defined(VK_USE_PLATFORM_ANDROID_KHR) - if (wi->type == WindowInfo::Type::Android) + if (m_window_info.type == WindowInfo::Type::Android) { - VkAndroidSurfaceCreateInfoKHR surface_create_info = { - VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkAndroidSurfaceCreateFlagsKHR flags - reinterpret_cast(wi->window_handle) // ANativeWindow* window - }; - - VkSurfaceKHR surface; - VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + const VkAndroidSurfaceCreateInfoKHR surface_create_info = { + .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, + .window = static_cast(m_window_info.window_handle)}; + const VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: "); - return VK_NULL_HANDLE; + Vulkan::SetErrorObject(error, "vkCreateAndroidSurfaceKHR failed: ", res); + return false; } - return surface; + return true; } #endif #if defined(VK_USE_PLATFORM_XLIB_KHR) - if (wi->type == WindowInfo::Type::X11) + if (m_window_info.type == WindowInfo::Type::X11) { - VkXlibSurfaceCreateInfoKHR surface_create_info = { - VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType - nullptr, // const void* pNext - 0, // VkXlibSurfaceCreateFlagsKHR flags - static_cast(wi->display_connection), // Display* dpy - reinterpret_cast(wi->window_handle) // Window window - }; - - VkSurfaceKHR surface; - VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + const VkXlibSurfaceCreateInfoKHR surface_create_info = { + .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, + .dpy = static_cast(m_window_info.display_connection), + .window = reinterpret_cast(m_window_info.window_handle)}; + const VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: "); - return VK_NULL_HANDLE; + Vulkan::SetErrorObject(error, "vkCreateXlibSurfaceKHR failed: ", res); + return false; } - return surface; + return true; } #endif #if defined(VK_USE_PLATFORM_WAYLAND_KHR) - if (wi->type == WindowInfo::Type::Wayland) + if (m_window_info.type == WindowInfo::Type::Wayland) { - VkWaylandSurfaceCreateInfoKHR surface_create_info = {VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0, - static_cast(wi->display_connection), - static_cast(wi->window_handle)}; - - VkSurfaceKHR surface; - VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + const VkWaylandSurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0, + static_cast(m_window_info.display_connection), + static_cast(m_window_info.window_handle)}; + VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &m_surface); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkCreateWaylandSurfaceEXT failed: "); - return VK_NULL_HANDLE; + Vulkan::SetErrorObject(error, "vkCreateWaylandSurfaceEXT failed: ", res); + return false; } - return surface; + return true; } #endif - return VK_NULL_HANDLE; + Error::SetStringFmt(error, "Unhandled window type: {}", static_cast(m_window_info.type)); + return false; } -void VulkanSwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface) +void VulkanSwapChain::DestroySurface() { - vkDestroySurfaceKHR(VulkanDevice::GetInstance().GetVulkanInstance(), surface, nullptr); + if (m_surface != VK_NULL_HANDLE) + { + vkDestroySurfaceKHR(VulkanDevice::GetInstance().GetVulkanInstance(), m_surface, nullptr); + m_surface = VK_NULL_HANDLE; + } #if defined(__APPLE__) - if (wi->type == WindowInfo::Type::MacOS && wi->surface_handle) - CocoaTools::DestroyMetalLayer(wi); + if (m_metal_layer) + { + CocoaTools::DestroyMetalLayer(m_window_info, m_metal_layer); + m_metal_layer = nullptr; + } #endif } -std::unique_ptr VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface, - VkPresentModeKHR present_mode, - std::optional exclusive_fullscreen_control) +std::optional VulkanSwapChain::SelectSurfaceFormat(VkPhysicalDevice physdev, Error* error) { - std::unique_ptr swap_chain = - std::unique_ptr(new VulkanSwapChain(wi, surface, present_mode, exclusive_fullscreen_control)); - if (!swap_chain->CreateSwapChain()) - return nullptr; - - return swap_chain; -} - -std::optional VulkanSwapChain::SelectSurfaceFormat(VkSurfaceKHR surface) -{ - VulkanDevice& dev = VulkanDevice::GetInstance(); u32 format_count; - VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, nullptr); + VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(physdev, m_surface, &format_count, nullptr); if (res != VK_SUCCESS || format_count == 0) { - LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); + Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ", res); return std::nullopt; } std::vector surface_formats(format_count); - res = - vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, surface_formats.data()); + res = vkGetPhysicalDeviceSurfaceFormatsKHR(physdev, m_surface, &format_count, surface_formats.data()); Assert(res == VK_SUCCESS); // If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA @@ -255,28 +226,28 @@ std::optional VulkanSwapChain::SelectSurfaceFormat(VkSurface return VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; } - ERROR_LOG("Failed to find a suitable format for swap chain buffers. Available formats were:"); + SmallString errormsg = "Failed to find a suitable format for swap chain buffers. Available formats were:"; for (const VkSurfaceFormatKHR& sf : surface_formats) - ERROR_LOG(" {}", static_cast(sf.format)); - + errormsg.append_format(" {}", static_cast(sf.format)); + Error::SetStringView(error, errormsg); return std::nullopt; } -bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode) +std::optional VulkanSwapChain::SelectPresentMode(VkPhysicalDevice physdev, GPUVSyncMode& vsync_mode, + Error* error) { - VulkanDevice& dev = VulkanDevice::GetInstance(); + VkResult res; u32 mode_count; - res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count, nullptr); + res = vkGetPhysicalDeviceSurfacePresentModesKHR(physdev, m_surface, &mode_count, nullptr); if (res != VK_SUCCESS || mode_count == 0) { - LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); - return false; + Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ", res); + return std::nullopt; } std::vector present_modes(mode_count); - res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count, - present_modes.data()); + res = vkGetPhysicalDeviceSurfacePresentModesKHR(physdev, m_surface, &mode_count, present_modes.data()); Assert(res == VK_SUCCESS); // Checks if a particular mode is supported, if it is, returns that mode. @@ -286,25 +257,25 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn return it != present_modes.end(); }; - switch (*vsync_mode) + switch (vsync_mode) { case GPUVSyncMode::Disabled: { // Prefer immediate > mailbox > fifo. if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR)) { - *present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; + return VK_PRESENT_MODE_IMMEDIATE_KHR; } else if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) { WARNING_LOG("Immediate not supported for vsync-disabled, using mailbox."); - *present_mode = VK_PRESENT_MODE_MAILBOX_KHR; + return VK_PRESENT_MODE_MAILBOX_KHR; } else { WARNING_LOG("Mailbox not supported for vsync-disabled, using FIFO."); - *present_mode = VK_PRESENT_MODE_FIFO_KHR; - *vsync_mode = GPUVSyncMode::FIFO; + vsync_mode = GPUVSyncMode::FIFO; + return VK_PRESENT_MODE_FIFO_KHR; } } break; @@ -312,7 +283,7 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn case GPUVSyncMode::FIFO: { // FIFO is always available. - *present_mode = VK_PRESENT_MODE_FIFO_KHR; + return VK_PRESENT_MODE_FIFO_KHR; } break; @@ -321,48 +292,50 @@ bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsyn // Mailbox > fifo. if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) { - *present_mode = VK_PRESENT_MODE_MAILBOX_KHR; + return VK_PRESENT_MODE_MAILBOX_KHR; } else { WARNING_LOG("Mailbox not supported for vsync-mailbox, using FIFO."); - *present_mode = VK_PRESENT_MODE_FIFO_KHR; - *vsync_mode = GPUVSyncMode::FIFO; + vsync_mode = GPUVSyncMode::FIFO; + return VK_PRESENT_MODE_FIFO_KHR; } } break; DefaultCaseIsUnreachable() } - - return true; } -bool VulkanSwapChain::CreateSwapChain() +bool VulkanSwapChain::CreateSwapChain(VulkanDevice& dev, Error* error) { - VulkanDevice& dev = VulkanDevice::GetInstance(); + const VkPhysicalDevice physdev = dev.GetVulkanPhysicalDevice(); // Select swap chain format - std::optional surface_format = SelectSurfaceFormat(m_surface); + std::optional surface_format = SelectSurfaceFormat(physdev, error); if (!surface_format.has_value()) return false; + const std::optional present_mode = SelectPresentMode(physdev, m_vsync_mode, error); + if (!present_mode.has_value()) + return false; + // Look up surface properties to determine image count and dimensions VkSurfaceCapabilitiesKHR surface_capabilities; - VkResult res = - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev.GetVulkanPhysicalDevice(), m_surface, &surface_capabilities); + VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physdev, m_surface, &surface_capabilities); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: "); + Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: ", res); return false; } // Select number of images in swap chain, we prefer one buffer in the background to work on in triple-buffered mode. // maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers. u32 image_count = std::clamp( - (m_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount, + (present_mode.value() == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount, (surface_capabilities.maxImageCount == 0) ? std::numeric_limits::max() : surface_capabilities.maxImageCount); - DEV_LOG("Creating a swap chain with {} images in present mode {}", image_count, PresentModeToString(m_present_mode)); + DEV_LOG("Creating a swap chain with {} images in present mode {}", image_count, + PresentModeToString(present_mode.value())); // Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here // determines window size? Android sometimes lags updating currentExtent, so don't use it. @@ -396,7 +369,7 @@ bool VulkanSwapChain::CreateSwapChain() VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; if ((surface_capabilities.supportedUsageFlags & image_usage) != image_usage) { - ERROR_LOG("Vulkan: Swap chain does not support usage as color attachment"); + Error::SetStringView(error, "Swap chain does not support usage as color attachment"); return false; } @@ -406,25 +379,21 @@ bool VulkanSwapChain::CreateSwapChain() m_swap_chain = VK_NULL_HANDLE; // Now we can actually create the swap chain - VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, - nullptr, - 0, - m_surface, - image_count, - surface_format->format, - surface_format->colorSpace, - size, - 1u, - image_usage, - VK_SHARING_MODE_EXCLUSIVE, - 0, - nullptr, - transform, - alpha, - m_present_mode, - VK_TRUE, - old_swap_chain}; - std::array indices = {{ + VkSwapchainCreateInfoKHR swap_chain_info = {.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = m_surface, + .minImageCount = image_count, + .imageFormat = surface_format->format, + .imageColorSpace = surface_format->colorSpace, + .imageExtent = size, + .imageArrayLayers = 1u, + .imageUsage = image_usage, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .preTransform = transform, + .compositeAlpha = alpha, + .presentMode = present_mode.value(), + .clipped = VK_TRUE, + .oldSwapchain = old_swap_chain}; + const std::array queue_indices = {{ dev.GetGraphicsQueueFamilyIndex(), dev.GetPresentQueueFamilyIndex(), }}; @@ -432,7 +401,7 @@ bool VulkanSwapChain::CreateSwapChain() { swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swap_chain_info.queueFamilyIndexCount = 2; - swap_chain_info.pQueueFamilyIndices = indices.data(); + swap_chain_info.pQueueFamilyIndices = queue_indices.data(); } #ifdef _WIN32 @@ -466,81 +435,101 @@ bool VulkanSwapChain::CreateSwapChain() ERROR_LOG("Exclusive fullscreen control requested, but is not supported on this platform."); #endif - res = vkCreateSwapchainKHR(dev.GetVulkanDevice(), &swap_chain_info, nullptr, &m_swap_chain); + const VkDevice vkdev = dev.GetVulkanDevice(); + res = vkCreateSwapchainKHR(vkdev, &swap_chain_info, nullptr, &m_swap_chain); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: "); + Vulkan::SetErrorObject(error, "vkCreateSwapchainKHR failed: ", res); return false; } // Now destroy the old swap chain, since it's been recreated. // We can do this immediately since all work should have been completed before calling resize. if (old_swap_chain != VK_NULL_HANDLE) - vkDestroySwapchainKHR(dev.GetVulkanDevice(), old_swap_chain, nullptr); + vkDestroySwapchainKHR(vkdev, old_swap_chain, nullptr); - m_format = surface_format->format; - m_window_info.surface_width = std::max(1u, size.width); - m_window_info.surface_height = std::max(1u, size.height); + if (size.width == 0 || size.width > std::numeric_limits::max() || size.height == 0 || + size.height > std::numeric_limits::max()) + { + Error::SetStringFmt(error, "Invalid swap chain dimensions: {}x{}", size.width, size.height); + return false; + } + + m_present_mode = present_mode.value(); + m_window_info.surface_width = static_cast(size.width); + m_window_info.surface_height = static_cast(size.height); m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format); if (m_window_info.surface_format == GPUTexture::Format::Unknown) { - ERROR_LOG("Unknown Vulkan surface format {}", static_cast(surface_format->format)); + Error::SetStringFmt(error, "Unknown surface format {}", static_cast(surface_format->format)); return false; } + return true; +} + +bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error) +{ + const VkDevice vkdev = dev.GetVulkanDevice(); + // Get and create images. Assert(m_images.empty()); - res = vkGetSwapchainImagesKHR(dev.GetVulkanDevice(), m_swap_chain, &image_count, nullptr); + u32 image_count; + VkResult res = vkGetSwapchainImagesKHR(vkdev, m_swap_chain, &image_count, nullptr); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: "); + Vulkan::SetErrorObject(error, "vkGetSwapchainImagesKHR failed: ", res); return false; } std::vector images(image_count); - res = vkGetSwapchainImagesKHR(dev.GetVulkanDevice(), m_swap_chain, &image_count, images.data()); + res = vkGetSwapchainImagesKHR(vkdev, m_swap_chain, &image_count, images.data()); Assert(res == VK_SUCCESS); - VkRenderPass render_pass = dev.GetSwapChainRenderPass(m_window_info.surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR); + const VkRenderPass render_pass = + dev.GetSwapChainRenderPass(m_window_info.surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR); if (render_pass == VK_NULL_HANDLE) + { + Error::SetStringFmt(error, "Failed to get render pass for format {}", + GPUTexture::GetFormatName(m_window_info.surface_format)); return false; + } Vulkan::FramebufferBuilder fbb; m_images.reserve(image_count); m_current_image = 0; for (u32 i = 0; i < image_count; i++) { - Image image = {}; + Image& image = m_images.emplace_back(); image.image = images[i]; const VkImageViewCreateInfo view_info = { - VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - nullptr, - 0, - images[i], - VK_IMAGE_VIEW_TYPE_2D, - m_format, - {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, - VK_COMPONENT_SWIZZLE_IDENTITY}, - {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u}, + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = images[i], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = VulkanDevice::TEXTURE_FORMAT_MAPPING[static_cast(m_window_info.surface_format)], + .components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY}, + .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u}, }; - if ((res = vkCreateImageView(dev.GetVulkanDevice(), &view_info, nullptr, &image.view)) != VK_SUCCESS) + if ((res = vkCreateImageView(vkdev, &view_info, nullptr, &image.view)) != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkCreateImageView() failed: "); + Vulkan::SetErrorObject(error, "vkCreateImageView() failed: ", res); + m_images.pop_back(); return false; } fbb.AddAttachment(image.view); fbb.SetRenderPass(render_pass); - fbb.SetSize(size.width, size.height, 1); - if ((image.framebuffer = fbb.Create(dev.GetVulkanDevice())) == VK_NULL_HANDLE) + fbb.SetSize(m_window_info.surface_width, m_window_info.surface_height, 1); + if ((image.framebuffer = fbb.Create(vkdev)) == VK_NULL_HANDLE) { - vkDestroyImageView(dev.GetVulkanDevice(), image.view, nullptr); + Error::SetStringView(error, "Failed to create swap chain image framebuffer."); + vkDestroyImageView(vkdev, image.view, nullptr); + m_images.pop_back(); return false; } - - m_images.push_back(image); } m_current_semaphore = 0; @@ -549,18 +538,18 @@ bool VulkanSwapChain::CreateSwapChain() ImageSemaphores& sema = m_semaphores[i]; const VkSemaphoreCreateInfo semaphore_info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0}; - res = vkCreateSemaphore(dev.GetVulkanDevice(), &semaphore_info, nullptr, &sema.available_semaphore); + res = vkCreateSemaphore(vkdev, &semaphore_info, nullptr, &sema.available_semaphore); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: "); + Vulkan::SetErrorObject(error, "vkCreateSemaphore failed: ", res); return false; } - res = vkCreateSemaphore(dev.GetVulkanDevice(), &semaphore_info, nullptr, &sema.rendering_finished_semaphore); + res = vkCreateSemaphore(vkdev, &semaphore_info, nullptr, &sema.rendering_finished_semaphore); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: "); - vkDestroySemaphore(dev.GetVulkanDevice(), sema.available_semaphore, nullptr); + Vulkan::SetErrorObject(error, "vkCreateSemaphore failed: ", res); + vkDestroySemaphore(vkdev, sema.available_semaphore, nullptr); sema.available_semaphore = VK_NULL_HANDLE; return false; } @@ -569,22 +558,39 @@ bool VulkanSwapChain::CreateSwapChain() return true; } +void VulkanSwapChain::Destroy(VulkanDevice& dev, bool wait_for_idle) +{ + if (!m_swap_chain && !m_surface) + return; + + if (wait_for_idle) + { + if (dev.InRenderPass()) + dev.EndRenderPass(); + + dev.WaitForGPUIdle(); + } + + DestroySwapChain(); + DestroySurface(); +} + void VulkanSwapChain::DestroySwapChainImages() { - VulkanDevice& dev = VulkanDevice::GetInstance(); + const VkDevice vkdev = VulkanDevice::GetInstance().GetVulkanDevice(); for (const auto& it : m_images) { // don't defer view destruction, images are no longer valid - vkDestroyFramebuffer(dev.GetVulkanDevice(), it.framebuffer, nullptr); - vkDestroyImageView(dev.GetVulkanDevice(), it.view, nullptr); + vkDestroyFramebuffer(vkdev, it.framebuffer, nullptr); + vkDestroyImageView(vkdev, it.view, nullptr); } m_images.clear(); - for (auto& it : m_semaphores) + for (const auto& it : m_semaphores) { if (it.rendering_finished_semaphore != VK_NULL_HANDLE) - vkDestroySemaphore(dev.GetVulkanDevice(), it.rendering_finished_semaphore, nullptr); + vkDestroySemaphore(vkdev, it.rendering_finished_semaphore, nullptr); if (it.available_semaphore != VK_NULL_HANDLE) - vkDestroySemaphore(dev.GetVulkanDevice(), it.available_semaphore, nullptr); + vkDestroySemaphore(vkdev, it.available_semaphore, nullptr); } m_semaphores = {}; @@ -595,13 +601,11 @@ void VulkanSwapChain::DestroySwapChain() { DestroySwapChainImages(); - if (m_swap_chain == VK_NULL_HANDLE) - return; - - vkDestroySwapchainKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, nullptr); - m_swap_chain = VK_NULL_HANDLE; - m_window_info.surface_width = 0; - m_window_info.surface_height = 0; + if (m_swap_chain != VK_NULL_HANDLE) + { + vkDestroySwapchainKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, nullptr); + m_swap_chain = VK_NULL_HANDLE; + } } VkResult VulkanSwapChain::AcquireNextImage() @@ -649,20 +653,27 @@ void VulkanSwapChain::ResetImageAcquireResult() m_image_acquire_result.reset(); } -bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_scale) +bool VulkanSwapChain::ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) { + m_window_info.surface_scale = new_scale; + if (m_window_info.surface_width == new_width && m_window_info.surface_height == new_height) + return true; + + VulkanDevice& dev = VulkanDevice::GetInstance(); + if (dev.InRenderPass()) + dev.EndRenderPass(); + dev.SubmitCommandBuffer(true); + ReleaseCurrentImage(); DestroySwapChainImages(); if (new_width != 0 && new_height != 0) { - m_window_info.surface_width = new_width; - m_window_info.surface_height = new_height; + m_window_info.surface_width = static_cast(new_width); + m_window_info.surface_height = static_cast(new_height); } - m_window_info.surface_scale = new_scale; - - if (!CreateSwapChain()) + if (!CreateSwapChain(VulkanDevice::GetInstance(), error) || !CreateSwapChainImages(dev, error)) { DestroySwapChain(); return false; @@ -671,18 +682,31 @@ bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_s return true; } -bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode) +bool VulkanSwapChain::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) { - if (m_present_mode == present_mode) + m_allow_present_throttle = allow_present_throttle; + + VulkanDevice& dev = VulkanDevice::GetInstance(); + const std::optional new_present_mode = + SelectPresentMode(dev.GetVulkanPhysicalDevice(), mode, error); + if (!new_present_mode.has_value()) + return false; + + // High-level mode could change without the actual backend mode changing. + m_vsync_mode = mode; + if (m_present_mode == new_present_mode.value()) return true; - m_present_mode = present_mode; + if (dev.InRenderPass()) + dev.EndRenderPass(); + dev.SubmitCommandBuffer(true); + // TODO: Use the maintenance extension to change it without recreating... // Recreate the swap chain with the new present mode. VERBOSE_LOG("Recreating swap chain to change present mode."); ReleaseCurrentImage(); DestroySwapChainImages(); - if (!CreateSwapChain()) + if (!CreateSwapChain(dev, error) || !CreateSwapChainImages(dev, error)) { DestroySwapChain(); return false; @@ -691,18 +715,19 @@ bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode) return true; } -bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi) +bool VulkanSwapChain::RecreateSurface(Error* error) { VulkanDevice& dev = VulkanDevice::GetInstance(); + if (dev.InRenderPass()) + dev.EndRenderPass(); + dev.SubmitCommandBuffer(true); // Destroy the old swap chain, images, and surface. DestroySwapChain(); DestroySurface(); // Re-create the surface with the new native handle - m_window_info = new_wi; - m_surface = CreateVulkanSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), &m_window_info); - if (m_surface == VK_NULL_HANDLE) + if (!CreateSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), error)) return false; // The validation layers get angry at us if we don't call this before creating the swapchain. @@ -711,17 +736,13 @@ bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi) m_surface, &present_supported); if (res != VK_SUCCESS) { - LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: "); - return false; - } - if (!present_supported) - { - Panic("Recreated surface does not support presenting."); + Vulkan::SetErrorObject(error, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ", res); return false; } + AssertMsg(present_supported, "Recreated surface does not support presenting."); // Finally re-create the swap chain - if (!CreateSwapChain()) + if (!CreateSwapChain(dev, error) || !CreateSwapChainImages(dev, error)) { DestroySwapChain(); return false; @@ -729,12 +750,3 @@ bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi) return true; } - -void VulkanSwapChain::DestroySurface() -{ - if (m_surface == VK_NULL_HANDLE) - return; - - DestroyVulkanSurface(VulkanDevice::GetInstance().GetVulkanInstance(), &m_window_info, m_surface); - m_surface = VK_NULL_HANDLE; -} diff --git a/src/util/vulkan_swap_chain.h b/src/util/vulkan_swap_chain.h index cdca30961..f81ba1c60 100644 --- a/src/util/vulkan_swap_chain.h +++ b/src/util/vulkan_swap_chain.h @@ -3,6 +3,7 @@ #pragma once +#include "gpu_device.h" #include "vulkan_loader.h" #include "vulkan_texture.h" #include "window_info.h" @@ -14,41 +15,19 @@ #include #include -class VulkanSwapChain +class VulkanSwapChain final : public GPUSwapChain { public: - // We don't actually need +1 semaphores, or, more than one really. - // But, the validation layer gets cranky if we don't fence wait before the next image acquire. - // So, add an additional semaphore to ensure that we're never acquiring before fence waiting. - static constexpr u32 NUM_SEMAPHORES = 4; // Should be command buffers + 1 - - ~VulkanSwapChain(); - - // Creates a vulkan-renderable surface for the specified window handle. - static VkSurfaceKHR CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi); - - // Destroys a previously-created surface. - static void DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface); - - // Create a new swap chain from a pre-existing surface. - static std::unique_ptr Create(const WindowInfo& wi, VkSurfaceKHR surface, - VkPresentModeKHR present_mode, - std::optional exclusive_fullscreen_control); - - // Determines present mode to use. - static bool SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode); + VulkanSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle, + std::optional exclusive_fullscreen_control); + ~VulkanSwapChain() override; ALWAYS_INLINE VkSurfaceKHR GetSurface() const { return m_surface; } ALWAYS_INLINE VkSwapchainKHR GetSwapChain() const { return m_swap_chain; } ALWAYS_INLINE const VkSwapchainKHR* GetSwapChainPtr() const { return &m_swap_chain; } - ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; } - ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; } - ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; } - ALWAYS_INLINE float GetScale() const { return m_window_info.surface_scale; } ALWAYS_INLINE u32 GetCurrentImageIndex() const { return m_current_image; } ALWAYS_INLINE const u32* GetCurrentImageIndexPtr() const { return &m_current_image; } ALWAYS_INLINE u32 GetImageCount() const { return static_cast(m_images.size()); } - ALWAYS_INLINE VkFormat GetImageFormat() const { return m_format; } ALWAYS_INLINE VkImage GetCurrentImage() const { return m_images[m_current_image].image; } ALWAYS_INLINE VkImageView GetCurrentImageView() const { return m_images[m_current_image].view; } ALWAYS_INLINE VkFramebuffer GetCurrentFramebuffer() const { return m_images[m_current_image].framebuffer; } @@ -69,27 +48,31 @@ public: return &m_semaphores[m_current_semaphore].rendering_finished_semaphore; } + bool CreateSurface(VkInstance instance, VkPhysicalDevice physical_device, Error* error); + bool CreateSwapChain(VulkanDevice& dev, Error* error); + bool CreateSwapChainImages(VulkanDevice& dev, Error* error); + void Destroy(VulkanDevice& dev, bool wait_for_idle); + VkResult AcquireNextImage(); void ReleaseCurrentImage(); void ResetImageAcquireResult(); - bool RecreateSurface(const WindowInfo& new_wi); - bool ResizeSwapChain(u32 new_width = 0, u32 new_height = 0, float new_scale = 1.0f); + bool RecreateSurface(Error* error); - // Change vsync enabled state. This may fail as it causes a swapchain recreation. - bool SetPresentMode(VkPresentModeKHR present_mode); + bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) override; + bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) override; private: - VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode, - std::optional exclusive_fullscreen_control); + // We don't actually need +1 semaphores, or, more than one really. + // But, the validation layer gets cranky if we don't fence wait before the next image acquire. + // So, add an additional semaphore to ensure that we're never acquiring before fence waiting. + static constexpr u32 NUM_SEMAPHORES = 4; // Should be command buffers + 1 - static std::optional SelectSurfaceFormat(VkSurfaceKHR surface); - - bool CreateSwapChain(); - void DestroySwapChain(); + std::optional SelectSurfaceFormat(VkPhysicalDevice physdev, Error* error); + std::optional SelectPresentMode(VkPhysicalDevice physdev, GPUVSyncMode& vsync_mode, Error* error); void DestroySwapChainImages(); - + void DestroySwapChain(); void DestroySurface(); struct Image @@ -105,9 +88,13 @@ private: VkSemaphore rendering_finished_semaphore; }; - WindowInfo m_window_info; - VkSurfaceKHR m_surface = VK_NULL_HANDLE; + +#ifdef __APPLE__ + // On MacOS, we need to store a pointer to the metal layer as well. + void* m_metal_layer = nullptr; +#endif + VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE; std::vector m_images; @@ -116,7 +103,6 @@ private: u32 m_current_image = 0; u32 m_current_semaphore = 0; - VkFormat m_format = VK_FORMAT_UNDEFINED; VkPresentModeKHR m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; std::optional m_image_acquire_result; diff --git a/src/util/vulkan_texture.cpp b/src/util/vulkan_texture.cpp index 83bfc7b21..cb121ca44 100644 --- a/src/util/vulkan_texture.cpp +++ b/src/util/vulkan_texture.cpp @@ -10,7 +10,7 @@ #include "common/bitutils.h" #include "common/log.h" -LOG_CHANNEL(VulkanDevice); +LOG_CHANNEL(GPUDevice); static constexpr const VkComponentMapping s_identity_swizzle{ VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, diff --git a/src/util/window_info.cpp b/src/util/window_info.cpp index 7c19abeed..bc74d8a2b 100644 --- a/src/util/window_info.cpp +++ b/src/util/window_info.cpp @@ -11,21 +11,6 @@ LOG_CHANNEL(WindowInfo); -void WindowInfo::SetSurfaceless() -{ - type = Type::Surfaceless; - window_handle = nullptr; - surface_width = 0; - surface_height = 0; - surface_refresh_rate = 0.0f; - surface_scale = 1.0f; - surface_format = GPUTexture::Format::Unknown; - -#ifdef __APPLE__ - surface_handle = nullptr; -#endif -} - #if defined(_WIN32) #include "common/windows_headers.h" @@ -318,4 +303,4 @@ std::optional WindowInfo::QueryRefreshRateForWindow(const WindowInfo& wi) return std::nullopt; } -#endif \ No newline at end of file +#endif diff --git a/src/util/window_info.h b/src/util/window_info.h index 19a471ed6..61d9570c7 100644 --- a/src/util/window_info.h +++ b/src/util/window_info.h @@ -3,15 +3,15 @@ #pragma once -#include "gpu_texture.h" #include "common/types.h" +#include "gpu_texture.h" #include // Contains the information required to create a graphics context in a window. struct WindowInfo { - enum class Type + enum class Type : u8 { Surfaceless, Win32, @@ -19,27 +19,18 @@ struct WindowInfo Wayland, MacOS, Android, - Display, }; Type type = Type::Surfaceless; - void* display_connection = nullptr; - void* window_handle = nullptr; - u32 surface_width = 0; - u32 surface_height = 0; + GPUTexture::Format surface_format = GPUTexture::Format::Unknown; + u16 surface_width = 0; + u16 surface_height = 0; float surface_refresh_rate = 0.0f; float surface_scale = 1.0f; - GPUTexture::Format surface_format = GPUTexture::Format::Unknown; - - // Needed for macOS. -#ifdef __APPLE__ - void* surface_handle = nullptr; -#endif + void* display_connection = nullptr; + void* window_handle = nullptr; ALWAYS_INLINE bool IsSurfaceless() const { return type == Type::Surfaceless; } - // Changes the window to be surfaceless (i.e. no handle/size/etc). - void SetSurfaceless(); - static std::optional QueryRefreshRateForWindow(const WindowInfo& wi); };