From e25c237a01ec3b198aabf53626de0461b80046f5 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 14 Jul 2018 20:50:09 +0900 Subject: [PATCH] Add IsVsyncEnabled / SetVsyncEnabled This enables the game to work more efficiently (but consume much more CPU). Fixes #405. --- examples/windowsize/main.go | 7 ++++++ internal/ui/ui_glfw.go | 50 +++++++++++++++++++++++++++++++------ internal/ui/ui_js.go | 17 ++++++++++++- internal/ui/ui_mobile.go | 8 ++++++ run.go | 25 +++++++++++++++++++ 5 files changed, 98 insertions(+), 9 deletions(-) diff --git a/examples/windowsize/main.go b/examples/windowsize/main.go index c21dea1fd..9a388b86c 100644 --- a/examples/windowsize/main.go +++ b/examples/windowsize/main.go @@ -86,6 +86,7 @@ func update(screen *ebiten.Image) error { fullscreen := ebiten.IsFullscreen() runnableInBackground := ebiten.IsRunnableInBackground() cursorVisible := ebiten.IsCursorVisible() + vsyncEnabled := ebiten.IsVsyncEnabled() if inpututil.IsKeyJustPressed(ebiten.KeyUp) { screenHeight += d @@ -126,11 +127,16 @@ func update(screen *ebiten.Image) error { if inpututil.IsKeyJustPressed(ebiten.KeyC) { cursorVisible = !cursorVisible } + if inpututil.IsKeyJustPressed(ebiten.KeyV) { + vsyncEnabled = !vsyncEnabled + } + ebiten.SetScreenSize(screenWidth, screenHeight) ebiten.SetScreenScale(screenScale) ebiten.SetFullscreen(fullscreen) ebiten.SetRunnableInBackground(runnableInBackground) ebiten.SetCursorVisible(cursorVisible) + ebiten.SetVsyncEnabled(vsyncEnabled) if inpututil.IsKeyJustPressed(ebiten.KeyI) { ebiten.SetWindowIcon([]image.Image{createRandomIconImage()}) @@ -159,6 +165,7 @@ Press F key to switch the fullscreen state Press B key to switch the run-in-background state Press C key to switch the cursor visibility Press I key to change the window icon +Press V key to switch vsync Press Q key to quit Cursor: (%d, %d) FPS: %0.2f`, x, y, ebiten.CurrentFPS()) diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index f3a83cbf4..833ac7a4a 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -49,6 +49,7 @@ type userInterface struct { origPosX int origPosY int runnableInBackground bool + vsync bool initFullscreen bool initCursorVisible bool @@ -66,6 +67,7 @@ var ( origPosY: -1, initCursorVisible: true, initWindowDecorated: true, + vsync: true, } ) @@ -241,7 +243,7 @@ func SetScreenSize(width, height int) bool { } r := false _ = u.runOnMainThread(func() error { - r = u.setScreenSize(width, height, u.scale, u.fullscreen()) + r = u.setScreenSize(width, height, u.scale, u.fullscreen(), u.vsync) return nil }) return r @@ -254,7 +256,7 @@ func SetScreenScale(scale float64) bool { } r := false _ = u.runOnMainThread(func() error { - r = u.setScreenSize(u.width, u.height, scale, u.fullscreen()) + r = u.setScreenSize(u.width, u.height, scale, u.fullscreen(), u.vsync) return nil }) return r @@ -302,7 +304,7 @@ func SetFullscreen(fullscreen bool) { } _ = u.runOnMainThread(func() error { u := currentUI - u.setScreenSize(u.width, u.height, u.scale, fullscreen) + u.setScreenSize(u.width, u.height, u.scale, fullscreen, u.vsync) return nil }) } @@ -315,6 +317,32 @@ func IsRunnableInBackground() bool { return currentUI.isRunnableInBackground() } +func SetVsyncEnabled(enabled bool) { + u := currentUI + if !u.isRunning() { + _ = u.runOnMainThread(func() error { + u.vsync = enabled + return nil + }) + return + } + _ = u.runOnMainThread(func() error { + u := currentUI + u.setScreenSize(u.width, u.height, u.scale, u.fullscreen(), enabled) + return nil + }) +} + +func IsVsyncEnabled() bool { + r := false + u := currentUI + _ = u.runOnMainThread(func() error { + r = currentUI.vsync + return nil + }) + return r +} + func SetWindowTitle(title string) { if !currentUI.isRunning() { return @@ -459,7 +487,7 @@ func Run(width, height int, scale float64, title string, g GraphicsContext, main // The game is in window mode (not fullscreen mode) at the first state. // Don't refer u.initFullscreen here to avoid some GLFW problems. - u.setScreenSize(width, height, scale, false) + u.setScreenSize(width, height, scale, false, u.vsync) u.title = title u.window.SetTitle(title) u.window.Show() @@ -542,7 +570,7 @@ func (u *userInterface) update(g GraphicsContext) error { _ = u.runOnMainThread(func() error { if u.isInitFullscreen() { u := currentUI - u.setScreenSize(u.width, u.height, u.scale, true) + u.setScreenSize(u.width, u.height, u.scale, true, u.vsync) u.setInitFullscreen(false) } return nil @@ -603,8 +631,8 @@ func (u *userInterface) swapBuffers() { } // setScreenSize must be called from the main thread. -func (u *userInterface) setScreenSize(width, height int, scale float64, fullscreen bool) bool { - if u.width == width && u.height == height && u.scale == scale && u.fullscreen() == fullscreen { +func (u *userInterface) setScreenSize(width, height int, scale float64, fullscreen bool, vsync bool) bool { + if u.width == width && u.height == height && u.scale == scale && u.fullscreen() == fullscreen && u.vsync == vsync { return false } @@ -622,6 +650,7 @@ func (u *userInterface) setScreenSize(width, height int, scale float64, fullscre u.height = height u.scale = scale u.fullscreenScale = 0 + u.vsync = vsync // To make sure the current existing framebuffers are rendered, // swap buffers here before SetSize is called. @@ -665,6 +694,7 @@ func (u *userInterface) setScreenSize(width, height int, scale float64, fullscre // Window title might be lost on macOS after coming back from fullscreen. u.window.SetTitle(u.title) } + // SwapInterval is affected by the current monitor of the window. // This needs to be called at least after SetMonitor. // Without SwapInterval after SetMonitor, vsynch doesn't work (#375). @@ -672,7 +702,11 @@ func (u *userInterface) setScreenSize(width, height int, scale float64, fullscre // TODO: (#405) If triple buffering is needed, SwapInterval(0) should be called, // but is this correct? If glfw.SwapInterval(0) and the driver doesn't support triple // buffering, what will happen? - glfw.SwapInterval(1) + if u.vsync { + glfw.SwapInterval(1) + } else { + glfw.SwapInterval(0) + } u.toChangeSize = true return true diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index 4ee26ff20..03050a64c 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -36,6 +36,7 @@ type userInterface struct { scale float64 fullscreen bool runnableInBackground bool + vsync bool sizeChanged bool windowFocus bool @@ -46,12 +47,14 @@ var currentUI = &userInterface{ sizeChanged: true, windowFocus: true, pageVisible: true, + vsync: true, } var ( window = js.Global().Get("window") document = js.Global().Get("document") requestAnimationFrame = window.Get("requestAnimationFrame") + setTimeoutForLoop = js.Global().Call("eval", "((f) => { setTimeout(f, 0); })") ) func MonitorSize() (int, int) { @@ -86,6 +89,14 @@ func IsRunnableInBackground() bool { return currentUI.runnableInBackground } +func SetVsyncEnabled(enabled bool) { + currentUI.vsync = enabled +} + +func IsVsyncEnabled() bool { + return currentUI.vsync +} + func ScreenPadding() (x0, y0, x1, y1 float64) { return 0, 0, 0, 0 } @@ -215,7 +226,11 @@ func (u *userInterface) loop(g GraphicsContext) error { close(ch) return } - requestAnimationFrame.Invoke(cf) + if u.vsync { + requestAnimationFrame.Invoke(cf) + } else { + setTimeoutForLoop.Invoke(cf) + } } cf = js.NewCallback(f) // Call f asyncly to be async since ch is used in f. diff --git a/internal/ui/ui_mobile.go b/internal/ui/ui_mobile.go index 53e61b4af..887076235 100644 --- a/internal/ui/ui_mobile.go +++ b/internal/ui/ui_mobile.go @@ -394,6 +394,14 @@ func SetWindowDecorated(decorated bool) { // Do nothing } +func IsVsyncEnabled() bool { + return true +} + +func SetVsyncEnabled(enabled bool) { + // Do nothing +} + func UpdateTouches(touches []*input.Touch) { input.Get().UpdateTouches(touches) } diff --git a/run.go b/run.go index 3926db8f5..41d5403a8 100644 --- a/run.go +++ b/run.go @@ -520,3 +520,28 @@ func SetWindowIcon(iconImages []image.Image) { func DeviceScaleFactor() float64 { return devicescale.DeviceScale() } + +// IsVsyncEnabled returns a boolean value indicating whether +// the game uses the display's vsync. +// +// IsVsyncEnabled is concurrent-safe. +func IsVsyncEnabled() bool { + return ui.IsVsyncEnabled() +} + +// SetVsyncEnabled sets a boolean value indicating whether +// the game uses the display's vsync. +// +// If the given value is true, the game tries to sync the display's refresh rate. +// If false, the game ignores the display's refresh rate. +// The initial value is true. +// By disabling vsync, the game works more efficiently but consumes more CPU. +// +// Note that the state doesn't affect how many the run funciton is updated per second. +// +// SetVsyncEnabled doesn't work on mobiles so far. +// +// SetVsyncEnabled is concurrent-safe. +func SetVsyncEnabled(enabled bool) { + ui.SetVsyncEnabled(enabled) +}