subsoap/defos

Scaling Factor detection on Windows 10

Opened this issue · 8 comments

I have a 24' 3840x2160 monitor that I use with Windows 10 at a scale of 200%. So virtually it's 1920x1080 with PPI, like Apple Retina.

DefOS failed to determine the correct scaling_factor (must be 2):

DEBUG:SCRIPT: Found 1 displays:
DEBUG:SCRIPT: 
{ --[[000000001CBDA340]]
  bounds = { --[[000000001CBDA3E0]]
    y = 0,
    x = 0,
    height = 2160,
    width = 3840
  },
  name = "Generic PnP Monitor",
  mode = { --[[000000001CBDA410]]
    reflect_y = false,
    width = 3840,
    scaling_factor = 1,
    refresh_rate = 60,
    reflect_x = false,
    orientation = 0,
    height = 2160,
    bits_per_pixel = 32
  },
  id = "\\.\DISPLAY1"
}

Seems that on Windows 10 you should to use this API to determine the correct scaling_factor:
https://docs.microsoft.com/en-us/windows/desktop/api/shellscalingapi/nf-shellscalingapi-getscalefactorformonitor
https://docs.microsoft.com/en-us/windows/desktop/api/shtypes/ne-shtypes-device_scale_factor

Also, width/height in bounds or mode should be half the size, right?

From what I know there are two ways to scale a display on Windows, which compound with each other. One which controls the actual size of the display on the desktop canvas (which DefOS tracks) and one which controls the size of UI elements for windows on a particular screen (shell scaling).

The values that bounds returns are returned in such a way that you can plug them back into defos.set_window_size to move your window around the desktop canvas. They also match the window size values that Defold sets your window to when HiDPI is disabled. I intend on sticking with that behaviour and keeping it consistent across platforms.

I'm not that sure about the value in mode, though. I could include the shell scaling factor, but then mode.width / mode.scaling_factor won't equal bounds.width anymore. Alternatively, we could add another ui_scaling_factor value in the mode table, which won't break this behaviour, but might be a little confusing.

I just found out that scaling_factor becomes equal to 1 when High Dpi is enabled in game.project.
If hidpi is off, scaling_factor is detected correctly, so the issue can be closed.
Is it a bug or a feature?

@aglitchman Does mode.width / mode.scaling_factor equal bounds.width? Can I see the full table?

I recently upgraded my monitor to 27 inches, so scaling_factor is now 1.5 (150%).

pprint(defos.get_current_display_id())
pprint(defos.get_displays())

High Dpi is off

DEBUG:SCRIPT: \\.\DISPLAY1
DEBUG:SCRIPT: 
{ --[[000000003ED84720]]
  1 = { --[[000000003ED84750]]
    bounds = { --[[000000003ED84810]]
      y = 0,
      x = 0,
      height = 1440,
      width = 2560
    },
    name = "BenQ PD2700U",
    mode = { --[[000000003ED84840]]
      reflect_y = false,
      width = 3840,
      scaling_factor = 1.5,
      refresh_rate = 60,
      reflect_x = false,
      orientation = 0,
      height = 2160,
      bits_per_pixel = 32
    },
    id = "\\.\DISPLAY1"
  },
  \\.\DISPLAY1 = { --[[000000003ED84750]]
    bounds = { --[[000000003ED84810]]
      y = 0,
      x = 0,
      height = 1440,
      width = 2560
    },
    name = "BenQ PD2700U",
    mode = { --[[000000003ED84840]]
      reflect_y = false,
      width = 3840,
      scaling_factor = 1.5,
      refresh_rate = 60,
      reflect_x = false,
      orientation = 0,
      height = 2160,
      bits_per_pixel = 32
    },
    id = "\\.\DISPLAY1"
  }
}

High Dpi is on

DEBUG:SCRIPT: \\.\DISPLAY1
DEBUG:SCRIPT: 
{ --[[0000000030374720]]
  1 = { --[[0000000030374750]]
    bounds = { --[[0000000030374810]]
      y = 0,
      x = 0,
      height = 2160,
      width = 3840
    },
    name = "BenQ PD2700U",
    mode = { --[[0000000030374840]]
      reflect_y = false,
      width = 3840,
      scaling_factor = 1,
      refresh_rate = 60,
      reflect_x = false,
      orientation = 0,
      height = 2160,
      bits_per_pixel = 32
    },
    id = "\\.\DISPLAY1"
  },
  \\.\DISPLAY1 = { --[[0000000030374750]]
    bounds = { --[[0000000030374810]]
      y = 0,
      x = 0,
      height = 2160,
      width = 3840
    },
    name = "BenQ PD2700U",
    mode = { --[[0000000030374840]]
      reflect_y = false,
      width = 3840,
      scaling_factor = 1,
      refresh_rate = 60,
      reflect_x = false,
      orientation = 0,
      height = 2160,
      bits_per_pixel = 32
    },
    id = "\\.\DISPLAY1"
  }
}

Yeah. This is the intended behaviour. Window coordinates map 1:1 to the display pixels, so scaling_factor is 1. As I said above, we maybe should add a ui_scaling_factor property that would report the shell scaling factor independently from this.

I use DefOS on Windows to scale the window down to the desktop height after starting and move the window to the center of the screen. Very useful in the development process.

When high dpi is off, my code works fine. When high dpi is on, it works incorrectly because the real height of the taskbar and the window borders are unknown because of incorrect scaling_factor.

Adding ui_scaling_factor will help me a lot.

For the window borders, you can query them. defos.get_window_size() returns the window bounds including window borders and defos.get_view_size() returns the bounds of just the rendering surface (excluding window borders). The setters work the same.

For the window borders, you can query them.

Yeah, that's what I do. But without ui_scaling_factor it is impossible to calculate the height of the taskbar.