SDL: Support high-dpi displays

This commit is contained in:
Connor McLaughlin 2020-02-28 16:59:48 +10:00
parent a0a0cd48fa
commit ce31c85983
9 changed files with 148 additions and 48 deletions

View file

@ -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()

View file

@ -156,10 +156,6 @@ std::tuple<u32, u32> D3D11HostDisplay::GetWindowSize() const
void D3D11HostDisplay::WindowResized()
{
SDL_GetWindowSize(m_window, &m_window_width, &m_window_height);
ImGui::GetIO().DisplaySize.x = static_cast<float>(m_window_width);
ImGui::GetIO().DisplaySize.y = static_cast<float>(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<int>(desc.BufferDesc.Width);
m_window_height = static_cast<int>(desc.BufferDesc.Height);
ImGui::GetIO().DisplaySize.x = static_cast<float>(m_window_width);
ImGui::GetIO().DisplaySize.y = static_cast<float>(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<float>(m_window_width);
ImGui::GetIO().DisplaySize.y = static_cast<float>(m_window_height);
if (!ImGui_ImplSDL2_InitForD3D(m_window) || !ImGui_ImplDX11_Init(m_device.Get(), m_context.Get()))
return false;

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

View file

@ -66,6 +66,9 @@
<ClInclude Include="sdl_host_interface.h" />
<ClInclude Include="sdl_settings_interface.h" />
</ItemGroup>
<ItemGroup>
<Manifest Include="duckstation-sdl.manifest" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
@ -389,4 +392,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View file

@ -15,4 +15,7 @@
<ClInclude Include="sdl_settings_interface.h" />
<ClInclude Include="imgui_impl_sdl.h" />
</ItemGroup>
<ItemGroup>
<Manifest Include="duckstation-sdl.manifest" />
</ItemGroup>
</Project>

View file

@ -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();

View file

@ -138,7 +138,7 @@ std::tuple<u32, u32> 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<float>(m_window_width);
ImGui::GetIO().DisplaySize.y = static_cast<float>(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<float>(m_window_width);
ImGui::GetIO().DisplaySize.y = static_cast<float>(m_window_height);
if (!ImGui_ImplSDL2_InitForOpenGL(m_window, m_gl_context) || !ImGui_ImplOpenGL3_Init(GetGLSLVersionString()))
return false;

View file

@ -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<u32>(std::round(static_cast<float>(window_width) * scale));
window_height = static_cast<u32>(std::round(static_cast<float>(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<u32>(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<float>(WINDOW_WIDTH) * framebuffer_scale,
static_cast<float>(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<float>((WINDOW_WIDTH - APP_ICON_WIDTH) / 2) * framebuffer_scale);
ImGui::Image(m_app_icon_texture->GetHandle(), ImVec2(static_cast<float>(APP_ICON_WIDTH) * framebuffer_scale,
static_cast<float>(APP_ICON_HEIGHT) * framebuffer_scale));
ImGui::SetCursorPosY(static_cast<float>(APP_ICON_HEIGHT + 32) * framebuffer_scale);
static const ImVec2 button_size(static_cast<float>(BUTTON_WIDTH), static_cast<float>(BUTTON_HEIGHT));
constexpr float button_left = static_cast<float>((WINDOW_WIDTH - BUTTON_WIDTH) / 2);
const ImVec2 button_size(static_cast<float>(BUTTON_WIDTH) * framebuffer_scale,
static_cast<float>(BUTTON_HEIGHT) * framebuffer_scale);
const float button_left = static_cast<float>((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;

View file

@ -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<void()> callback);