ebiten: Fullscreen broken on X11 with HiDPI

Repro:

  • Set display DPI to something other than 96: echo Xft.dpi: 144 | xrdb -merge
  • Run examples/windowsize
  • Press F

Observe that a large part of the screen is cut off (see screenshots).

20210820-100222 20210820-100226

Note how Xft.dpi does not influence the Device Scale Factor, but it does change the initial window size and causes this weird fullscreen scale up.

Reproduced on:

  • X11/i3
  • Wayland/Sway/Xwayland

Does NOT reproduce on:

  • Cinnamon

Most likely because ebiten has its own support for querying device scale that is specific to Cinnamon and GNOME, while not supporting anything else; at the same time, glfw has its different support and these two disagree.

Maybe we should just always use glfw’s content scale?

Note though:

// deviceScaleFactor() is a scale by desktop environment (e.g., Cinnamon), while GetContentScale() is X's scale.

I do not quite understand this - when do these two ever differ, except when there simply is no desktop environment managing this at all? In fact, do we ever handle the case of these two differing right?

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 45 (45 by maintainers)

Commits related to this issue

Most upvoted comments

I’m rather confident about correctness for Windows now.

Relevant deltas for Windows:

  • fromGLFWMonitprPixel applies the given scale and the deviceScaleFactor. The given scale is always 1.0; previously the deviceScaleFactor was given, so this should be a NOP, but a potentially broken refactor. Worth verifying.
  • Otherwise, although GLFW and Ebiten have both their own implementation of device scale factor querying, I assume the changes are NOP. It appears they use the same APIs but the way of detecting which API to use differs (GLFW seems to purely go by OS version, while Ebiten checks if the DLL exists).
  • The call to SetProcessDPIAware has been removed and may need to be inserted elsewhere. Technically that should be GLFW’s job though. In case of my game I am using the dpiAware manifest setting so my binary is probably not affected by this.

Relevant deltas for OS X:

  • Added device scale support at all. Not sure if that was a good or a bad idea. It is possible that the OS X conversions actually should just be “return x” with my changes too, which would imply that GLFW wants device independent coordinates on OS X (even though it wants frame buffer pixels anywhere else).
  • Pretty sure Ebiten’s and GLFW’s value of deviceScaleFactor agree (appears they use the same API), so this should not be a change.
  • Am unsure about fromGLFWMonitorPixel, as it depends on what kind of data GLFW returns. Might have to be x or x / (screenScale * u.deviceScaleFactor()) like in all other backends.
  • Am unsure if screenScale is relevant on OS X at all. Suspecting it isn’t and always having it 1.0 is right.
  • Mouse cursor position conversion changed; previous code just returned coordinates as is, new code divides by device scale factor. Rather sure this is a positive improvement.

Filed these GLFW bugs:

Note that a fix to any of these might need an Ebiten update in response. As Ebiten’s go.mod controls the GLFW version used, this can then be in sync.

For now, I figured out that querying the workarea only sometimes works (namely, when the desktop environment is setting one), so I am now switching to an approach that doesn’t use the workarea query but really operates at XRandR level.

Sounds good, will try.

The main complication will be that I can’t use the devicescale mode or it’d cause a cyclic dependency. I’ll probably at least at first just skip that module entirely for the GLFW/X11 code path.

The other complication is that there’s no “explicitly intended” way to get the scale from XRandR - one can get a matrix, or the current mode and the current output size. I think I’ll go for the latter and just divide. Have to see though how that interacts with panning (which one can set up too in XRandR).

On Sat, Aug 28, 2021 at 8:16 AM Hajime Hoshi @.***> wrote:

Thanks, so is your suggestion

  • fromGLFWMonitorPixel(x) = x / r
  • fromGLFWPixel(x) = x / d
  • toGLFWPixel(x) = x * d
  • toFramebufferPixel(x) = x
  • DeviceScaleFactor() = d

?

I’d be very happy if you could send a PR for this. I’m not confident this would work perfectly yet as I don’t understand about XRandR, but I’d like to test this on my some Linux machines.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/hajimehoshi/ebiten/issues/1774#issuecomment-907618590, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAB5NMFLSNRSNCQIDBO67KLT7DHSPANCNFSM5CQWKZSQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

Okay, so from what I see I guess the first thing to decide is, what should Ebiten’s device scale factor be?

It seems like Ebiten primarily uses it to map from game-defined to actual window sizes, but also - sadly - in lots of other places.

So my very first question would be: why? Can’t this be simplified and deviceScaleFactor be used way less?

Then there is GLFW’s own content scale. This is entirely unnecessary but “nice to have”.

What I would suggest as correct behavior:

  • In GLFW, ebiten.DeviceScaleFactor() shall return GLFW’s content scale.
  • When querying video modes (ScreenSizeInFullscreen), translate GLFW resolution using BOTH ebiten.DeviceScaleFactor() AND “the XRandR scale”. Actually getting the XRandR scale is somewhat tricky, as XRandR tracks it as a 3x3 transformation matrix.
  • For internal usage, however, do not rely on this conversion; instead create a fullscreen window, wait for it to be done and then query its size to find what you actually got.
  • In order to call Layout(), first translate the input using ebiten.DeviceScaleFactor() only.
  • Similarly, translate CursorPosition(), TouchPosition(), WindowSize(), SetWindowPosition(), SetWindowSize().
  • Internal code outside these API calls shall not use the device scale factor at all. I.e. Ebiten internally “thinks” in window system pixels (post-XRandR, pre-ContentScale).

This way should work perfectly on X11. Can’t tell it will work this way on Windows or Mac - depends on what kind of coordinates GLFW returns on these platforms.

Making everything work seems almost possible:

  • Do not rely on the monitor sizes for computing fullscreen size; instead update based on the GLFW callback for framebuffer size. This is what the example that comes with GLFW does, although you apparently had a case where the callback never came.
  • Failing that, use the window size approach always (which is what my attempted patch does).

Catch is, if we call GetSize too early, we will stay at the wrong size - and I believe this happened to me once, although I cannot repro. Would it actually be OK to just call GetSize every frame, or is that call expensive?

What this can NOT fix (which is why this is just an “almost”) is predicting the screen size BEFORE entering fullscreen. This is plain impossible using GLFW’s API. I guess the way to fix this is filing a GLFW feature request to either return “adjusted for scale” resolutions, or to include the scale factor as an additional struct member in the monitor info.

Impact of this breakage to many games would be displaying the wrong fullscreen size in the graphics settings menu, but everything else would work.

Impact of this breakage to my game in specific would be that I will calculate a too large initial window size in windowed mode on such systems. Luckily it appears that the desktop environments clip this overly large size to the screen size then, so this is kinda OK (plus I provide a setting for window size anyway).

On Sat, Aug 21, 2021, 02:48 Hoshi, Hajime (星一) @.***> wrote:

Also, given SDL has precisely the same bug, I’d like to make the “political decision” for my game to be equally broken as SDL when xrandr –scale is used, rather than ONLY working in GNOME and Cinnamon and not anywhere else (status quo).

So, what if we for now create a setting (be it a build tag or a runtime variable of some sort) to toggle the behavior to just use one scale factor for everything? I personally don’t care which one to use, although I’d be leaning towards “just force all scale factors to 1.0” given I already compute a “good window size” in my game anyway by multiplying game size by an integer factor “as big as possible” to leave some desktop environment borders.

Hmm, so are you suggesting to make things broken for consistency rather than hacks only for Gnome and Cinnamon, which seems inconsistent? Would it be feasible to make everything work, which is also consistent and ideal?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/hajimehoshi/ebiten/issues/1774#issuecomment-903070369, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAB5NMENQKIK45HWZKJKDOLT55D3XANCNFSM5CQWKZSQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&utm_campaign=notification-email .

Trying to use other conversions as a “proxy” to find a chain that should be the identity.

  • Mouse position handling: window.GetCursorPos() (physical pixel) -> fromGLFWMonitorPixel(., deviceScaleFactor) -> AdjustPosition(multiplies with deviceScaleFactor, divides by screenScale)
  • Drawing: multiply by screenScale to get from texture pixel to physical pixel

This suggests that the combination of all these conversions should always be the identity.

Thus, fromGLFWMonitorPixel . (* deviceScaleFactor) . (/ screenScale) . (* screenScale) must be the identity, so fromGLFWMonitorPixel can always just divide by deviceScaleFactor (which is what it indeed does).

Let’s see if I can find more such relations to solve this.