Page MenuHome

Fix T85706: wm_window_make_drawable update DPI
ClosedPublic

Authored by Harley Acheson (harley) on Feb 20 2021, 9:01 PM.

Details

Summary

When drawing windows on monitors that differ in DPI, we can sometimes
have UI elements draw at an incorrect scale. This patch just ensures
that wm_window_make_drawable always updates DPI.


This bug is not seen often, but the following bug report has a good reproducible way to see it in action: https://developer.blender.org/T85706

When drawing windows we only have a single place that stores all the current metrics for user scale, monitor dpi, font sizes, etc. Obviously we then have to frequently update this information when there are multiple monitors that differ in DPI. Unfortunately most of the times we do this are unnecessary because we don't actually draw immediately afterward. Instead we draw at regular intervals, checking then to see which areas are in need of drawing. And at that time it is also necessary to do the same checks between monitor draws, making the prior values moot.

In this particular example, we are changing the workspace, which triggers each monitor to update these settings (in Userprefs), one at a time. The last one had higher dpi. And all were tagged with a need to be redrawn.

At drawing time we go through each monitor and again have to check dpi between each one. But there is one flaw. At this time the first monitor is currently the active and drawable one. We call wm_window_make_drawable() and it decides that because the monitor is already the "windrawable" it does not need to check dpi. And so it draws it out with the incorrect sizes.

This patch just makes it so that wm_window_make_drawable() always checks for dpi. But, as mentioned, this thing always needs to be called between window draws anyway and is in almost every case.

Diff Detail

Repository
rB Blender

Event Timeline

Harley Acheson (harley) requested review of this revision.Feb 20 2021, 9:01 PM
Harley Acheson (harley) created this revision.

wm_event_do_handlers calss this function for every event, I'd prefer to avoid setting DPI for every event.

Have not checked closely, but some potential solutions:

  • Move this into wm_window_set_drawable?
  • Call wm_window_reset_drawable on GHOST_kEventWindowDPIHintChanged or other relevant events?
Harley Acheson (harley) planned changes to this revision.Feb 22 2021, 6:03 PM

@Brecht Van Lommel (brecht) - ...I'd prefer to avoid setting DPI for every event.

  • Call wm_window_reset_drawable on GHOST_kEventWindowDPIHintChanged or other relevant events?

This might work. Will take a look. This is a pretty obscure issue - I think we get a report a year on this - so can take my time I think. I can at least reproduce the issue quickly now.

Will revisit this soon as a possible 3.0 target

Not sure if it is the same issue, but https://developer.blender.org/T86466 seems to be related.

At least it is also caused by different monitor DPIs

Note that the only reason I have not worked on this further is that I have been unable to recreate this error condition at will. We still get reports that this still occurs of course, but I need an easily reproducible situation in order to examine, test, and correct.

wm_event_do_handlers calls this function for every event, I'd prefer to avoid setting DPI for every event.

Yes, I have moved this DPI update to inside the wm_draw_update loop instead.

Have not checked closely, but some potential solutions:

  • Move this into wm_window_set_drawable?

This issue is not fixed if moved to wm_window_set_drawable

  • Call wm_window_reset_drawable on GHOST_kEventWindowDPIHintChanged or other relevant events?

There isn't an event that is applicable here. For example that GHOST_kEventWindowDPIHintChanged is called at the moment when a user changes the DPI of one of their monitors.

This problem occurs afterward and repeatedly. Once you have set your multiple monitors to have differing DPI then we are continually painting one then another and we have to adjust all our metrics and font sizes in between.

It's not clear to me that removing it from wm_window_make_drawable is safe. That's called from more than just the wm_draw_update, for example when initially creating windows.

I would instead change the implementation of wm_window_make_drawable to:

if (win != wm->windrawable && win->ghostwin) {
  ...   
  wm_window_set_drawable(wm, win, true);
}

if (win->ghostwin) {
  /* this can change per window */
  WM_window_set_dpi(win);
}
Brecht Van Lommel (brecht) requested changes to this revision.Jan 5 2022, 3:07 PM
This revision now requires changes to proceed.Jan 5 2022, 3:07 PM

! In D10483#363743, @Brecht Van Lommel (brecht) wrote:
It's not clear to me that removing it from wm_window_make_drawable is safe. That's called from more than just the wm_draw_update, for example when initially creating windows.

It can be removed from wm_window_make_drawable when it is added to wm_draw_update, which is always called including when initially creating windows.

! In D10483#363743, @Brecht Van Lommel (brecht) wrote:
I would instead change the implementation of wm_window_make_drawable to:

if (win != wm->windrawable && win->ghostwin) {
  ...   
  wm_window_set_drawable(wm, win, true);
}
 
if (win->ghostwin) {
  /* this can change per window */
  WM_window_set_dpi(win);
}

Doing only that is enough to fix this issue, and is also my very first version of this patch from Feb 20 2021. Your reply back then was that "wm_event_do_handlers calls this function for every event, I'd prefer to avoid setting DPI for every event." And you were correct. Not only is this called between windows (good) but it is also called with events.

Ah ok, I was confused.

The diff shows WM_window_set_dpi being removed from wm_window_make_drawable, but it's not actually there in master. If we are only adding a call to WM_window_set_dpi it's safe, and we can think about perhaps refactoring this state to be more clear in the future.

This revision is now accepted and ready to land.Jan 7 2022, 6:24 PM
Brecht Van Lommel (brecht) requested changes to this revision.Jan 7 2022, 6:35 PM

Actually I confused myself, it is in wm_window_make_drawable in master and this patch is removing it from there. We definitely need to have the correct DPI for event handling, so we can not remove it.

Anyway, if you can't figure why windrawable gets out of sync with the DPI, or if we are missing some GHOST event, then it seems reasonable to go with the original patch. It's not that expensive, just would be nice to avoid.

But the original patch does then need an if (win->ghostwin) { check since I believe that can be NULL.

This revision now requires changes to proceed.Jan 7 2022, 6:35 PM

@Brecht Van Lommel (brecht) - Anyway, if you can't figure why windrawable gets out of sync with the DPI...

That part is easy, but I find hard to explain because I am unclear on some of the intent of these functions...

We have wm_window_set_drawable, wm_window_clear_drawable, wm_window_reset_drawable and wm_window_make_drawable

It is only wm_window_MAKE_drawable that calls WM_window_set_dpi, but only if not already drawable. However we call wm_window_SET_drawable all over the place. So a window can be drawable and not have had that function called.

So we are doing the actual drawing in wm_draw_update, going one window at a time. if the first and last monitors differ in dpi you can have that loop start with a window that is drawable and has a different dpi from the last in the list. wm_window_make_drawable is called and does not update dpi because it is already drawable. Bam, it draws too large.

The smallest change to fix this would be to call wm_window_clear_drawable in that function:

void wm_draw_update(bContext *C)
{
...
  LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
...

      wm_window_clear_drawable(wm);

      /* sets context window+screen */
      wm_window_make_drawable(wm, win);
...

or perhaps a bit more correct to put that before the loop

void wm_draw_update(bContext *C)
{
...

  wm_window_clear_drawable(wm);

  LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
...
      /* sets context window+screen */
      wm_window_make_drawable(wm, win);
...

I think the ideal fix for this ensures windrawable and DPI is always matching, rather than fixing wm_draw_update specifically. Event handling and operators can use the DPI too.

wm_window_set_drawable is called in 3 places, and they seems correct to me:

  • wm_window_ghostwindow_add sets the DPI
  • wm_window_make_drawable sets the DPI
  • wm_window_reset_drawable does not change the window, so no DPI change needed

I think probably the thing that makes it go out of sync is the calls to WM_window_set_dpi without corresponding wm_window_set/make_drawable. Probably some calls can be removed, or replaced by a function that returns DPI rather than changing global state.

It would be good to do that kind of refactor, but I'm ok with the simpler fix I proposed in my previous comment.

Harley Acheson (harley) updated this revision to Diff 46828.EditedJan 10 2022, 9:09 PM

@Brecht Van Lommel (brecht) - but I'm ok with the simpler fix I proposed in my previous comment

Yes, this revision brings us back to that.

Probably some calls can be removed, or replaced by a function that returns DPI rather than changing global state... It would be good to do that kind of refactor,

Perhaps storing the DPI (as we know it but don't keep: int dpi = auto_dpi * U.ui_scale * (72.0 / 96.0f);) in the wmWindow would help a bit. At least while painting we can check if it is the same as the last value. But if we did that it might make sense to have a monitor struct that the window points to. It could keep that, color management details, etc. Nothing easy is jumping out. LOL

This revision is now accepted and ready to land.Jan 12 2022, 6:10 PM
Harley Acheson (harley) retitled this revision from Fix T85706: Change of child's DPI can sometimes cause incorrect drawing of parent to Fix T85706: wm_window_make_drawable update DPI.Jan 12 2022, 7:31 PM
Harley Acheson (harley) edited the summary of this revision. (Show Details)