Skip to content

Scale factor changes (2x vs 1x displays) not propagated into ANGLE #2802

@j-h-a

Description

@j-h-a

This is intended to open a discussion about the "FIX" I made - there's probably a better way to fix this, but I'm new so have no idea.

The issue

When using GLFW with ANGLE on macOS with multiple displays at different DPIs, after initial window and EGL surface creation, subsequent moves to the other display produce incorrect rendering.

Launching app on an external 1x display, then moving it to the built-in 2x display:
ImageImage

Launching app on the built-in 2x display, then moving it to the external 1x display:
ImageImage

This is the same as issue #1857 - which includes the above screenshots and a video of it happening from a few years back. Apparently it only happens with ANGLE.

Investigation and verification

I initially thought this might be caused by #2609 but I patched that fix into a local copy of GLFW and it didn't fix it. After that (so including that patch) I then verified that the reported EGL surface size and scale values and framebuffer sizes were all correct when moving between displays. That is the window logical size would stay the same, the frame buffer and EGL surface would double (or half going the other way), and the contentScale on the underlying Layer was set correctly, all the correct values are also reported to the GLFW callbacks.

Everything is correct - so why does it render wrong?

What I think is happening

I think EGL and GLFW are doing the right thing, and ANGLE is also doing the right thing based on what they know about the contentScale... but when it changes, the new value can't be (or isn't) passed on into ANGLE - so ANGLE is stuck with the initial scale. You can see what's happening in the screenshots above:

  • 1st case: the 1x buffer is 100x100 and is correctly increased to 200x200, but ANGLE doesn't know that this now represents a @2x content scale (even though the layer is told), so it also scales it up and you end up seeing the bottom-left 100x100 of the 200x200 framebuffer scaled up to 200x200 for the high-res display.
  • 2nd case: the 2x buffer is 200x200 and is correctly decreased to 100x100, but ANGLE doesn't know that this now represents a @1x content scale (even though the layer is told), so it scales it down thinking it should be 50x50 on a 1x display.

My fix / workaround

My naive/dumb fix is to re-create the EGL surface when the scale changes, in viewDidChangeBackingProperties. This way the underlying EGL Surface knows the correct scale value again. Here are the specifics:

  1. I added this code to egl_context.c (with signature in internal.h):
void _glfwRecreateEGLSurface(_GLFWwindow* window)
{
    if (window->context.egl.surface == EGL_NO_SURFACE)
        return;

    // Destroy old surface
    eglMakeCurrent(_glfw.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroySurface(_glfw.egl.display, window->context.egl.surface);

    // Create new surface with same config
    EGLNativeWindowType native = _glfw.platform.getEGLNativeWindow(window);
    window->context.egl.surface =
        eglCreateWindowSurface(_glfw.egl.display, window->context.egl.config, native, NULL);

    if (window->context.egl.surface == EGL_NO_SURFACE)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "EGL: Failed to recreate window surface: %s",
                        getEGLErrorString(eglGetError()));
        return;
    }

    // Restore context
    eglMakeCurrent(_glfw.egl.display, window->context.egl.surface,
                   window->context.egl.surface, window->context.egl.handle);
}
  1. I call it from viewDidChangeBackingProperties in cocoa_window.m:
    if (xscale != window->ns.xscale || yscale != window->ns.yscale)
    {
...
        if (window->context.source == GLFW_EGL_CONTEXT_API)
        {
            NSLog(@"  Recreating EGL surface for scale change");
            _glfwRecreateEGLSurface(window);
        }
...
    }

And that fixes it.

To discuss

This is probably not the right way to do it, and even if it is then likely it should be behind a hint or something so that it is only done when wanted.

  • I don't know for sure if the "what I think is happening" above is correct, if anyone knows better please correct it
  • Is there actually an EGL API to change the scale that could be called instead of re-creating?
  • Should there be a hint or something to tell GLFW to do this when scale changes?

Metadata

Metadata

Assignees

No one assigned

    Labels

    ANGLEEGLEGL API specificbugBug reports and bugfix pull requestsmacOS

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions