From ce31c859830f2a70c4c7e32cb4086ef693ba4b8e Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 28 Feb 2020 16:59:48 +1000 Subject: [PATCH] SDL: Support high-dpi displays --- src/duckstation-sdl/CMakeLists.txt | 1 + src/duckstation-sdl/d3d11_host_display.cpp | 16 ++- src/duckstation-sdl/duckstation-sdl.manifest | 17 +++ src/duckstation-sdl/duckstation-sdl.vcxproj | 5 +- .../duckstation-sdl.vcxproj.filters | 3 + src/duckstation-sdl/imgui_impl_sdl.cpp | 9 -- src/duckstation-sdl/opengl_host_display.cpp | 8 +- src/duckstation-sdl/sdl_host_interface.cpp | 134 +++++++++++++----- src/duckstation-sdl/sdl_host_interface.h | 3 + 9 files changed, 148 insertions(+), 48 deletions(-) create mode 100644 src/duckstation-sdl/duckstation-sdl.manifest diff --git a/src/duckstation-sdl/CMakeLists.txt b/src/duckstation-sdl/CMakeLists.txt index d24ccc3b3..8f9caf7c9 100644 --- a/src/duckstation-sdl/CMakeLists.txt +++ b/src/duckstation-sdl/CMakeLists.txt @@ -17,6 +17,7 @@ if(WIN32) target_sources(duckstation-sdl PRIVATE d3d11_host_display.cpp d3d11_host_display.h + duckstation-sdl.manifest ) target_link_libraries(duckstation-sdl PRIVATE d3d11.lib dxgi.lib ${SDL2MAIN_LIBRARIES}) endif() diff --git a/src/duckstation-sdl/d3d11_host_display.cpp b/src/duckstation-sdl/d3d11_host_display.cpp index 0e95016ca..d972a6450 100644 --- a/src/duckstation-sdl/d3d11_host_display.cpp +++ b/src/duckstation-sdl/d3d11_host_display.cpp @@ -156,10 +156,6 @@ std::tuple D3D11HostDisplay::GetWindowSize() const void D3D11HostDisplay::WindowResized() { - SDL_GetWindowSize(m_window, &m_window_width, &m_window_height); - ImGui::GetIO().DisplaySize.x = static_cast(m_window_width); - ImGui::GetIO().DisplaySize.y = static_cast(m_window_height); - m_swap_chain_rtv.Reset(); HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, @@ -169,6 +165,15 @@ void D3D11HostDisplay::WindowResized() if (!CreateSwapChainRTV()) Panic("Failed to recreate swap chain RTV after resize"); + + DXGI_SWAP_CHAIN_DESC desc; + if (SUCCEEDED(m_swap_chain->GetDesc(&desc))) + { + m_window_width = static_cast(desc.BufferDesc.Width); + m_window_height = static_cast(desc.BufferDesc.Height); + ImGui::GetIO().DisplaySize.x = static_cast(m_window_width); + ImGui::GetIO().DisplaySize.y = static_cast(m_window_height); + } } bool D3D11HostDisplay::CreateD3DDevice(bool debug_device) @@ -334,6 +339,9 @@ bool D3D11HostDisplay::CreateD3DResources() bool D3D11HostDisplay::CreateImGuiContext() { + ImGui::GetIO().DisplaySize.x = static_cast(m_window_width); + ImGui::GetIO().DisplaySize.y = static_cast(m_window_height); + if (!ImGui_ImplSDL2_InitForD3D(m_window) || !ImGui_ImplDX11_Init(m_device.Get(), m_context.Get())) return false; diff --git a/src/duckstation-sdl/duckstation-sdl.manifest b/src/duckstation-sdl/duckstation-sdl.manifest new file mode 100644 index 000000000..6bbce6d9b --- /dev/null +++ b/src/duckstation-sdl/duckstation-sdl.manifest @@ -0,0 +1,17 @@ + + + + + PerMonitorV2 + true + + + + + + + + + + + \ No newline at end of file diff --git a/src/duckstation-sdl/duckstation-sdl.vcxproj b/src/duckstation-sdl/duckstation-sdl.vcxproj index b924b00c4..8048f331a 100644 --- a/src/duckstation-sdl/duckstation-sdl.vcxproj +++ b/src/duckstation-sdl/duckstation-sdl.vcxproj @@ -66,6 +66,9 @@ + + + {DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9} Win32Proj @@ -389,4 +392,4 @@ - + \ No newline at end of file diff --git a/src/duckstation-sdl/duckstation-sdl.vcxproj.filters b/src/duckstation-sdl/duckstation-sdl.vcxproj.filters index 0d8aef02b..7c6071779 100644 --- a/src/duckstation-sdl/duckstation-sdl.vcxproj.filters +++ b/src/duckstation-sdl/duckstation-sdl.vcxproj.filters @@ -15,4 +15,7 @@ + + + \ No newline at end of file diff --git a/src/duckstation-sdl/imgui_impl_sdl.cpp b/src/duckstation-sdl/imgui_impl_sdl.cpp index 892b2ccb1..c3177cbe6 100644 --- a/src/duckstation-sdl/imgui_impl_sdl.cpp +++ b/src/duckstation-sdl/imgui_impl_sdl.cpp @@ -388,15 +388,6 @@ void ImGui_ImplSDL2_NewFrame(SDL_Window* window) ImGuiIO& io = ImGui::GetIO(); IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer back-end. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); - // Setup display size (every frame to accommodate for window resizing) - int w, h; - int display_w, display_h; - SDL_GetWindowSize(window, &w, &h); - SDL_GL_GetDrawableSize(window, &display_w, &display_h); - io.DisplaySize = ImVec2((float)w, (float)h); - if (w > 0 && h > 0) - io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); - // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) static Uint64 frequency = SDL_GetPerformanceFrequency(); Uint64 current_time = SDL_GetPerformanceCounter(); diff --git a/src/duckstation-sdl/opengl_host_display.cpp b/src/duckstation-sdl/opengl_host_display.cpp index b70b4ed01..0daf42fba 100644 --- a/src/duckstation-sdl/opengl_host_display.cpp +++ b/src/duckstation-sdl/opengl_host_display.cpp @@ -138,7 +138,7 @@ std::tuple OpenGLHostDisplay::GetWindowSize() const void OpenGLHostDisplay::WindowResized() { - SDL_GetWindowSize(m_window, &m_window_width, &m_window_height); + SDL_GL_GetDrawableSize(m_window, &m_window_width, &m_window_height); ImGui::GetIO().DisplaySize.x = static_cast(m_window_width); ImGui::GetIO().DisplaySize.y = static_cast(m_window_height); } @@ -264,6 +264,9 @@ bool OpenGLHostDisplay::CreateGLContext(bool debug_device) glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); } + // this can change due to retina scaling on macos? + SDL_GL_GetDrawableSize(m_window, &m_window_width, &m_window_height); + // start with vsync on SDL_GL_SetSwapInterval(1); return true; @@ -271,6 +274,9 @@ bool OpenGLHostDisplay::CreateGLContext(bool debug_device) bool OpenGLHostDisplay::CreateImGuiContext() { + ImGui::GetIO().DisplaySize.x = static_cast(m_window_width); + ImGui::GetIO().DisplaySize.y = static_cast(m_window_height); + if (!ImGui_ImplSDL2_InitForOpenGL(m_window, m_gl_context) || !ImGui_ImplOpenGL3_Init(GetGLSLVersionString())) return false; diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 69d68f0d5..d744e6ac0 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -43,17 +43,60 @@ SDLHostInterface::~SDLHostInterface() DestroySDLWindow(); } +float SDLHostInterface::GetDPIScaleFactor(SDL_Window* window) +{ +#ifdef __APPLE__ + static constexpr float DEFAULT_DPI = 72.0f; +#else + static constexpr float DEFAULT_DPI = 96.0f; +#endif + + if (!window) + { + SDL_Window* dummy_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1, 1, + SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); + if (!dummy_window) + return 1.0f; + + const float scale = GetDPIScaleFactor(dummy_window); + + SDL_DestroyWindow(dummy_window); + + return scale; + } + + int display_index = SDL_GetWindowDisplayIndex(window); + float display_dpi = DEFAULT_DPI; + if (SDL_GetDisplayDPI(display_index, &display_dpi, nullptr, nullptr) != 0) + return 1.0f; + + return display_dpi / DEFAULT_DPI; +} + bool SDLHostInterface::CreateSDLWindow() { - constexpr u32 DEFAULT_WINDOW_WIDTH = 900; - constexpr u32 DEFAULT_WINDOW_HEIGHT = 700; + static constexpr u32 DEFAULT_WINDOW_WIDTH = 900; + static constexpr u32 DEFAULT_WINDOW_HEIGHT = 700; // Create window. const u32 window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | (UseOpenGLRenderer() ? SDL_WINDOW_OPENGL : 0); - m_window = SDL_CreateWindow("DuckStation", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DEFAULT_WINDOW_WIDTH, - DEFAULT_WINDOW_HEIGHT, window_flags); + u32 window_width = DEFAULT_WINDOW_WIDTH; + u32 window_height = DEFAULT_WINDOW_HEIGHT; + + // macOS does DPI scaling differently.. +#ifndef __APPLE__ + { + // scale by default monitor's DPI + float scale = GetDPIScaleFactor(nullptr); + window_width = static_cast(std::round(static_cast(window_width) * scale)); + window_height = static_cast(std::round(static_cast(window_height) * scale)); + } +#endif + + m_window = SDL_CreateWindow("DuckStation", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, + window_height, window_flags); if (!m_window) return false; @@ -113,12 +156,24 @@ void SDLHostInterface::DestroyDisplay() void SDLHostInterface::CreateImGuiContext() { + const float framebuffer_scale = GetDPIScaleFactor(m_window); + ImGui::CreateContext(); ImGui::GetIO().IniFilename = nullptr; ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + ImGui::GetIO().DisplayFramebufferScale.x = framebuffer_scale; + ImGui::GetIO().DisplayFramebufferScale.y = framebuffer_scale; + ImGui::GetStyle().ScaleAllSizes(framebuffer_scale); ImGui::StyleColorsDarker(); - ImGui::AddRobotoRegularFont(); + ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale); +} + +void SDLHostInterface::UpdateFramebufferScale() +{ + const float framebuffer_scale = GetDPIScaleFactor(m_window); + ImGui::GetIO().DisplayFramebufferScale.x = framebuffer_scale; + ImGui::GetIO().DisplayFramebufferScale.y = framebuffer_scale; } bool SDLHostInterface::AcquireHostDisplay() @@ -315,7 +370,14 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) case SDL_WINDOWEVENT: { if (event->window.event == SDL_WINDOWEVENT_RESIZED) + { m_display->WindowResized(); + UpdateFramebufferScale(); + } + else if (event->window.event == SDL_WINDOWEVENT_MOVED) + { + UpdateFramebufferScale(); + } } break; @@ -722,9 +784,11 @@ void SDLHostInterface::DrawMainMenuBar() if (m_system) { + const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; + if (!m_paused) { - ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - 210.0f); + ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (210.0f * framebuffer_scale)); const float speed = m_system->GetEmulationSpeed(); const u32 rounded_speed = static_cast(std::round(speed)); @@ -735,15 +799,15 @@ void SDLHostInterface::DrawMainMenuBar() else ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%u%%", rounded_speed); - ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - 165.0f); + ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (165.0f * framebuffer_scale)); ImGui::Text("FPS: %.2f", m_system->GetFPS()); - ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - 80.0f); + ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (80.0f * framebuffer_scale)); ImGui::Text("VPS: %.2f", m_system->GetVPS()); } else { - ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - 50.0f); + ImGui::SetCursorPosX(ImGui::GetIO().DisplaySize.x - (50.0f * framebuffer_scale)); ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Paused"); } } @@ -875,12 +939,14 @@ void SDLHostInterface::DrawDebugMenu() void SDLHostInterface::DrawPoweredOffWindow() { - constexpr int WINDOW_WIDTH = 400; - constexpr int WINDOW_HEIGHT = 650; - constexpr int BUTTON_WIDTH = 200; - constexpr int BUTTON_HEIGHT = 40; + static constexpr int WINDOW_WIDTH = 400; + static constexpr int WINDOW_HEIGHT = 650; + static constexpr int BUTTON_WIDTH = 200; + static constexpr int BUTTON_HEIGHT = 40; + const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; - ImGui::SetNextWindowSize(ImVec2(WINDOW_WIDTH, WINDOW_HEIGHT)); + ImGui::SetNextWindowSize(ImVec2(static_cast(WINDOW_WIDTH) * framebuffer_scale, + static_cast(WINDOW_HEIGHT) * framebuffer_scale)); ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); @@ -892,15 +958,17 @@ void SDLHostInterface::DrawPoweredOffWindow() ImGui::End(); } - ImGui::SetCursorPosX((WINDOW_WIDTH - APP_ICON_WIDTH) / 2); - ImGui::Image(m_app_icon_texture->GetHandle(), ImVec2(APP_ICON_WIDTH, APP_ICON_HEIGHT)); - ImGui::SetCursorPosY(APP_ICON_HEIGHT + 32); + ImGui::SetCursorPosX(static_cast((WINDOW_WIDTH - APP_ICON_WIDTH) / 2) * framebuffer_scale); + ImGui::Image(m_app_icon_texture->GetHandle(), ImVec2(static_cast(APP_ICON_WIDTH) * framebuffer_scale, + static_cast(APP_ICON_HEIGHT) * framebuffer_scale)); + ImGui::SetCursorPosY(static_cast(APP_ICON_HEIGHT + 32) * framebuffer_scale); - static const ImVec2 button_size(static_cast(BUTTON_WIDTH), static_cast(BUTTON_HEIGHT)); - constexpr float button_left = static_cast((WINDOW_WIDTH - BUTTON_WIDTH) / 2); + const ImVec2 button_size(static_cast(BUTTON_WIDTH) * framebuffer_scale, + static_cast(BUTTON_HEIGHT) * framebuffer_scale); + const float button_left = static_cast((WINDOW_WIDTH - BUTTON_WIDTH) / 2) * framebuffer_scale; - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * framebuffer_scale); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * framebuffer_scale); ImGui::PushStyleColor(ImGuiCol_Button, 0xFF202020); ImGui::PushStyleColor(ImGuiCol_ButtonActive, 0xFF808080); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, 0xFF575757); @@ -972,9 +1040,11 @@ static bool DrawSettingsSectionHeader(const char* title) void SDLHostInterface::DrawSettingsWindow() { + const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; + ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); - ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(500 * framebuffer_scale, 400.0f * framebuffer_scale), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Settings", &m_settings_window_open, ImGuiWindowFlags_NoResize)) { @@ -986,7 +1056,7 @@ void SDLHostInterface::DrawSettingsWindow() if (ImGui::BeginTabBar("SettingsTabBar", 0)) { - const float indent = 150.0f; + const float indent = 150.0f * framebuffer_scale; if (ImGui::BeginTabItem("General")) { @@ -1203,12 +1273,6 @@ void SDLHostInterface::DrawSettingsWindow() ImGui::EndTabBar(); } - const auto window_size = ImGui::GetWindowSize(); - ImGui::SetCursorPosX(window_size.x - 50.0f); - ImGui::SetCursorPosY(window_size.y - 30.0f); - if (ImGui::Button("Close")) - m_settings_window_open = false; - ImGui::End(); if (settings_changed) @@ -1222,6 +1286,8 @@ void SDLHostInterface::DrawSettingsWindow() void SDLHostInterface::DrawAboutWindow() { + const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; + ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); @@ -1243,8 +1309,8 @@ void SDLHostInterface::DrawAboutWindow() ImGui::NewLine(); - ImGui::SetCursorPosX((ImGui::GetWindowSize().x - 60.0f) / 2.0f); - if (ImGui::Button("Close", ImVec2(60.0f, 20.0f))) + ImGui::SetCursorPosX((ImGui::GetWindowSize().x - (60.0f * framebuffer_scale)) / 2.0f); + if (ImGui::Button("Close", ImVec2(60.0f * framebuffer_scale, 20.0f * framebuffer_scale))) m_about_window_open = false; ImGui::EndPopup(); @@ -1252,11 +1318,13 @@ void SDLHostInterface::DrawAboutWindow() bool SDLHostInterface::DrawFileChooser(const char* label, std::string* path, const char* filter /* = nullptr */) { - ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - 50.0f); + const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x; + + ImGui::SetNextItemWidth((ImGui::CalcItemWidth() - 50.0f) * framebuffer_scale); bool result = ImGui::InputText(label, path); ImGui::SameLine(); - ImGui::SetNextItemWidth(50.0f); + ImGui::SetNextItemWidth(50.0f * framebuffer_scale); if (ImGui::Button("...")) { nfdchar_t* out_path = nullptr; diff --git a/src/duckstation-sdl/sdl_host_interface.h b/src/duckstation-sdl/sdl_host_interface.h index 2df660f43..3589170b9 100644 --- a/src/duckstation-sdl/sdl_host_interface.h +++ b/src/duckstation-sdl/sdl_host_interface.h @@ -78,11 +78,14 @@ private: bool UseOpenGLRenderer() const { return true; } #endif + static float GetDPIScaleFactor(SDL_Window* window); + bool CreateSDLWindow(); void DestroySDLWindow(); bool CreateDisplay(); void DestroyDisplay(); void CreateImGuiContext(); + void UpdateFramebufferScale(); /// Executes a callback later, after the UI has finished rendering. Needed to boot while rendering ImGui. void RunLater(std::function callback);