Skip to content

Backends: Glfw, SDL2: ImplXXXGetContentScale shall return 1 on emscripten / apple / android #8742

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 2 commits into
base: master
Choose a base branch
from

Conversation

pthom
Copy link
Contributor

@pthom pthom commented Jun 27, 2025

This is a fix related to #8733

We can divide platforms into two cases based on how they report screen geometry:

Case 1: Platforms which report screen size in "physical pixels": Windows (for "Dpi aware" apps), Linux (with Wayland)

Case 2: Platforms which report screen size in "density-independent pixels": macOS, iOS, Android, emscripten

As a consequence, there are two important things we need to know:

  • FramebufferScale: The scaling factor FrameBufferSize / ScreenSize

    • In case 1, the framebuffer size is equal to the screen size and DisplayFramebufferScale=1.
    • In case 2, the framebuffer size is equal to the screen size multiplied by a factor, for example DisplayFramebufferScale=2.
  • ContentScale The scaling factor for the content that we will display

    • In case 1, the content scale will often need to be > 1 (e.g., 2), because we will need to display bigger elements so that they show with a correct physical size on the screen.
    • In case 2, the content scale is equal to 1

This commit fixes ContentScale for platforms in case 2.

@ocornut
Copy link
Owner

ocornut commented Jun 27, 2025

Thanks for the PR.

There is a printf() left.

Is the value of io.DisplayFramebufferScale & returned by Platform_GetWindowFramebufferScale then correct on Emscripten on all 3 backends?

…pten / apple / android

This is a fix related to ocornut#8733

We can divide platforms into two cases based on how they report screen geometry:

    Case 1: Platforms which report screen size in "physical pixels": Windows (for "Dpi aware" apps), Linux (with Wayland)
    Case 2: Platforms which report screen size in "density-independent pixels": macOS, iOS, Android, emscripten

As a consequence, there are two important things we need to know:

    FramebufferScale: The scaling factor FrameBufferSize / ScreenSize
        In case 1, the framebuffer size is equal to the screen size and DisplayFramebufferScale=1.
        In case 2, the framebuffer size is equal to the screen size multiplied by a factor, for example DisplayFramebufferScale=2.
    ContentScale The scaling factor for the content that we will display
        In case 1, the content scale will often need to be > 1 (e.g., 2), because we will need to display bigger elements so that they show with a correct physical size on the screen.
        In case 2, the content scale is equal to 1

This commit fixes ContentScale for platforms in case 2.
@pthom
Copy link
Contributor Author

pthom commented Jun 27, 2025

There is a printf() left.

fixed, sorry

@pthom
Copy link
Contributor Author

pthom commented Jun 27, 2025

Is the value of io.DisplayFramebufferScale & returned by Platform_GetWindowFramebufferScale then correct on Emscripten on all 3 backends?

  • example_sdl2_opengl3:

    • ContentScale is equal to 1 (OK)
    • DisplayFrameBufferScale is equal to 2 on a retina screen, and varies according to browser zoom: OK
  • example_glfw_opengl3 and example_glfw_webgpu:

    • ContentScale is equal to 1 (OK)
    • DisplayFrameBufferScale is now correctly handled (and depends on the browser zoom level): OK

This was done using the flag --use-port=contrib.glfw3 instead of -s USE_GLFW=3

This way, we use emscripten-glfw, which offers a better support for HighDPI.


Note: an alternative (but faulty) solution would have been to stick to -sUSE_GLFW=3 and then modify main.cpp by adding :

#ifdef __EMSCRIPTEN__
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
#endif

This solution works partially (the FrameBufferScale is passed). However, it will create a window with a bad size as soon as the browser zoom level is not 100%. It is therefore not applicable.

@@ -32,8 +32,9 @@ EMS =
##---------------------------------------------------------------------

# ("EMS" options gets added to both CPPFLAGS and LDFLAGS, whereas some options are for linker only)
EMS += -s DISABLE_EXCEPTION_CATCHING=1
LDFLAGS += -s USE_GLFW=3 -s USE_WEBGPU=1
# Note: For glfw, we use emscripten-glfw port (contrib.glfw3) instead of (-s USE_GLFW=3) to get a better support for High DPI displays.
Copy link
Contributor

Choose a reason for hiding this comment

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

Just an FYI, emscripten-glfw offers much more than better support for Hi DPI, like for example gamepad support, copy/paste, workaround for meta key (broken with -s USE_GLFW=3), etc...

@ocornut ocornut added the web label Jul 8, 2025
ocornut pushed a commit that referenced this pull request Jul 8, 2025
…f on emscripten / apple / android (#8742, #8733)

We can divide platforms into two cases based on how they report screen geometry:
- Case 1: Platforms which report screen size in "physical pixels": Windows (for "Dpi aware" apps), Linux (with Wayland)
- Case 2: Platforms which report screen size in "density-independent pixels": macOS, iOS, Android, emscripten

As a consequence, there are two important things we need to know:
- FramebufferScale: The scaling factor FrameBufferSize / ScreenSize
  - In case 1, the framebuffer size is equal to the screen size and DisplayFramebufferScale=1.
  - In case 2, the framebuffer size is equal to the screen size multiplied by a factor, for example DisplayFramebufferScale=2.
- ContentScale The scaling factor for the content that we will display
  - In case 1, the content scale will often need to be > 1 (e.g., 2), because we will need to display bigger elements so that they show with a correct physical size on the screen.
  - In case 2, the content scale is equal to 1
This commit fixes ContentScale for platforms in case 2.
ocornut pushed a commit that referenced this pull request Jul 8, 2025
@ocornut
Copy link
Owner

ocornut commented Jul 8, 2025

I have merged this as 18dca11 + ed7d965 as this is a definitive improvement over 1.92.0 status.

HOWEVER,
FramebufferScale was never designed to be non-integer. As thus, I am not even sure this is working correctly in term of font quality.

A way to compare would be to use a nicer font, display text in varying sizes, capture and do a visual compare between e.g. native macOS and Emscripten. Does it looks the same? Could someone perform this test? (perhaps display ImGui::GetFontSize() + ImGui::GetCurrentContext()->FontRasterizerDensity on screen next to tests to ensure matching values are used in both tests)
Using imgui_freetype would facilitate the compare because hinting output more pixels precisely to the pixel grid, and if there is a difference it would be much easier to spot and compare.

One current benefit of FramebufferScale over ContentScale, is that currently the earlier works as-is, without application needing to support scaling. vs ContentScale requires Style scaling of sizes for which we don't currently provide a dynamic solution.

If we notice the output of non-integer FramebufferScale values is not perfect, or not good enough, we could perfectly decide to do the opposite on those platforms where FramebufferScale is not integer: move this scaling factor from FramebufferScale to ContentScale. This would put the same burden for scaling on app side as on Windows.

@ypujante
Copy link
Contributor

ypujante commented Jul 8, 2025

I am away at the moment (no access to my desktop), but unless somebody else take a look at it in the meantime, I should be able to run a test like this, this week-end/early next week.

@pthom
Copy link
Contributor Author

pthom commented Jul 8, 2025

Same as @ypujante , I can test this next week.

@pthom
Copy link
Contributor Author

pthom commented Jul 14, 2025

Below are several screenshots on macOS + emscripten (firefox and chrome) at various zoom levels.
FBS_shots.zip

Images content:

emscripten_chrome_fbs1.5.png
emscripten_chrome_fbs2.0.png
emscripten_chrome_fbs2.5.png
emscripten_chrome_fbs3.5.png
emscripten_firefox_fbs1.58.png
emscripten_firefox_fbs2.0.png
emscripten_firefox_fbs2.22.png
emscripten_firefox_fbs2.61.png
emscripten_firefox_fbs3.33.png
emscripten_firefox_fbs7.5.png
macOS_fbs2.0.png

Example:

image

This uses imgui_freetype + PlutoSvg and the font DroidSans (ImGui Bundle default).

Code (for my own reference if additional tests and/or changes are needed)

// This example will use the default settings of HelloImGui: imgui_freetype + plutosvg, font droid_sans.ttf
#include "hello_imgui/hello_imgui.h"
#include "imgui.h"
#include "imgui_internal.h"

void Gui() {
    auto fbs = ImGui::GetIO().DisplayFramebufferScale;
    ImGui::Text("Framebuffer scale: %.2f, %.2f", fbs.x, fbs.y);

    for (float font_size = 8.0f; font_size <= 32.0f; font_size += 2.0f) {
        ImGui::PushFont(nullptr, font_size);
        ImGui::Text("Font size: %.2f, FontRasterizerDensity=%.1f  the quick brown fox jumps over the lazy dog", font_size, ImGui::GetCurrentContext()->FontRasterizerDensity);
        ImGui::PopFont();
        ImGui::Separator();
    }
}

int main() { HelloImGui::Run(Gui); }

@ypujante
Copy link
Contributor

@ocornut did you need more than the test that @pthom created? It seems pretty complete to me, but I am not sure if this is what you were looking for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants