mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +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
@ -23,13 +23,13 @@ type pos struct {
|
||||
}
|
||||
|
||||
var (
|
||||
m sync.Mutex
|
||||
cache = map[pos]float64{}
|
||||
m sync.Mutex
|
||||
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".
|
||||
// The device scale maps device dependent pixels to device independent pixels.
|
||||
func GetAt(x, y int) float64 {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
@ -46,3 +46,16 @@ func GetAt(x, y int) float64 {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func videoModeScaleImpl(x, y int) float64 {
|
||||
return 1
|
||||
}
|
||||
|
@ -33,3 +33,8 @@ func monitorAt(x, y int) *glfw.Monitor {
|
||||
}
|
||||
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 {
|
||||
return float64(C.devicePixelRatio())
|
||||
}
|
||||
|
||||
func videoModeScaleImpl(x, y int) float64 {
|
||||
return 1
|
||||
}
|
||||
|
@ -33,3 +33,7 @@ func impl(x, y int) float64 {
|
||||
}
|
||||
return ratio
|
||||
}
|
||||
|
||||
func videoModeScaleImpl(x, y int) float64 {
|
||||
return 1
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
package devicescale
|
||||
|
||||
func impl(x, y int) float64 {
|
||||
sx, _ := monitorAt(x, y).GetContentScale()
|
||||
return float64(sx)
|
||||
func videoModeScaleImpl(x, y int) float64 {
|
||||
return 1
|
||||
}
|
||||
|
@ -25,9 +25,9 @@ import (
|
||||
"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.
|
||||
// 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,
|
||||
// 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.
|
||||
// So this function computes the ratio of physical per logical pixels.
|
||||
m := monitorAt(x, y)
|
||||
sx, _ := m.GetContentScale()
|
||||
monitorX, monitorY := m.GetPos()
|
||||
xconn, err := xgb.NewConn()
|
||||
defer xconn.Close()
|
||||
@ -43,19 +42,19 @@ func impl(x, y int) float64 {
|
||||
// No X11 connection?
|
||||
// Assume we're on pure Wayland then.
|
||||
// GLFW/Wayland shouldn't be having this issue.
|
||||
return float64(sx)
|
||||
return 1
|
||||
}
|
||||
if err = randr.Init(xconn); err != nil {
|
||||
// No RANDR extension?
|
||||
// No problem.
|
||||
return float64(sx)
|
||||
return 1
|
||||
}
|
||||
root := xproto.Setup(xconn).DefaultScreen(xconn).Root
|
||||
res, err := randr.GetScreenResourcesCurrent(xconn, root).Reply()
|
||||
if err != nil {
|
||||
// Likely means RANDR is not working.
|
||||
// No problem.
|
||||
return float64(sx)
|
||||
return 1
|
||||
}
|
||||
for _, crtc := range res.Crtcs[:res.NumCrtcs] {
|
||||
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 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))
|
||||
return float64(sx) * scale
|
||||
return scale
|
||||
}
|
||||
}
|
||||
// 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()
|
||||
// TODO: This is tricky. Rename the function?
|
||||
s := i.ui.deviceScaleFactor()
|
||||
cx = fromGLFWMonitorPixel(cx, s)
|
||||
cy = fromGLFWMonitorPixel(cy, s)
|
||||
cx = i.ui.fromGLFWPixel(cx)
|
||||
cy = i.ui.fromGLFWPixel(cy)
|
||||
cx, cy = context.AdjustPosition(cx, cy, s)
|
||||
|
||||
// AdjustPosition can return NaN at the initialization.
|
||||
|
@ -189,9 +189,10 @@ func initialize() error {
|
||||
m := currentMonitor(w)
|
||||
theUI.initMonitor = m
|
||||
v := m.GetVideoMode()
|
||||
scale := devicescale.GetAt(currentMonitor(w).GetPos())
|
||||
theUI.initFullscreenWidthInDP = int(fromGLFWMonitorPixel(float64(v.Width), scale))
|
||||
theUI.initFullscreenHeightInDP = int(fromGLFWMonitorPixel(float64(v.Height), scale))
|
||||
mx, my := currentMonitor(w).GetPos()
|
||||
scale := devicescale.VideoModeScaleAt(mx, my)
|
||||
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().
|
||||
glfwSystemCursors[driver.CursorShapeDefault] = nil
|
||||
@ -528,10 +529,12 @@ func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
|
||||
|
||||
var w, h int
|
||||
_ = u.t.Call(func() error {
|
||||
v := currentMonitor(u.window).GetVideoMode()
|
||||
s := u.deviceScaleFactor()
|
||||
w = int(fromGLFWMonitorPixel(float64(v.Width), s))
|
||||
h = int(fromGLFWMonitorPixel(float64(v.Height), s))
|
||||
m := currentMonitor(u.window)
|
||||
v := m.GetVideoMode()
|
||||
mx, my := m.GetPos()
|
||||
s := devicescale.VideoModeScaleAt(mx, my)
|
||||
w = int(u.fromGLFWMonitorPixel(float64(v.Width), s))
|
||||
h = int(u.fromGLFWMonitorPixel(float64(v.Height), s))
|
||||
return nil
|
||||
})
|
||||
return w, h
|
||||
@ -707,7 +710,7 @@ func (u *UserInterface) SetCursorShape(shape driver.CursorShape) {
|
||||
func (u *UserInterface) DeviceScaleFactor() float64 {
|
||||
if !u.isRunning() {
|
||||
// TODO: Use the initWindowPosition. This requires to convert the units correctly (#1575).
|
||||
return devicescale.GetAt(u.initMonitor.GetPos())
|
||||
return u.deviceScaleFactor()
|
||||
}
|
||||
|
||||
f := 0.0
|
||||
@ -724,7 +727,8 @@ func (u *UserInterface) deviceScaleFactor() float64 {
|
||||
if u.window != nil {
|
||||
m = currentMonitor(u.window)
|
||||
}
|
||||
return devicescale.GetAt(m.GetPos())
|
||||
mx, my := m.GetPos()
|
||||
return devicescale.GetAt(mx, my)
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -933,11 +937,13 @@ func (u *UserInterface) updateSize() (float64, float64, bool) {
|
||||
|
||||
var w, h float64
|
||||
if u.isFullscreen() {
|
||||
v := currentMonitor(u.window).GetVideoMode()
|
||||
m := currentMonitor(u.window)
|
||||
v := m.GetVideoMode()
|
||||
ww, wh := v.Width, v.Height
|
||||
s := u.deviceScaleFactor()
|
||||
w = fromGLFWMonitorPixel(float64(ww), s)
|
||||
h = fromGLFWMonitorPixel(float64(wh), s)
|
||||
mx, my := m.GetPos()
|
||||
s := devicescale.VideoModeScaleAt(mx, my)
|
||||
w = u.fromGLFWMonitorPixel(float64(ww), s)
|
||||
h = u.fromGLFWMonitorPixel(float64(wh), s)
|
||||
} else {
|
||||
// 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
|
||||
@ -946,9 +952,6 @@ func (u *UserInterface) updateSize() (float64, float64, bool) {
|
||||
w = u.fromGLFWPixel(float64(ww))
|
||||
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
|
||||
}
|
||||
|
@ -81,22 +81,27 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||
)
|
||||
|
||||
func fromGLFWMonitorPixel(x float64, deviceScale float64) float64 {
|
||||
return x
|
||||
// fromGLFWMonitorPixel must be called from the main thread.
|
||||
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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
// toGLFWPixel must be called from the main thread.
|
||||
func (u *UserInterface) toGLFWPixel(x float64) float64 {
|
||||
return x
|
||||
}
|
||||
|
||||
func (u *UserInterface) toFramebufferPixel(x float64) float64 {
|
||||
return x
|
||||
}
|
||||
|
||||
func (u *UserInterface) adjustWindowPosition(x, y int) (int, int) {
|
||||
return x, y
|
||||
}
|
||||
|
@ -19,37 +19,23 @@
|
||||
package glfw
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||
)
|
||||
|
||||
// fromGLFWMonitorPixel must be called from the main thread.
|
||||
func fromGLFWMonitorPixel(x float64, deviceScale float64) float64 {
|
||||
// deviceScaleFactor is sometimes an unnice value (e.g., 1.502361). Use math.Ceil to clean the vaule.
|
||||
return math.Ceil(x / deviceScale)
|
||||
func (u *UserInterface) fromGLFWMonitorPixel(x float64, videoModeScale float64) float64 {
|
||||
return x / (videoModeScale * u.deviceScaleFactor())
|
||||
}
|
||||
|
||||
// fromGLFWPixel must be called from the main thread.
|
||||
func (u *UserInterface) fromGLFWPixel(x float64) float64 {
|
||||
// deviceScaleFactor() is a scale by desktop environment (e.g., Cinnamon), while GetContentScale() is X's scale.
|
||||
// They are different things and then need to be treated in different ways (#1350).
|
||||
s, _ := currentMonitor(u.window).GetContentScale()
|
||||
return x / float64(s)
|
||||
return x / u.deviceScaleFactor()
|
||||
}
|
||||
|
||||
// toGLFWPixel must be called from the main thread.
|
||||
func (u *UserInterface) toGLFWPixel(x float64) float64 {
|
||||
s, _ := currentMonitor(u.window).GetContentScale()
|
||||
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())
|
||||
return x * u.deviceScaleFactor()
|
||||
}
|
||||
|
||||
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.
|
||||
func fromGLFWMonitorPixel(x float64, deviceScale float64) float64 {
|
||||
return x / deviceScale
|
||||
func (u *UserInterface) fromGLFWMonitorPixel(x float64, videoModeScale float64) float64 {
|
||||
return x / (videoModeScale * u.deviceScaleFactor())
|
||||
}
|
||||
|
||||
// fromGLFWPixel must be called from the main thread.
|
||||
@ -113,11 +113,6 @@ func (u *UserInterface) toGLFWPixel(x float64) float64 {
|
||||
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) {
|
||||
mx, my := currentMonitor(u.window).GetPos()
|
||||
// As the video width/height might be wrong,
|
||||
|
Loading…
Reference in New Issue
Block a user