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) {
scale := ebiten.DeviceScaleFactor()
scale := ebiten.Monitor().DeviceScaleFactor()
w, h := gophersImage.Bounds().Dx(), gophersImage.Bounds().Dy()
op := &ebiten.DrawImageOptions{}
@ -99,15 +99,15 @@ func (g *Game) Draw(screen *ebiten.Image) {
textOp := &text.DrawOptions{}
textOp.GeoM.Translate(50*scale, 50*scale)
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{
Source: mplusFaceSource,
Size: 12 * ebiten.DeviceScaleFactor(),
Size: 12 * ebiten.Monitor().DeviceScaleFactor(),
}, textOp)
}
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)
}

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
// on various (different-DPI) environments.
scale := ebiten.DeviceScaleFactor()
scale := ebiten.Monitor().DeviceScaleFactor()
op.GeoM.Scale(scale, scale)
// 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) {
// 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.
s := ebiten.DeviceScaleFactor()
s := ebiten.Monitor().DeviceScaleFactor()
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)
TPS: Current: %0.2f / Max: %s
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)
}
@ -400,7 +400,7 @@ func parseWindowPosition() (int, int, bool) {
}
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()
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) {
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 {
return err
}
s := m.deviceScaleFactor()
s := m.DeviceScaleFactor()
cx, cy := u.savedCursorX, u.savedCursorY
defer func() {

View File

@ -305,7 +305,7 @@ func (u *UserInterface) updateInputState() error {
u.keyDurationsByKeyProperty[key]++
}
s := u.DeviceScaleFactor()
s := theMonitor.DeviceScaleFactor()
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.

View File

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

View File

@ -60,7 +60,7 @@ func (u *UserInterface) updateInputStateImpl() error {
u.inputState.Touches = u.inputState.Touches[:0]
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{
ID: TouchID(t.id),
X: int(x),

View File

@ -40,7 +40,8 @@ func (m *Monitor) Name() string {
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.
// In this case, return 1 as a tentative scale (#1878).
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 must be called from the main thread.
@ -988,7 +965,7 @@ func (u *UserInterface) registerWindowFramebufferSizeCallback() error {
u.setError(err)
return
}
s := m.deviceScaleFactor()
s := m.DeviceScaleFactor()
ww := int(float64(w) / s)
wh := int(float64(h) / s)
if err := u.setWindowSizeInDIP(ww, wh, false); err != nil {
@ -1460,7 +1437,7 @@ func (u *UserInterface) updateGame() error {
if err != nil {
return
}
deviceScaleFactor = m.deviceScaleFactor()
deviceScaleFactor = m.DeviceScaleFactor()
}); err != nil {
return err
}
@ -1639,7 +1616,7 @@ func (u *UserInterface) setWindowSizeInDIP(width, height int, callSetSize bool)
if err != nil {
return err
}
scale := mon.deviceScaleFactor()
scale := mon.DeviceScaleFactor()
if u.origWindowWidthInDIP == width && u.origWindowHeightInDIP == height && u.lastDeviceScaleFactor == scale {
return nil
}

View File

@ -97,8 +97,6 @@ type userInterfaceImpl struct {
onceUpdateCalled bool
lastCaptureExitTime time.Time
deviceScaleFactor float64
context *context
inputState InputState
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) {
if document.Truthy() {
body := document.Get("body")
@ -365,11 +350,11 @@ func (u *UserInterface) updateImpl(force bool) error {
w, h := u.outsideSize()
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
}
} 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
}
}
@ -771,8 +756,9 @@ func (u *UserInterface) initOnMainThread(options *RunOptions) error {
func (u *UserInterface) updateScreenSize() {
if document.Truthy() {
body := document.Get("body")
bw := int(body.Get("clientWidth").Float() * u.DeviceScaleFactor())
bh := int(body.Get("clientHeight").Float() * u.DeviceScaleFactor())
f := theMonitor.DeviceScaleFactor()
bw := int(body.Get("clientWidth").Float() * f)
bh := int(body.Get("clientHeight").Float() * f)
canvas.Set("width", bw)
canvas.Set("height", bh)
}
@ -787,7 +773,9 @@ func (u *UserInterface) Window() Window {
return &nullWindow{}
}
type Monitor struct{}
type Monitor struct {
deviceScaleFactor float64
}
var theMonitor = &Monitor{}
@ -795,6 +783,19 @@ func (m *Monitor) Name() string {
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 {
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 {
return x / monitor.deviceScaleFactor()
return x / monitor.DeviceScaleFactor()
}
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) {

View File

@ -89,9 +89,6 @@ type userInterfaceImpl struct {
outsideWidth float64
outsideHeight float64
deviceScaleFactorOnce sync.Once
deviceScaleFactor float64
foreground int32
errCh chan error
@ -179,8 +176,11 @@ func (u *UserInterface) update() error {
renderEndCh <- struct{}{}
}()
// The device scale factor can be obtained after the main function starts, especially on Android.
theMonitor.initDeviceScaleFactorIfNeeded()
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 nil
@ -256,14 +256,6 @@ func (u *UserInterface) updateExplicitRenderingModeIfNeeded(fpsMode FPSModeType)
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) {
u.m.Lock()
defer u.m.Unlock()
@ -274,7 +266,11 @@ func (u *UserInterface) Window() Window {
return &nullWindow{}
}
type Monitor struct{}
type Monitor struct {
deviceScaleFactor float64
m sync.Mutex
}
var theMonitor = &Monitor{}
@ -282,6 +278,22 @@ func (m *Monitor) Name() string {
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 {
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")
}
const deviceScaleFactor = 1
func init() {
runtime.LockOSThread()
}
@ -94,16 +92,12 @@ func (u *UserInterface) loopGame() error {
for {
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
}
}
}
func (*UserInterface) DeviceScaleFactor() float64 {
return deviceScaleFactor
}
func (*UserInterface) IsFocused() bool {
return true
}
@ -172,6 +166,10 @@ func (m *Monitor) Name() string {
return ""
}
func (m *Monitor) DeviceScaleFactor() float64 {
return 1
}
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
return append(mons, theMonitor)
}

View File

@ -50,9 +50,8 @@ func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, er
const (
// TODO: Get this value from the SDK.
screenWidth = 3840
screenHeight = 2160
deviceScaleFactor = 1
screenWidth = 3840
screenHeight = 2160
)
func init() {
@ -82,17 +81,13 @@ func (u *UserInterface) initOnMainThread(options *RunOptions) error {
func (u *UserInterface) loopGame() error {
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 nil
}
func (*UserInterface) DeviceScaleFactor() float64 {
return deviceScaleFactor
}
func (*UserInterface) IsFocused() bool {
return true
}
@ -164,6 +159,10 @@ func (m *Monitor) Name() string {
return ""
}
func (m *Monitor) DeviceScaleFactor() float64 {
return 1
}
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
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 {
return x / monitor.deviceScaleFactor()
return x / monitor.DeviceScaleFactor()
}
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) {

View File

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

View File

@ -27,6 +27,20 @@ func (m *MonitorType) Name() string {
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.
func Monitor() *MonitorType {
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
// loop.
//
// DeviceScaleFactor is concurrent-safe.
//
// BUG: DeviceScaleFactor value is not affected by SetWindowPosition before RunGame (#1575).
//
// Deprecated: as of v2.6. Use Monitor().DeviceScaleFactor() instead.
func DeviceScaleFactor() float64 {
return ui.Get().DeviceScaleFactor()
return Monitor().DeviceScaleFactor()
}
// IsVsyncEnabled returns a boolean value indicating whether