mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +01:00
Split the concept of device scale and screen scale (#1811)
We now set deviceScale to always the mapping from logical window system pixels to device independent pixels (which is what Ebiten API users expect), and introduce a new concept of videoModeScale that maps from video mode to logical window system pixels. videoModeScale is now only used for computing full-screen resolutions, while deviceScale is used for any other conversion. Fixes window sizes on X11, should be a NOP otherwise. Closes #1774.
This commit is contained in:
parent
60df512352
commit
923c84a3d6
@ -25,11 +25,11 @@ type pos struct {
|
|||||||
var (
|
var (
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
cache = map[pos]float64{}
|
cache = map[pos]float64{}
|
||||||
|
videoModeScaleCache = map[pos]float64{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetAt returns the device scale at (x, y).
|
// GetAt returns the device scale at (x, y), i.e. the number of device-dependent pixels per device-independent pixel.
|
||||||
// x and y are in device-dependent pixels and must be the top-left coordinate of a monitor, or 0,0 to request a "global scale".
|
// x and y are in device-dependent pixels and must be the top-left coordinate of a monitor, or 0,0 to request a "global scale".
|
||||||
// The device scale maps device dependent pixels to device independent pixels.
|
|
||||||
func GetAt(x, y int) float64 {
|
func GetAt(x, y int) float64 {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
@ -46,3 +46,16 @@ func GetAt(x, y int) float64 {
|
|||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VideoModeScaleAt returns the video mode scale scale at (x, y), i.e. the number of video mode pixels per device-dependent pixel.
|
||||||
|
// x and y are in device-dependent pixels and must be the top-left coordinate of a monitor, or 0,0 to request a "global scale".
|
||||||
|
func VideoModeScaleAt(x, y int) float64 {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
if s, ok := videoModeScaleCache[pos{x, y}]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
s := videoModeScaleImpl(x, y)
|
||||||
|
videoModeScaleCache[pos{x, y}] = s
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
@ -103,3 +103,7 @@ func impl(x, y int) float64 {
|
|||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func videoModeScaleImpl(x, y int) float64 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
@ -33,3 +33,8 @@ func monitorAt(x, y int) *glfw.Monitor {
|
|||||||
}
|
}
|
||||||
return monitors[0]
|
return monitors[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func impl(x, y int) float64 {
|
||||||
|
sx, _ := monitorAt(x, y).GetContentScale()
|
||||||
|
return float64(sx)
|
||||||
|
}
|
||||||
|
@ -30,3 +30,7 @@ import "C"
|
|||||||
func impl(x, y int) float64 {
|
func impl(x, y int) float64 {
|
||||||
return float64(C.devicePixelRatio())
|
return float64(C.devicePixelRatio())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func videoModeScaleImpl(x, y int) float64 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
@ -33,3 +33,7 @@ func impl(x, y int) float64 {
|
|||||||
}
|
}
|
||||||
return ratio
|
return ratio
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func videoModeScaleImpl(x, y int) float64 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package devicescale
|
package devicescale
|
||||||
|
|
||||||
func impl(x, y int) float64 {
|
func videoModeScaleImpl(x, y int) float64 {
|
||||||
sx, _ := monitorAt(x, y).GetContentScale()
|
return 1
|
||||||
return float64(sx)
|
|
||||||
}
|
}
|
||||||
|
@ -25,9 +25,9 @@ import (
|
|||||||
"github.com/jezek/xgb/xproto"
|
"github.com/jezek/xgb/xproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func impl(x, y int) float64 {
|
func videoModeScaleImpl(x, y int) float64 {
|
||||||
// TODO: if https://github.com/glfw/glfw/issues/1961 gets fixed, this function may need revising.
|
// TODO: if https://github.com/glfw/glfw/issues/1961 gets fixed, this function may need revising.
|
||||||
// In case GLFW decides to switch to returning logical pixels, we can just return 1.0.
|
// In case GLFW decides to switch to returning logical pixels, we can just return 1.
|
||||||
|
|
||||||
// Note: GLFW currently returns physical pixel sizes,
|
// Note: GLFW currently returns physical pixel sizes,
|
||||||
// but we need to predict the window system-side size of the fullscreen window
|
// but we need to predict the window system-side size of the fullscreen window
|
||||||
@ -35,7 +35,6 @@ func impl(x, y int) float64 {
|
|||||||
// Also at the moment we need this prior to switching to fullscreen, but that might be replacable.
|
// Also at the moment we need this prior to switching to fullscreen, but that might be replacable.
|
||||||
// So this function computes the ratio of physical per logical pixels.
|
// So this function computes the ratio of physical per logical pixels.
|
||||||
m := monitorAt(x, y)
|
m := monitorAt(x, y)
|
||||||
sx, _ := m.GetContentScale()
|
|
||||||
monitorX, monitorY := m.GetPos()
|
monitorX, monitorY := m.GetPos()
|
||||||
xconn, err := xgb.NewConn()
|
xconn, err := xgb.NewConn()
|
||||||
defer xconn.Close()
|
defer xconn.Close()
|
||||||
@ -43,19 +42,19 @@ func impl(x, y int) float64 {
|
|||||||
// No X11 connection?
|
// No X11 connection?
|
||||||
// Assume we're on pure Wayland then.
|
// Assume we're on pure Wayland then.
|
||||||
// GLFW/Wayland shouldn't be having this issue.
|
// GLFW/Wayland shouldn't be having this issue.
|
||||||
return float64(sx)
|
return 1
|
||||||
}
|
}
|
||||||
if err = randr.Init(xconn); err != nil {
|
if err = randr.Init(xconn); err != nil {
|
||||||
// No RANDR extension?
|
// No RANDR extension?
|
||||||
// No problem.
|
// No problem.
|
||||||
return float64(sx)
|
return 1
|
||||||
}
|
}
|
||||||
root := xproto.Setup(xconn).DefaultScreen(xconn).Root
|
root := xproto.Setup(xconn).DefaultScreen(xconn).Root
|
||||||
res, err := randr.GetScreenResourcesCurrent(xconn, root).Reply()
|
res, err := randr.GetScreenResourcesCurrent(xconn, root).Reply()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Likely means RANDR is not working.
|
// Likely means RANDR is not working.
|
||||||
// No problem.
|
// No problem.
|
||||||
return float64(sx)
|
return 1
|
||||||
}
|
}
|
||||||
for _, crtc := range res.Crtcs[:res.NumCrtcs] {
|
for _, crtc := range res.Crtcs[:res.NumCrtcs] {
|
||||||
info, err := randr.GetCrtcInfo(xconn, crtc, res.ConfigTimestamp).Reply()
|
info, err := randr.GetCrtcInfo(xconn, crtc, res.ConfigTimestamp).Reply()
|
||||||
@ -75,9 +74,9 @@ func impl(x, y int) float64 {
|
|||||||
// Return one scale, even though there may be separate X and Y scales.
|
// Return one scale, even though there may be separate X and Y scales.
|
||||||
// Return the _larger_ scale, as this would yield a letterboxed display on mismatch, rather than a cut-off one.
|
// Return the _larger_ scale, as this would yield a letterboxed display on mismatch, rather than a cut-off one.
|
||||||
scale := math.Max(float64(physWidth)/float64(xWidth), float64(physHeight)/float64(xHeight))
|
scale := math.Max(float64(physWidth)/float64(xWidth), float64(physHeight)/float64(xHeight))
|
||||||
return float64(sx) * scale
|
return scale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Monitor not known to XRandR. Weird.
|
// Monitor not known to XRandR. Weird.
|
||||||
return float64(sx)
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -305,8 +305,8 @@ func (i *Input) update(window *glfw.Window, context driver.UIContext) {
|
|||||||
cx, cy := window.GetCursorPos()
|
cx, cy := window.GetCursorPos()
|
||||||
// TODO: This is tricky. Rename the function?
|
// TODO: This is tricky. Rename the function?
|
||||||
s := i.ui.deviceScaleFactor()
|
s := i.ui.deviceScaleFactor()
|
||||||
cx = fromGLFWMonitorPixel(cx, s)
|
cx = i.ui.fromGLFWPixel(cx)
|
||||||
cy = fromGLFWMonitorPixel(cy, s)
|
cy = i.ui.fromGLFWPixel(cy)
|
||||||
cx, cy = context.AdjustPosition(cx, cy, s)
|
cx, cy = context.AdjustPosition(cx, cy, s)
|
||||||
|
|
||||||
// AdjustPosition can return NaN at the initialization.
|
// AdjustPosition can return NaN at the initialization.
|
||||||
|
@ -189,9 +189,10 @@ func initialize() error {
|
|||||||
m := currentMonitor(w)
|
m := currentMonitor(w)
|
||||||
theUI.initMonitor = m
|
theUI.initMonitor = m
|
||||||
v := m.GetVideoMode()
|
v := m.GetVideoMode()
|
||||||
scale := devicescale.GetAt(currentMonitor(w).GetPos())
|
mx, my := currentMonitor(w).GetPos()
|
||||||
theUI.initFullscreenWidthInDP = int(fromGLFWMonitorPixel(float64(v.Width), scale))
|
scale := devicescale.VideoModeScaleAt(mx, my)
|
||||||
theUI.initFullscreenHeightInDP = int(fromGLFWMonitorPixel(float64(v.Height), scale))
|
theUI.initFullscreenWidthInDP = int(theUI.fromGLFWMonitorPixel(float64(v.Width), scale))
|
||||||
|
theUI.initFullscreenHeightInDP = int(theUI.fromGLFWMonitorPixel(float64(v.Height), scale))
|
||||||
|
|
||||||
// Create system cursors. These cursors are destroyed at glfw.Terminate().
|
// Create system cursors. These cursors are destroyed at glfw.Terminate().
|
||||||
glfwSystemCursors[driver.CursorShapeDefault] = nil
|
glfwSystemCursors[driver.CursorShapeDefault] = nil
|
||||||
@ -528,10 +529,12 @@ func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
|
|||||||
|
|
||||||
var w, h int
|
var w, h int
|
||||||
_ = u.t.Call(func() error {
|
_ = u.t.Call(func() error {
|
||||||
v := currentMonitor(u.window).GetVideoMode()
|
m := currentMonitor(u.window)
|
||||||
s := u.deviceScaleFactor()
|
v := m.GetVideoMode()
|
||||||
w = int(fromGLFWMonitorPixel(float64(v.Width), s))
|
mx, my := m.GetPos()
|
||||||
h = int(fromGLFWMonitorPixel(float64(v.Height), s))
|
s := devicescale.VideoModeScaleAt(mx, my)
|
||||||
|
w = int(u.fromGLFWMonitorPixel(float64(v.Width), s))
|
||||||
|
h = int(u.fromGLFWMonitorPixel(float64(v.Height), s))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return w, h
|
return w, h
|
||||||
@ -707,7 +710,7 @@ func (u *UserInterface) SetCursorShape(shape driver.CursorShape) {
|
|||||||
func (u *UserInterface) DeviceScaleFactor() float64 {
|
func (u *UserInterface) DeviceScaleFactor() float64 {
|
||||||
if !u.isRunning() {
|
if !u.isRunning() {
|
||||||
// TODO: Use the initWindowPosition. This requires to convert the units correctly (#1575).
|
// TODO: Use the initWindowPosition. This requires to convert the units correctly (#1575).
|
||||||
return devicescale.GetAt(u.initMonitor.GetPos())
|
return u.deviceScaleFactor()
|
||||||
}
|
}
|
||||||
|
|
||||||
f := 0.0
|
f := 0.0
|
||||||
@ -724,7 +727,8 @@ func (u *UserInterface) deviceScaleFactor() float64 {
|
|||||||
if u.window != nil {
|
if u.window != nil {
|
||||||
m = currentMonitor(u.window)
|
m = currentMonitor(u.window)
|
||||||
}
|
}
|
||||||
return devicescale.GetAt(m.GetPos())
|
mx, my := m.GetPos()
|
||||||
|
return devicescale.GetAt(mx, my)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -933,11 +937,13 @@ func (u *UserInterface) updateSize() (float64, float64, bool) {
|
|||||||
|
|
||||||
var w, h float64
|
var w, h float64
|
||||||
if u.isFullscreen() {
|
if u.isFullscreen() {
|
||||||
v := currentMonitor(u.window).GetVideoMode()
|
m := currentMonitor(u.window)
|
||||||
|
v := m.GetVideoMode()
|
||||||
ww, wh := v.Width, v.Height
|
ww, wh := v.Width, v.Height
|
||||||
s := u.deviceScaleFactor()
|
mx, my := m.GetPos()
|
||||||
w = fromGLFWMonitorPixel(float64(ww), s)
|
s := devicescale.VideoModeScaleAt(mx, my)
|
||||||
h = fromGLFWMonitorPixel(float64(wh), s)
|
w = u.fromGLFWMonitorPixel(float64(ww), s)
|
||||||
|
h = u.fromGLFWMonitorPixel(float64(wh), s)
|
||||||
} else {
|
} else {
|
||||||
// Instead of u.windowWidth and u.windowHeight, use the actual window size here.
|
// Instead of u.windowWidth and u.windowHeight, use the actual window size here.
|
||||||
// On Windows, the specified size at SetSize and the actual window size might not
|
// On Windows, the specified size at SetSize and the actual window size might not
|
||||||
@ -946,9 +952,6 @@ func (u *UserInterface) updateSize() (float64, float64, bool) {
|
|||||||
w = u.fromGLFWPixel(float64(ww))
|
w = u.fromGLFWPixel(float64(ww))
|
||||||
h = u.fromGLFWPixel(float64(wh))
|
h = u.fromGLFWPixel(float64(wh))
|
||||||
}
|
}
|
||||||
// On Linux/UNIX, further adjusting is required (#1307).
|
|
||||||
w = u.toFramebufferPixel(w)
|
|
||||||
h = u.toFramebufferPixel(h)
|
|
||||||
|
|
||||||
return w, h, true
|
return w, h, true
|
||||||
}
|
}
|
||||||
|
@ -81,22 +81,27 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||||
)
|
)
|
||||||
|
|
||||||
func fromGLFWMonitorPixel(x float64, deviceScale float64) float64 {
|
// fromGLFWMonitorPixel must be called from the main thread.
|
||||||
return x
|
func (u *UserInterface) fromGLFWMonitorPixel(x float64, videoModeScale float64) float64 {
|
||||||
|
// videoModeScale is always 1 on macOS,
|
||||||
|
// however leaving the divison in place for consistency.
|
||||||
|
return x / videoModeScale
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fromGLFWPixel must be called from the main thread.
|
||||||
func (u *UserInterface) fromGLFWPixel(x float64) float64 {
|
func (u *UserInterface) fromGLFWPixel(x float64) float64 {
|
||||||
|
// NOTE: On macOS, GLFW exposes the device independent coordinate system.
|
||||||
|
// Thus, the conversion functions are unnecessary,
|
||||||
|
// however we still need the deviceScaleFactor internally
|
||||||
|
// so we can create and maintain a HiDPI frame buffer.
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toGLFWPixel must be called from the main thread.
|
||||||
func (u *UserInterface) toGLFWPixel(x float64) float64 {
|
func (u *UserInterface) toGLFWPixel(x float64) float64 {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) toFramebufferPixel(x float64) float64 {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UserInterface) adjustWindowPosition(x, y int) (int, int) {
|
func (u *UserInterface) adjustWindowPosition(x, y int) (int, int) {
|
||||||
return x, y
|
return x, y
|
||||||
}
|
}
|
||||||
|
@ -19,37 +19,23 @@
|
|||||||
package glfw
|
package glfw
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fromGLFWMonitorPixel must be called from the main thread.
|
// fromGLFWMonitorPixel must be called from the main thread.
|
||||||
func fromGLFWMonitorPixel(x float64, deviceScale float64) float64 {
|
func (u *UserInterface) fromGLFWMonitorPixel(x float64, videoModeScale float64) float64 {
|
||||||
// deviceScaleFactor is sometimes an unnice value (e.g., 1.502361). Use math.Ceil to clean the vaule.
|
return x / (videoModeScale * u.deviceScaleFactor())
|
||||||
return math.Ceil(x / deviceScale)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fromGLFWPixel must be called from the main thread.
|
// fromGLFWPixel must be called from the main thread.
|
||||||
func (u *UserInterface) fromGLFWPixel(x float64) float64 {
|
func (u *UserInterface) fromGLFWPixel(x float64) float64 {
|
||||||
// deviceScaleFactor() is a scale by desktop environment (e.g., Cinnamon), while GetContentScale() is X's scale.
|
return x / u.deviceScaleFactor()
|
||||||
// They are different things and then need to be treated in different ways (#1350).
|
|
||||||
s, _ := currentMonitor(u.window).GetContentScale()
|
|
||||||
return x / float64(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// toGLFWPixel must be called from the main thread.
|
// toGLFWPixel must be called from the main thread.
|
||||||
func (u *UserInterface) toGLFWPixel(x float64) float64 {
|
func (u *UserInterface) toGLFWPixel(x float64) float64 {
|
||||||
s, _ := currentMonitor(u.window).GetContentScale()
|
return x * u.deviceScaleFactor()
|
||||||
return x * float64(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// toFramebufferPixel must be called from the main thread.
|
|
||||||
func (u *UserInterface) toFramebufferPixel(x float64) float64 {
|
|
||||||
s, _ := currentMonitor(u.window).GetContentScale()
|
|
||||||
// deviceScaleFactor is sometimes an unnice value (e.g., 1.502361). Use math.Ceil to clean the vaule.
|
|
||||||
return math.Ceil(x * float64(s) / u.deviceScaleFactor())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) adjustWindowPosition(x, y int) (int, int) {
|
func (u *UserInterface) adjustWindowPosition(x, y int) (int, int) {
|
||||||
|
@ -99,8 +99,8 @@ func getMonitorInfoW(hMonitor uintptr, lpmi *monitorInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fromGLFWMonitorPixel must be called from the main thread.
|
// fromGLFWMonitorPixel must be called from the main thread.
|
||||||
func fromGLFWMonitorPixel(x float64, deviceScale float64) float64 {
|
func (u *UserInterface) fromGLFWMonitorPixel(x float64, videoModeScale float64) float64 {
|
||||||
return x / deviceScale
|
return x / (videoModeScale * u.deviceScaleFactor())
|
||||||
}
|
}
|
||||||
|
|
||||||
// fromGLFWPixel must be called from the main thread.
|
// fromGLFWPixel must be called from the main thread.
|
||||||
@ -113,11 +113,6 @@ func (u *UserInterface) toGLFWPixel(x float64) float64 {
|
|||||||
return x * u.deviceScaleFactor()
|
return x * u.deviceScaleFactor()
|
||||||
}
|
}
|
||||||
|
|
||||||
// toFramebufferPixel must be called from the main thread.
|
|
||||||
func (u *UserInterface) toFramebufferPixel(x float64) float64 {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UserInterface) adjustWindowPosition(x, y int) (int, int) {
|
func (u *UserInterface) adjustWindowPosition(x, y int) (int, int) {
|
||||||
mx, my := currentMonitor(u.window).GetPos()
|
mx, my := currentMonitor(u.window).GetPos()
|
||||||
// As the video width/height might be wrong,
|
// As the video width/height might be wrong,
|
||||||
|
Loading…
Reference in New Issue
Block a user