diff --git a/ui/src/DeviceResources.cpp b/ui/src/DeviceResources.cpp index b2b44de0..c70bfe72 100644 --- a/ui/src/DeviceResources.cpp +++ b/ui/src/DeviceResources.cpp @@ -78,6 +78,8 @@ bool DeviceResources::EnsureForWindow(HWND hwnd) } if (hwndRenderTarget_) { + const FLOAT dpi = DpiForHwnd(); + hwndRenderTarget_->SetDpi(dpi, dpi); return true; } @@ -143,6 +145,8 @@ bool DeviceResources::EnsureForComposition(HWND hwnd) if (composition_ && deviceContext_ && swapChain_ && pixelWidth_ >= width && pixelHeight_ >= height) { + const FLOAT dpi = DpiForHwnd(); + deviceContext_->SetDpi(dpi, dpi); return true; } diff --git a/ui/tests/CMakeLists.txt b/ui/tests/CMakeLists.txt index da102b8e..9fb18873 100644 --- a/ui/tests/CMakeLists.txt +++ b/ui/tests/CMakeLists.txt @@ -2,6 +2,7 @@ set(TEST_EXECUTABLE_NAME "msimeui-tests") add_executable(${TEST_EXECUTABLE_NAME} src/main.cpp + src/test_device_resources.cpp src/test_fonts.cpp src/test_layout.cpp src/test_font_fallback.cpp diff --git a/ui/tests/src/test_device_resources.cpp b/ui/tests/src/test_device_resources.cpp new file mode 100644 index 00000000..76bf4b95 --- /dev/null +++ b/ui/tests/src/test_device_resources.cpp @@ -0,0 +1,83 @@ +#include "tests/includes/test_framework.h" + +#include "msimeui/DeviceResources.h" + +#include +#include + +namespace +{ +class HiddenWindow +{ + public: + HiddenWindow() + { + REQUIRE(SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))); + hwnd = CreateWindowExW(WS_EX_NOREDIRECTIONBITMAP, L"STATIC", L"DPI resource test", WS_POPUP, 0, 0, 256, 128, + nullptr, nullptr, GetModuleHandleW(nullptr), nullptr); + REQUIRE(hwnd != nullptr); + } + + ~HiddenWindow() + { + DestroyWindow(hwnd); + CoUninitialize(); + } + + HWND hwnd = nullptr; +}; + +void CheckReusedTargetDpi(bool composition) +{ + HiddenWindow window; + msimeui::DeviceResources resources; + const auto ensure = [&]() { + return composition ? resources.EnsureForComposition(window.hwnd) : resources.EnsureForWindow(window.hwnd); + }; + REQUIRE(ensure()); + ID2D1RenderTarget *const target = resources.GetRenderTarget(); + REQUIRE(target != nullptr); + const float windowDpi = static_cast(GetDpiForWindow(window.hwnd)); + + // Reproduce the retained render target from the previous monitor DPI without + // changing the user's display settings. Reuse must refresh DPI, not recreate it. + for (const float previousDpi : {windowDpi / 2.0f, 96.0f, 120.0f, 144.0f, 192.0f, windowDpi * 2.0f}) + { + target->SetDpi(previousDpi, previousDpi); + REQUIRE(ensure()); + REQUIRE(resources.GetRenderTarget() == target); + float dpiX = 0.0f; + float dpiY = 0.0f; + target->GetDpi(&dpiX, &dpiY); + std::printf("%s retained DPI %.0f -> window %.0f, target %.0f/%.0f\n", composition ? "composition" : "HWND", + previousDpi, windowDpi, dpiX, dpiY); + REQUIRE_NEAR(dpiX, windowDpi); + REQUIRE_NEAR(dpiY, windowDpi); + const D2D1_SIZE_F size = target->GetSize(); + const D2D1_SIZE_U pixels = target->GetPixelSize(); + REQUIRE_NEAR(size.width, pixels.width * 96.0f / windowDpi); + REQUIRE_NEAR(size.height, pixels.height * 96.0f / windowDpi); + } + + // A smaller client area also reuses the larger composition surface. + REQUIRE(SetWindowPos(window.hwnd, nullptr, 0, 0, 128, 64, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE)); + target->SetDpi(windowDpi * 1.25f, windowDpi * 1.25f); + REQUIRE(ensure()); + REQUIRE(resources.GetRenderTarget() == target); + float dpiX = 0.0f; + float dpiY = 0.0f; + target->GetDpi(&dpiX, &dpiY); + REQUIRE_NEAR(dpiX, windowDpi); + REQUIRE_NEAR(dpiY, windowDpi); +} +} // namespace + +TEST_CASE(hwnd_target_refreshes_dpi_when_reused) +{ + CheckReusedTargetDpi(false); +} + +TEST_CASE(composition_target_refreshes_dpi_when_reused) +{ + CheckReusedTargetDpi(true); +}