ebiten: add (*Monitor).DeviceScaleFactor()

This replaces ebiten.DeviceScaleFactor().

Updates #2795
This commit is contained in:
Hajime Hoshi 2024-02-12 15:03:07 +09:00
parent 67d947d37a
commit 6d898d752e
19 changed files with 98 additions and 96 deletions

View File

@ -72,7 +72,7 @@ func (g *Game) Update() error {
} }
func (g *Game) Draw(screen *ebiten.Image) { func (g *Game) Draw(screen *ebiten.Image) {
scale := ebiten.DeviceScaleFactor() scale := ebiten.Monitor().DeviceScaleFactor()
w, h := gophersImage.Bounds().Dx(), gophersImage.Bounds().Dy() w, h := gophersImage.Bounds().Dx(), gophersImage.Bounds().Dy()
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
@ -99,15 +99,15 @@ func (g *Game) Draw(screen *ebiten.Image) {
textOp := &text.DrawOptions{} textOp := &text.DrawOptions{}
textOp.GeoM.Translate(50*scale, 50*scale) textOp.GeoM.Translate(50*scale, 50*scale)
textOp.ColorScale.ScaleWithColor(color.White) textOp.ColorScale.ScaleWithColor(color.White)
textOp.LineSpacing = 12 * ebiten.DeviceScaleFactor() * 1.5 textOp.LineSpacing = 12 * ebiten.Monitor().DeviceScaleFactor() * 1.5
text.Draw(screen, msg, &text.GoTextFace{ text.Draw(screen, msg, &text.GoTextFace{
Source: mplusFaceSource, Source: mplusFaceSource,
Size: 12 * ebiten.DeviceScaleFactor(), Size: 12 * ebiten.Monitor().DeviceScaleFactor(),
}, textOp) }, textOp)
} }
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
s := ebiten.DeviceScaleFactor() s := ebiten.Monitor().DeviceScaleFactor()
return int(float64(outsideWidth) * s), int(float64(outsideHeight) * s) return int(float64(outsideWidth) * s), int(float64(outsideHeight) * s)
} }

View File

@ -84,7 +84,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
// Scale the image by the device ratio so that the rendering result can be same // Scale the image by the device ratio so that the rendering result can be same
// on various (different-DPI) environments. // on various (different-DPI) environments.
scale := ebiten.DeviceScaleFactor() scale := ebiten.Monitor().DeviceScaleFactor()
op.GeoM.Scale(scale, scale) op.GeoM.Scale(scale, scale)
// Move the image's center to the screen's center. // Move the image's center to the screen's center.
@ -99,7 +99,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
// The unit of outsideWidth/Height is device-independent pixels. // The unit of outsideWidth/Height is device-independent pixels.
// By multiplying them by the device scale factor, we can get a hi-DPI screen size. // By multiplying them by the device scale factor, we can get a hi-DPI screen size.
s := ebiten.DeviceScaleFactor() s := ebiten.Monitor().DeviceScaleFactor()
return int(float64(outsideWidth) * s), int(float64(outsideHeight) * s) return int(float64(outsideWidth) * s), int(float64(outsideHeight) * s)
} }

View File

@ -376,7 +376,7 @@ Window size limitation: (%d, %d) - (%d, %d)
Cursor: (%d, %d) Cursor: (%d, %d)
TPS: Current: %0.2f / Max: %s TPS: Current: %0.2f / Max: %s
FPS: %0.2f FPS: %0.2f
Device Scale Factor: %0.2f`, msgM, msgR, fg, wx, wy, ww, wh, minw, minh, maxw, maxh, cx, cy, ebiten.ActualTPS(), tpsStr, ebiten.ActualFPS(), ebiten.DeviceScaleFactor()) Device Scale Factor: %0.2f`, msgM, msgR, fg, wx, wy, ww, wh, minw, minh, maxw, maxh, cx, cy, ebiten.ActualTPS(), tpsStr, ebiten.ActualFPS(), ebiten.Monitor().DeviceScaleFactor())
ebitenutil.DebugPrint(screen, msg) ebitenutil.DebugPrint(screen, msg)
} }
@ -400,7 +400,7 @@ func parseWindowPosition() (int, int, bool) {
} }
func main() { func main() {
fmt.Printf("Device scale factor: %0.2f\n", ebiten.DeviceScaleFactor()) fmt.Printf("Device scale factor: %0.2f\n", ebiten.Monitor().DeviceScaleFactor())
w, h := ebiten.ScreenSizeInFullscreen() w, h := ebiten.ScreenSizeInFullscreen()
fmt.Printf("Screen size in fullscreen: %d, %d\n", w, h) fmt.Printf("Screen size in fullscreen: %d, %d\n", w, h)

View File

@ -286,5 +286,5 @@ func (c *context) screenScaleAndOffsets() (scale, offsetX, offsetY float64) {
} }
func (u *UserInterface) LogicalPositionToClientPosition(x, y float64) (float64, float64) { func (u *UserInterface) LogicalPositionToClientPosition(x, y float64) (float64, float64) {
return u.context.logicalPositionToClientPosition(x, y, u.DeviceScaleFactor()) return u.context.logicalPositionToClientPosition(x, y, u.Monitor().DeviceScaleFactor())
} }

View File

@ -86,7 +86,7 @@ func (u *UserInterface) updateInputStateImpl() error {
if err != nil { if err != nil {
return err return err
} }
s := m.deviceScaleFactor() s := m.DeviceScaleFactor()
cx, cy := u.savedCursorX, u.savedCursorY cx, cy := u.savedCursorX, u.savedCursorY
defer func() { defer func() {

View File

@ -305,7 +305,7 @@ func (u *UserInterface) updateInputState() error {
u.keyDurationsByKeyProperty[key]++ u.keyDurationsByKeyProperty[key]++
} }
s := u.DeviceScaleFactor() s := theMonitor.DeviceScaleFactor()
if !math.IsNaN(u.savedCursorX) && !math.IsNaN(u.savedCursorY) { if !math.IsNaN(u.savedCursorX) && !math.IsNaN(u.savedCursorY) {
// If savedCursorX and savedCursorY are valid values, the cursor is saved just before entering or exiting from fullscreen. // If savedCursorX and savedCursorY are valid values, the cursor is saved just before entering or exiting from fullscreen.

View File

@ -47,7 +47,7 @@ func (u *UserInterface) updateInputState() error {
u.m.Lock() u.m.Lock()
defer u.m.Unlock() defer u.m.Unlock()
s := u.DeviceScaleFactor() s := theMonitor.DeviceScaleFactor()
u.inputState.Touches = u.inputState.Touches[:0] u.inputState.Touches = u.inputState.Touches[:0]
for _, t := range u.touches { for _, t := range u.touches {

View File

@ -60,7 +60,7 @@ func (u *UserInterface) updateInputStateImpl() error {
u.inputState.Touches = u.inputState.Touches[:0] u.inputState.Touches = u.inputState.Touches[:0]
for _, t := range u.nativeTouches { for _, t := range u.nativeTouches {
x, y := u.context.clientPositionToLogicalPosition(float64(t.x), float64(t.y), deviceScaleFactor) x, y := u.context.clientPositionToLogicalPosition(float64(t.x), float64(t.y), theMonitor.DeviceScaleFactor())
u.inputState.Touches = append(u.inputState.Touches, Touch{ u.inputState.Touches = append(u.inputState.Touches, Touch{
ID: TouchID(t.id), ID: TouchID(t.id),
X: int(x), X: int(x),

View File

@ -40,7 +40,8 @@ func (m *Monitor) Name() string {
return m.name return m.name
} }
func (m *Monitor) deviceScaleFactor() float64 { // DeviceScaleFactor is concurrent-safe as contentScale is immutable.
func (m *Monitor) DeviceScaleFactor() float64 {
// It is rare, but monitor can be nil when glfw.GetPrimaryMonitor returns nil. // It is rare, but monitor can be nil when glfw.GetPrimaryMonitor returns nil.
// In this case, return 1 as a tentative scale (#1878). // In this case, return 1 as a tentative scale (#1878).
if m == nil { if m == nil {

View File

@ -833,29 +833,6 @@ func (u *UserInterface) SetCursorShape(shape CursorShape) {
}) })
} }
func (u *UserInterface) DeviceScaleFactor() float64 {
if u.isTerminated() {
return 0
}
if !u.isRunning() {
return u.getInitMonitor().deviceScaleFactor()
}
var f float64
u.mainThread.Call(func() {
if u.isTerminated() {
return
}
m, err := u.currentMonitor()
if err != nil {
u.setError(err)
return
}
f = m.deviceScaleFactor()
})
return f
}
// createWindow creates a GLFW window. // createWindow creates a GLFW window.
// //
// createWindow must be called from the main thread. // createWindow must be called from the main thread.
@ -988,7 +965,7 @@ func (u *UserInterface) registerWindowFramebufferSizeCallback() error {
u.setError(err) u.setError(err)
return return
} }
s := m.deviceScaleFactor() s := m.DeviceScaleFactor()
ww := int(float64(w) / s) ww := int(float64(w) / s)
wh := int(float64(h) / s) wh := int(float64(h) / s)
if err := u.setWindowSizeInDIP(ww, wh, false); err != nil { if err := u.setWindowSizeInDIP(ww, wh, false); err != nil {
@ -1460,7 +1437,7 @@ func (u *UserInterface) updateGame() error {
if err != nil { if err != nil {
return return
} }
deviceScaleFactor = m.deviceScaleFactor() deviceScaleFactor = m.DeviceScaleFactor()
}); err != nil { }); err != nil {
return err return err
} }
@ -1639,7 +1616,7 @@ func (u *UserInterface) setWindowSizeInDIP(width, height int, callSetSize bool)
if err != nil { if err != nil {
return err return err
} }
scale := mon.deviceScaleFactor() scale := mon.DeviceScaleFactor()
if u.origWindowWidthInDIP == width && u.origWindowHeightInDIP == height && u.lastDeviceScaleFactor == scale { if u.origWindowWidthInDIP == width && u.origWindowHeightInDIP == height && u.lastDeviceScaleFactor == scale {
return nil return nil
} }

View File

@ -97,8 +97,6 @@ type userInterfaceImpl struct {
onceUpdateCalled bool onceUpdateCalled bool
lastCaptureExitTime time.Time lastCaptureExitTime time.Time
deviceScaleFactor float64
context *context context *context
inputState InputState inputState InputState
keyDurationsByKeyProperty map[Key]int keyDurationsByKeyProperty map[Key]int
@ -271,19 +269,6 @@ func (u *UserInterface) SetCursorShape(shape CursorShape) {
} }
} }
func (u *UserInterface) DeviceScaleFactor() float64 {
if u.deviceScaleFactor != 0 {
return u.deviceScaleFactor
}
ratio := window.Get("devicePixelRatio").Float()
if ratio == 0 {
ratio = 1
}
u.deviceScaleFactor = ratio
return u.deviceScaleFactor
}
func (u *UserInterface) outsideSize() (float64, float64) { func (u *UserInterface) outsideSize() (float64, float64) {
if document.Truthy() { if document.Truthy() {
body := document.Get("body") body := document.Get("body")
@ -365,11 +350,11 @@ func (u *UserInterface) updateImpl(force bool) error {
w, h := u.outsideSize() w, h := u.outsideSize()
if force { if force {
if err := u.context.forceUpdateFrame(u.graphicsDriver, w, h, u.DeviceScaleFactor(), u); err != nil { if err := u.context.forceUpdateFrame(u.graphicsDriver, w, h, theMonitor.DeviceScaleFactor(), u); err != nil {
return err return err
} }
} else { } else {
if err := u.context.updateFrame(u.graphicsDriver, w, h, u.DeviceScaleFactor(), u); err != nil { if err := u.context.updateFrame(u.graphicsDriver, w, h, theMonitor.DeviceScaleFactor(), u); err != nil {
return err return err
} }
} }
@ -771,8 +756,9 @@ func (u *UserInterface) initOnMainThread(options *RunOptions) error {
func (u *UserInterface) updateScreenSize() { func (u *UserInterface) updateScreenSize() {
if document.Truthy() { if document.Truthy() {
body := document.Get("body") body := document.Get("body")
bw := int(body.Get("clientWidth").Float() * u.DeviceScaleFactor()) f := theMonitor.DeviceScaleFactor()
bh := int(body.Get("clientHeight").Float() * u.DeviceScaleFactor()) bw := int(body.Get("clientWidth").Float() * f)
bh := int(body.Get("clientHeight").Float() * f)
canvas.Set("width", bw) canvas.Set("width", bw)
canvas.Set("height", bh) canvas.Set("height", bh)
} }
@ -787,7 +773,9 @@ func (u *UserInterface) Window() Window {
return &nullWindow{} return &nullWindow{}
} }
type Monitor struct{} type Monitor struct {
deviceScaleFactor float64
}
var theMonitor = &Monitor{} var theMonitor = &Monitor{}
@ -795,6 +783,19 @@ func (m *Monitor) Name() string {
return "" return ""
} }
func (m *Monitor) DeviceScaleFactor() float64 {
if m.deviceScaleFactor != 0 {
return m.deviceScaleFactor
}
ratio := window.Get("devicePixelRatio").Float()
if ratio == 0 {
ratio = 1
}
m.deviceScaleFactor = ratio
return m.deviceScaleFactor
}
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
return append(mons, theMonitor) return append(mons, theMonitor)
} }

View File

@ -123,11 +123,11 @@ func glfwMonitorSizeInGLFWPixels(m *glfw.Monitor) (int, int, error) {
} }
func dipFromGLFWPixel(x float64, monitor *Monitor) float64 { func dipFromGLFWPixel(x float64, monitor *Monitor) float64 {
return x / monitor.deviceScaleFactor() return x / monitor.DeviceScaleFactor()
} }
func dipToGLFWPixel(x float64, monitor *Monitor) float64 { func dipToGLFWPixel(x float64, monitor *Monitor) float64 {
return x * monitor.deviceScaleFactor() return x * monitor.DeviceScaleFactor()
} }
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) { func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {

View File

@ -89,9 +89,6 @@ type userInterfaceImpl struct {
outsideWidth float64 outsideWidth float64
outsideHeight float64 outsideHeight float64
deviceScaleFactorOnce sync.Once
deviceScaleFactor float64
foreground int32 foreground int32
errCh chan error errCh chan error
@ -179,8 +176,11 @@ func (u *UserInterface) update() error {
renderEndCh <- struct{}{} renderEndCh <- struct{}{}
}() }()
// The device scale factor can be obtained after the main function starts, especially on Android.
theMonitor.initDeviceScaleFactorIfNeeded()
w, h := u.outsideSize() w, h := u.outsideSize()
if err := u.context.updateFrame(u.graphicsDriver, w, h, u.DeviceScaleFactor(), u); err != nil { if err := u.context.updateFrame(u.graphicsDriver, w, h, theMonitor.DeviceScaleFactor(), u); err != nil {
return err return err
} }
return nil return nil
@ -256,14 +256,6 @@ func (u *UserInterface) updateExplicitRenderingModeIfNeeded(fpsMode FPSModeType)
u.renderRequester.SetExplicitRenderingMode(fpsMode == FPSModeVsyncOffMinimum) u.renderRequester.SetExplicitRenderingMode(fpsMode == FPSModeVsyncOffMinimum)
} }
func (u *UserInterface) DeviceScaleFactor() float64 {
// Assume that the device scale factor never changes on mobiles.
u.deviceScaleFactorOnce.Do(func() {
u.deviceScaleFactor = deviceScaleFactorImpl()
})
return u.deviceScaleFactor
}
func (u *UserInterface) readInputState(inputState *InputState) { func (u *UserInterface) readInputState(inputState *InputState) {
u.m.Lock() u.m.Lock()
defer u.m.Unlock() defer u.m.Unlock()
@ -274,7 +266,11 @@ func (u *UserInterface) Window() Window {
return &nullWindow{} return &nullWindow{}
} }
type Monitor struct{} type Monitor struct {
deviceScaleFactor float64
m sync.Mutex
}
var theMonitor = &Monitor{} var theMonitor = &Monitor{}
@ -282,6 +278,22 @@ func (m *Monitor) Name() string {
return "" return ""
} }
func (m *Monitor) initDeviceScaleFactorIfNeeded() {
// Assume that the device scale factor never changes on mobiles.
m.m.Lock()
defer m.m.Unlock()
if m.deviceScaleFactor != 0 {
return
}
m.deviceScaleFactor = deviceScaleFactorImpl()
}
func (m *Monitor) DeviceScaleFactor() float64 {
m.m.Lock()
defer m.m.Unlock()
return m.deviceScaleFactor
}
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
return append(mons, theMonitor) return append(mons, theMonitor)
} }

View File

@ -54,8 +54,6 @@ func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, er
return nil, errors.New("ui: PlayStation 5 is not supported in this environment") return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
} }
const deviceScaleFactor = 1
func init() { func init() {
runtime.LockOSThread() runtime.LockOSThread()
} }
@ -94,16 +92,12 @@ func (u *UserInterface) loopGame() error {
for { for {
recordProfilerHeartbeat() recordProfilerHeartbeat()
if err := u.context.updateFrame(u.graphicsDriver, float64(C.kScreenWidth), float64(C.kScreenHeight), deviceScaleFactor, u); err != nil { if err := u.context.updateFrame(u.graphicsDriver, float64(C.kScreenWidth), float64(C.kScreenHeight), theMonitor.DeviceScaleFactor(), u); err != nil {
return err return err
} }
} }
} }
func (*UserInterface) DeviceScaleFactor() float64 {
return deviceScaleFactor
}
func (*UserInterface) IsFocused() bool { func (*UserInterface) IsFocused() bool {
return true return true
} }
@ -172,6 +166,10 @@ func (m *Monitor) Name() string {
return "" return ""
} }
func (m *Monitor) DeviceScaleFactor() float64 {
return 1
}
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
return append(mons, theMonitor) return append(mons, theMonitor)
} }

View File

@ -52,7 +52,6 @@ const (
// TODO: Get this value from the SDK. // TODO: Get this value from the SDK.
screenWidth = 3840 screenWidth = 3840
screenHeight = 2160 screenHeight = 2160
deviceScaleFactor = 1
) )
func init() { func init() {
@ -82,17 +81,13 @@ func (u *UserInterface) initOnMainThread(options *RunOptions) error {
func (u *UserInterface) loopGame() error { func (u *UserInterface) loopGame() error {
for { for {
if err := u.context.updateFrame(u.graphicsDriver, screenWidth, screenHeight, deviceScaleFactor, u); err != nil { if err := u.context.updateFrame(u.graphicsDriver, screenWidth, screenHeight, theMonitor.DeviceScaleFactor(), u); err != nil {
return err return err
} }
} }
return nil return nil
} }
func (*UserInterface) DeviceScaleFactor() float64 {
return deviceScaleFactor
}
func (*UserInterface) IsFocused() bool { func (*UserInterface) IsFocused() bool {
return true return true
} }
@ -164,6 +159,10 @@ func (m *Monitor) Name() string {
return "" return ""
} }
func (m *Monitor) DeviceScaleFactor() float64 {
return 1
}
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor { func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
return append(mons, theMonitor) return append(mons, theMonitor)
} }

View File

@ -102,11 +102,11 @@ func glfwMonitorSizeInGLFWPixels(m *glfw.Monitor) (int, int, error) {
} }
func dipFromGLFWPixel(x float64, monitor *Monitor) float64 { func dipFromGLFWPixel(x float64, monitor *Monitor) float64 {
return x / monitor.deviceScaleFactor() return x / monitor.DeviceScaleFactor()
} }
func dipToGLFWPixel(x float64, monitor *Monitor) float64 { func dipToGLFWPixel(x float64, monitor *Monitor) float64 {
return x * monitor.deviceScaleFactor() return x * monitor.DeviceScaleFactor()
} }
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) { func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {

View File

@ -113,7 +113,7 @@ func Resume() error {
} }
func DeviceScale() float64 { func DeviceScale() float64 {
return ui.Get().DeviceScaleFactor() return ui.Get().Monitor().DeviceScaleFactor()
} }
type RenderRequester interface { type RenderRequester interface {

View File

@ -27,6 +27,20 @@ func (m *MonitorType) Name() string {
return (*ui.Monitor)(m).Name() return (*ui.Monitor)(m).Name()
} }
// DeviceScaleFactor returns the device scale factor of the monitor.
//
// DeviceScaleFactor returns a meaningful value on high-DPI display environment,
// otherwise DeviceScaleFactor returns 1.
//
// DeviceScaleFactor might panic on init function on some devices like Android.
// Then, it is not recommended to call DeviceScaleFactor from init functions.
//
// DeviceScaleFactor must be called on the main thread before the main loop,
// and is concurrent-safe after the main loop.
func (m *MonitorType) DeviceScaleFactor() float64 {
return (*ui.Monitor)(m).DeviceScaleFactor()
}
// Monitor returns the current monitor. // Monitor returns the current monitor.
func Monitor() *MonitorType { func Monitor() *MonitorType {
m := ui.Get().Monitor() m := ui.Get().Monitor()

6
run.go
View File

@ -480,11 +480,11 @@ func SetRunnableOnUnfocused(runnableOnUnfocused bool) {
// DeviceScaleFactor must be called on the main thread before the main loop, and is concurrent-safe after the main // DeviceScaleFactor must be called on the main thread before the main loop, and is concurrent-safe after the main
// loop. // loop.
// //
// DeviceScaleFactor is concurrent-safe.
//
// BUG: DeviceScaleFactor value is not affected by SetWindowPosition before RunGame (#1575). // BUG: DeviceScaleFactor value is not affected by SetWindowPosition before RunGame (#1575).
//
// Deprecated: as of v2.6. Use Monitor().DeviceScaleFactor() instead.
func DeviceScaleFactor() float64 { func DeviceScaleFactor() float64 {
return ui.Get().DeviceScaleFactor() return Monitor().DeviceScaleFactor()
} }
// IsVsyncEnabled returns a boolean value indicating whether // IsVsyncEnabled returns a boolean value indicating whether