Skip to content

HDPI support #2826

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: docking
Choose a base branch
from
Open

HDPI support #2826

wants to merge 34 commits into from

Conversation

rokups
Copy link
Contributor

@rokups rokups commented Oct 2, 2019

This PR is supposed to solve HDPI issues once and for all (and close #1786). This is still a work in progress and incomplete.

With this PR user is expected to render UI at 96 DPI. ImGuiStyle::ScaleAllSizes() becomes obsolete in this case. Mouse position is scaled according to DPI window is using. When window is dragged between screens of different DPIs - DPI switch is performed when dragging stops (window rescales). Fonts are duplicated for each DPI and switched automatically when window moves between screens of different DPIs.

  • Implement per-dpi fonts
  • Implement support in OpenGL examples
  • Implement support in Vulkan examples
  • Implement support in windows examples
  • Implement support in MacOS / iOS examples
  • Fix shape instability problem
  • Implement some logic for when to use
    what DPI. For example system may report monitor with 1.1 DPI which makes window scaling pointless and we should just use DPI scale of 1.0.
  • Verify DPI-awareness handling on GLFW and remove manually enabling it on windows if needed.
  • Verify DPI-awareness handling on SDL and remove manually enabling it on windows if needed.
  • Password character masking broken
  • Popup window positioning broken
  • Multi-monitor handling on MacOS broken
  • Mouse position is not recognized correctly when window has DPI of monitor A, but also spans into monitor B and mouse is on monitor B.

Thanks to @themd for anti-alias fringe scale implementation.
Thanks to @mosra for fleshing out this method and giving me an example on how to proceed.


I am also researching a different concept proposed by @ocornut. Idea is that we create fonts per each different DPI (like this in this PR) and do automatic font swapping. There are no virtual grids and scaling is reflected in font->FontSize property. In addition to that - style is also automatically scaled according to current window DPI. There are no virtual grids, everything is still measured in pixels. Therefore user must do extra work for their application to support HDPI screens. Essentially user must not use hardcoded sizes. Everything must be relative to something else, most of the time relative to the font size or values in the style.

https://github.com/rokups/imgui/tree/hdpi-support-fontscale-master (based on master branch)
https://github.com/rokups/imgui/tree/hdpi-support-fontscale-viewport (based on docking branch)

@ocornut ocornut added the dpi label Oct 2, 2019
@rokups
Copy link
Contributor Author

rokups commented Oct 7, 2019

Still battling widget shaking.

Few observations:

  • Shaking is obviously caused due to misalignment to the pixel grid.
  • Disabling all the rounding (basically making ImFloor noop) and translating window by fractional coordinates (like window->Pos.x += 1.123) will result in shaking even if DPI=1.0f.
  • MacOS avoids this issue purely because retina screens use 2x scale. DPI=2.0f results in no shaking on other platforms as well.
  • Aligning window coordinates/size to physical pixel grid solves shaking of the window edges, however widgets within the window keep shaking.

This is a lead to work with, however i dont yet see how i could easily align all sizes to pixel grid. The main difference is that to move window by one pixel user expects to write window->Pos.x += 1.f``, but we actually need window->Pos.x += 1.f / DPI(or multiple of1.f / DPI) to keep everything aligned. What is worse - window size/pos is easy enough to fix automatically, but we need same alignment for every single point used in rendering. Modifying ImDrawList` functions to do this alignment could be an option maybe.

@rokups
Copy link
Contributor Author

rokups commented Oct 7, 2019

...brainstorming continued. So pixel grind misalignment... Black grid is physical pixels. Red grid is our virtual 96 DPI grid (where DPI=1.5f). Problem is rather obvious now.
image
Position (1.f, 1.f) ends up at the center of physical pixel at (2, 2).

Or maybe we can approach the problem from another angle. As we know fractional scaling works "fine" in magnum's web demo: https://magnum.graphics/showcase/imgui/?magnum-dpi-scaling=1.5
You can see that there is no shaking when window is moved even though it is upscaled. I made a few screenshots of same area, the only difference is window moved down by 1 pixel.
imgui
They look identical at the first sight, however you may notice some extra antialiasing on letter "e". Opening image in some application and zooming in clearly shows that there is some kind of antialiasing going on. @mosra do you have any idea where antialiasing is coming from in your web demo? Is it set up in magnum or is it browser's doing?

Edit: This looks relevant. https://www.opengl.org/archives/resources/code/samples/advanced/advanced97/notes/node63.html

Edit:
This bit enables similar behavior like in magnum webdemo. Default font jitters somewhat due to it's pixely nature, but other fonts (like Roboto-Medium) look quite ok.

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8);

@rokups rokups force-pushed the hdpi-support branch 2 times, most recently from cacd54b to 078d11f Compare October 29, 2019 14:48
@rokups
Copy link
Contributor Author

rokups commented Oct 29, 2019

Shaking fix is in place. I also made screenshots of docking branch vs hdpi branch to see if my changes do not break stuff in default setting. I am fairly happy with results. You may compare imgui_capture-docking vs imgui_capture-hdpi using something like diffimg. There are some differences in text rendering and widget sizes which mainly come from here and here. Rounding here eliminates shaking.

Approach i have taken is to do rounding and flooring to values that directly map to physical pixels on the screen. This makes flooring and rounding operations expensive as they now need access to DPI of current window and now are fat and ugly. I will still rework and clean up these once we figure out if this is a correct approach.

round(f * dpi + 0.0001f) / dpi;

This hack. In some cases rounding to multiples of physical pixel is supposed to yield value with fraction part being exactly 0.5f. However after performing rounding to dpi we end up with fractional part being 0.4999999.f all due to floating point imprecision. This particular instance resulted in a very rare widget shaking that happens only in some parts of the screen. A better solution would be great, but i am unsure if there is one.

@@ -419,15 +419,15 @@ typedef DPI_AWARENESS_CONTEXT(WINAPI * PFN_SetThreadDpiAwarenessContext)(DPI_AWA

void ImGui_ImplWin32_EnableDpiAwareness()
{
// if (IsWindows10OrGreater()) // FIXME-DPI: This needs a manifest to succeed. Instead we try to grab the function pointer.
/* if (IsWindows10OrGreater()) // FIXME-DPI: This needs a manifest to succeed. Instead we try to grab the function pointer.
{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: need an explanation about why that chunk of ImGui_ImplWin32_EnableDpiAwareness() was commented. You said you ran into a problem with it, details would be good.

int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x * draw_data->OwnerViewport->DpiScale);
int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y * draw_data->OwnerViewport->DpiScale);
if (fb_width == 0 || fb_height == 0)
return;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't DpiScale baked into FramebufferScale ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FramebufferScale is a workaround for MacOS having window size reported to user as if it was 96 DPI while it covers 2x as many pixels. Eventually either FramebufferScale or DpiScale has to disappear here.

@rokups rokups force-pushed the hdpi-support branch 8 times, most recently from b360180 to a843af4 Compare November 11, 2019 15:39
@rokups
Copy link
Contributor Author

rokups commented Nov 12, 2019

I think we are at the point where we can start taking a close look at current implementation and what we need to do further. Here are some things to consider:

ImGuiConfigFlags_DpiEnableScaleFonts flag was removed as it no longer makes sense.

ImGuiConfigFlags_DpiEnableScaleViewports flag could be either removed or implemented. At the moment DPI scaling is enabled when viewports are enabled.

io.DisplayFramebufferScale works as before when viewports are disabled and is ignored when viewports are enabled. draw_data->FramebufferScale is populated with {viewport->DpiScale, viewport->DpiScale} when viewports are enabled. This essentially gave us HDPI support in all samples that already supported io.DisplayFramebufferScale without changing them.

On MacOS user sets up io.DisplaySize and uses native coordinates in native window manipulation as before. Library will scale coordinates behind the scenes as necessary.

A workaround for SDL was added as library reports invalid DPI on retina screens (bug).

Need to decide what we do about this bit. So far i could not think of a better solution.

I also added lower limit of 1.0f to DPI scale. I do not think it is worth it to concern us with ultra low DPI screens at least for now.

@rokups
Copy link
Contributor Author

rokups commented Dec 2, 2019

I implemented support for ImGuiConfigFlags_DpiEnableScaleViewports, but i am unsure of it's usefulness. Do we want to keep this flag or should it be removed altogether and enabling viewports should imply dpi scaling?

At this point i am fairly certain that:

  1. IO.DisplayFramebufferScale should only be used when viewports are not enabled. Enabling viewports provides dpi information with platform monitor information.
  2. draw_data->FramebufferScale gets monitor dpi scale value or IO.DisplayFramebufferScale value depending on settings. Backend code does not need any changes.

Now there is a small discrepancy. IO.DisplayFramebufferScale is ImVec2 and monitor->DpiScale is float. I am not really aware of systems where different scales for each axis would make sense. However at least SDL does report per-axis dpi scales and they are different on my machine:

horizontal = {float} 108.917923
vertical = {float} 108.85714

We should use same type for dpi scales everywhere, so we either switch to float or to ImVec2. I suspect float is enough and we should use it. We do not really deal with pixels of non-square shapes. Any objections?

@Milerius
Copy link

Milerius commented Dec 5, 2019

Hey really nice PR !

We are near something usable I guess !

@ocornut
Copy link
Owner

ocornut commented Dec 25, 2019

ImGuiInputTextFlags_Password seems broken somehow (this pushes a temporary font).

@naezith
Copy link

naezith commented Dec 25, 2019

ImGuiInputTextFlags_Password seems broken somehow (this pushes a temporary font).

Only the hiding with *** part is broken. Otherwise it doesn't let you copy / cut.

@Milerius
Copy link

Milerius commented Dec 27, 2019

Hello it seems that when I use several monitors when moving the application window from one monitor to another part of the window becomes invisible.

Main monitor:

Capture d’écran 2019-12-27 à 13 47 50

When switching to the second monitor:
Capture d’écran 2019-12-27 à 13 48 41

The problem persists after restarting the application

SDL2 + HIGH DPI + Multiviewport.
OSX 10.15.1 Catalina.
MacBook Pro 2016.

@thedmd
Copy link
Contributor

thedmd commented Jan 3, 2020

I was annoyed by keeping to squint at my screen, so here I am. : )

This PR is nice reference, thanks! My personal goal here is to reduce amount of changes in widgets and imgui internals.

I tried a bit different approach trying to keep DPI problem on ImDrawXXX side. So far I managed to patch font generation to handle ImFontConfig::DpiScale. Generated font isn't aware of DPI and all metrics like for regular fonts. This way I don't have to touch any drawing routine.

Font is setup to be oversampled if DPI is > 1. This should look good as long as there is one pixel border around every glyph.

    float scaling = ImGui_ImplWin32_GetDpiScaleForHwnd(hwnd);

    ImFontConfig fontConfig;
    fontConfig.SizePixels = 13.0f;
    fontConfig.DpiScale = scaling;
    fontConfig.OversampleH = fontConfig.OversampleV = scaling > 1 ? 2 : 1;
    fontConfig.PixelSnapH = scaling > 1.0f ? false : true;
    io.Fonts->AddFontDefault(&fontConfig);

Result:
image

Widgets drifting is still a thing (on my side). PR is trying to deal with that with large amount of rounding. I think problem is rather in simplistic implementation of draw routines, ImDrawList::AddRect and friends.
Adding a fringe to most primitives should remove sharp edges on half-pixel boundaries. I will probably experiment with this approach.

@rokups You can use ImDrawData::ScaleClipRects instead of modifying clip rects calculations in backends.

@Boscop
Copy link

Boscop commented Aug 11, 2020

Any update on this?
I only have a 4k screen and I wrote an imgui based app.
How can I upscale it? This is very important for me, the small font is hardly readable..

@ocornut
Copy link
Owner

ocornut commented Aug 11, 2020

Any update on this?
I only have a 4k screen and I wrote an imgui based app.
How can I upscale it? This is very important for me, the small font is hardly readable..

The difficulty of what we are trying to do is in order to support multiple-varying DPI scale simultaneously (for multi-viewports over multi-monitor with different scale). Outside of that specific case, handling hi-dpi/4k is pretty much a matter of loading your font scaled by the right amount + calling ImGuiStyle::ScaleAllSizes() on your style. You should be able to do that already without any extra patch/work. Make sure to round your font size to nearest integer.

@Boscop
Copy link

Boscop commented Aug 14, 2020

@ocornut Thanks! I would already be happy to only support one DPI (not multiple monitors with different DPIs for the same window). I'm using the Rust bindings, loading my font like this now:
https://github.com/Gekkio/imgui-rs/blob/d5be602f73c51896838318958cd6c930f18cc8c3/imgui-examples/examples/support/mod.rs#L51-L71

It seems to work, but it would be more correct to round font_size to the nearest int, right?
Do I need to change rasterizer_multiply at all?
And do I still need to call ScaleAllSizes after this?

@rokups
Copy link
Contributor Author

rokups commented Aug 31, 2020

If anyone is interested - there is another experimental branch implementing a different approach we are exploring.

This approach implements multiple font atlases for each unique DPI value and automatic style rescaling. Naturally solution is not fleshed out and there will surely be dragons. We would appreciate any feedback. I also linked a gist describing some issues from which you can infer changes that are needed to support HDPI. Only SDL OpenGL3 example has automatic DPI scaling enabled.

https://github.com/rokups/imgui/tree/hdpi-support-fonstscale-viewport-3
https://gist.github.com/rokups/a322dff8ee885f75ee8326207ea0bf75

I would already be happy to only support one DPI

@Boscop to do that you should multiply font size by your desired DPI scale and also use style.ScaleAllSizes(dpi_scale). It wont be perfect, but about 95% good.

@halx99 halx99 mentioned this pull request Sep 3, 2020
67 tasks
@ocornut ocornut force-pushed the docking branch 2 times, most recently from 4a6447b to 6822493 Compare October 8, 2020 14:06
ocornut added a commit that referenced this pull request May 15, 2025
…mebufferScale() + Backends: GLFW, SDL2, SDL3, Apple: added support. (#1065, #1542, #1676, #1786, #2826, #3757, #5081, #5580, #5592, #6465, #7273, #7779 etc.)

)

Metal backend is not in charge of writing to DpiScale/FramebufferScale (tho it was a neat workaround).
elbadcode added a commit to elbadcode/imgui that referenced this pull request Jul 3, 2025
* origin/docking-dev: (1059 commits)
  Backends: Vulkan: Fix failing assertion for platforms where viewports are not supported (ocornut#8734)
  Backends: GLFW: Fixed not installing WndProc hook in all GLFW version, so AddMouseSourceEvent() logic was missing for some viewports.
  Backends: GLFW: Fixed crash when using GLFW 3.3 (ocornut#8713, ocornut#8676, ocornut#8239, ocornut#8069)
  Backends: warning fixes (for docking branch).
  Backends: GLFW: amend for multi-context support with multi-viewport. (ocornut#8676, ocornut#8239, ocornut#8069)
  Backends: OSX: ImGui_ImplOSX_HandleEvent() only process event for window containing our viewports. Amend 7ac99a4 for docking. (ocornut#8644)
  Fixed duplicate symbols in some compile-time configurations.
  Fonts: Misc merge fixes.
  Examples: set ConfigDpiScaleFonts / ConfigDpiScaleViewports in all examples already setup for scaling.
  Backends: GLFW, SDL2, SDL3, update for docking to use helpers.
  (Breaking) renamed/moved ImGuiConfigFlags_DpiEnableScaleFonts -> ioConfigDpiScaleFonts, ImGuiConfigFlags_DpiEnableScaleViewports -> io.ConfigDpiScaleViewports
  Backends: Win32: Viewports: handle WM_DPICHANGED in backend when ImGuiConfigFlags_DpiEnableScaleViewports flag is enabled.
  Viewports: fixed handling of simultaneous move + resize (e.g. toggling maximized) when ImGuiConfigFlags_DpiEnableScaleViewports is enabled.
  Refactor: move SetCurrentFont(), PushFont(), PopFont() to a section.
  Platform IME: Fixed multi-viewports IME support, affecting SDL backends. (ocornut#8648, ocornut#8584, ocornut#7492, ocornut#6341)
  Viewports: added per-viewport FramebufferScale, Platform_GetWindowFramebufferScale() + Backends: GLFW, SDL2, SDL3, Apple: added support. (ocornut#1065, ocornut#1542, ocornut#1676, ocornut#1786, ocornut#2826, ocornut#3757, ocornut#5081, ocornut#5580, ocornut#5592, ocornut#6465, ocornut#7273, ocornut#7779 etc.) )
  Backends: OSX: rename internal struct for consistency with other backends.
  Viewports: fallback DpiScale pulled from fallback Monitor for consistency.
  Backends: Vulkan: fixed build with VK_NO_PROTOTYPES.
  Backends: Vulkan: fixed validation errors during window detach in multi-viewport mode. [docking branch amend] (ocornut#8600, ocornut#8176)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants