subsoap / defos

Extra native OS functions for games written using the Defold game engine
Creative Commons Zero v1.0 Universal
112 stars 16 forks source link

Scaling Factor detection on Windows 10 #95

Open aglitchman opened 5 years ago

aglitchman commented 5 years ago

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?

dapetcu21 commented 5 years ago

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.

aglitchman commented 5 years ago

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?

dapetcu21 commented 5 years ago

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

aglitchman commented 5 years ago

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"
  }
}
dapetcu21 commented 5 years ago

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.

aglitchman commented 5 years ago

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.

dapetcu21 commented 5 years ago

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.

aglitchman commented 5 years ago

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.