Add IsVsyncEnabled / SetVsyncEnabled

This enables the game to work more efficiently (but consume much
more CPU).

Fixes #405.
This commit is contained in:
Hajime Hoshi 2018-07-14 20:50:09 +09:00
parent aed0bf4a37
commit e25c237a01
5 changed files with 98 additions and 9 deletions

View File

@ -86,6 +86,7 @@ func update(screen *ebiten.Image) error {
fullscreen := ebiten.IsFullscreen() fullscreen := ebiten.IsFullscreen()
runnableInBackground := ebiten.IsRunnableInBackground() runnableInBackground := ebiten.IsRunnableInBackground()
cursorVisible := ebiten.IsCursorVisible() cursorVisible := ebiten.IsCursorVisible()
vsyncEnabled := ebiten.IsVsyncEnabled()
if inpututil.IsKeyJustPressed(ebiten.KeyUp) { if inpututil.IsKeyJustPressed(ebiten.KeyUp) {
screenHeight += d screenHeight += d
@ -126,11 +127,16 @@ func update(screen *ebiten.Image) error {
if inpututil.IsKeyJustPressed(ebiten.KeyC) { if inpututil.IsKeyJustPressed(ebiten.KeyC) {
cursorVisible = !cursorVisible cursorVisible = !cursorVisible
} }
if inpututil.IsKeyJustPressed(ebiten.KeyV) {
vsyncEnabled = !vsyncEnabled
}
ebiten.SetScreenSize(screenWidth, screenHeight) ebiten.SetScreenSize(screenWidth, screenHeight)
ebiten.SetScreenScale(screenScale) ebiten.SetScreenScale(screenScale)
ebiten.SetFullscreen(fullscreen) ebiten.SetFullscreen(fullscreen)
ebiten.SetRunnableInBackground(runnableInBackground) ebiten.SetRunnableInBackground(runnableInBackground)
ebiten.SetCursorVisible(cursorVisible) ebiten.SetCursorVisible(cursorVisible)
ebiten.SetVsyncEnabled(vsyncEnabled)
if inpututil.IsKeyJustPressed(ebiten.KeyI) { if inpututil.IsKeyJustPressed(ebiten.KeyI) {
ebiten.SetWindowIcon([]image.Image{createRandomIconImage()}) 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 B key to switch the run-in-background state
Press C key to switch the cursor visibility Press C key to switch the cursor visibility
Press I key to change the window icon Press I key to change the window icon
Press V key to switch vsync
Press Q key to quit Press Q key to quit
Cursor: (%d, %d) Cursor: (%d, %d)
FPS: %0.2f`, x, y, ebiten.CurrentFPS()) FPS: %0.2f`, x, y, ebiten.CurrentFPS())

View File

@ -49,6 +49,7 @@ type userInterface struct {
origPosX int origPosX int
origPosY int origPosY int
runnableInBackground bool runnableInBackground bool
vsync bool
initFullscreen bool initFullscreen bool
initCursorVisible bool initCursorVisible bool
@ -66,6 +67,7 @@ var (
origPosY: -1, origPosY: -1,
initCursorVisible: true, initCursorVisible: true,
initWindowDecorated: true, initWindowDecorated: true,
vsync: true,
} }
) )
@ -241,7 +243,7 @@ func SetScreenSize(width, height int) bool {
} }
r := false r := false
_ = u.runOnMainThread(func() error { _ = 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 nil
}) })
return r return r
@ -254,7 +256,7 @@ func SetScreenScale(scale float64) bool {
} }
r := false r := false
_ = u.runOnMainThread(func() error { _ = 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 nil
}) })
return r return r
@ -302,7 +304,7 @@ func SetFullscreen(fullscreen bool) {
} }
_ = u.runOnMainThread(func() error { _ = u.runOnMainThread(func() error {
u := currentUI u := currentUI
u.setScreenSize(u.width, u.height, u.scale, fullscreen) u.setScreenSize(u.width, u.height, u.scale, fullscreen, u.vsync)
return nil return nil
}) })
} }
@ -315,6 +317,32 @@ func IsRunnableInBackground() bool {
return currentUI.isRunnableInBackground() 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) { func SetWindowTitle(title string) {
if !currentUI.isRunning() { if !currentUI.isRunning() {
return 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. // The game is in window mode (not fullscreen mode) at the first state.
// Don't refer u.initFullscreen here to avoid some GLFW problems. // 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.title = title
u.window.SetTitle(title) u.window.SetTitle(title)
u.window.Show() u.window.Show()
@ -542,7 +570,7 @@ func (u *userInterface) update(g GraphicsContext) error {
_ = u.runOnMainThread(func() error { _ = u.runOnMainThread(func() error {
if u.isInitFullscreen() { if u.isInitFullscreen() {
u := currentUI 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) u.setInitFullscreen(false)
} }
return nil return nil
@ -603,8 +631,8 @@ func (u *userInterface) swapBuffers() {
} }
// setScreenSize must be called from the main thread. // setScreenSize must be called from the main thread.
func (u *userInterface) setScreenSize(width, height int, scale float64, fullscreen bool) bool { 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 { if u.width == width && u.height == height && u.scale == scale && u.fullscreen() == fullscreen && u.vsync == vsync {
return false return false
} }
@ -622,6 +650,7 @@ func (u *userInterface) setScreenSize(width, height int, scale float64, fullscre
u.height = height u.height = height
u.scale = scale u.scale = scale
u.fullscreenScale = 0 u.fullscreenScale = 0
u.vsync = vsync
// To make sure the current existing framebuffers are rendered, // To make sure the current existing framebuffers are rendered,
// swap buffers here before SetSize is called. // 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. // Window title might be lost on macOS after coming back from fullscreen.
u.window.SetTitle(u.title) u.window.SetTitle(u.title)
} }
// SwapInterval is affected by the current monitor of the window. // SwapInterval is affected by the current monitor of the window.
// This needs to be called at least after SetMonitor. // This needs to be called at least after SetMonitor.
// Without SwapInterval after SetMonitor, vsynch doesn't work (#375). // 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, // 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 // but is this correct? If glfw.SwapInterval(0) and the driver doesn't support triple
// buffering, what will happen? // buffering, what will happen?
glfw.SwapInterval(1) if u.vsync {
glfw.SwapInterval(1)
} else {
glfw.SwapInterval(0)
}
u.toChangeSize = true u.toChangeSize = true
return true return true

View File

@ -36,6 +36,7 @@ type userInterface struct {
scale float64 scale float64
fullscreen bool fullscreen bool
runnableInBackground bool runnableInBackground bool
vsync bool
sizeChanged bool sizeChanged bool
windowFocus bool windowFocus bool
@ -46,12 +47,14 @@ var currentUI = &userInterface{
sizeChanged: true, sizeChanged: true,
windowFocus: true, windowFocus: true,
pageVisible: true, pageVisible: true,
vsync: true,
} }
var ( var (
window = js.Global().Get("window") window = js.Global().Get("window")
document = js.Global().Get("document") document = js.Global().Get("document")
requestAnimationFrame = window.Get("requestAnimationFrame") requestAnimationFrame = window.Get("requestAnimationFrame")
setTimeoutForLoop = js.Global().Call("eval", "((f) => { setTimeout(f, 0); })")
) )
func MonitorSize() (int, int) { func MonitorSize() (int, int) {
@ -86,6 +89,14 @@ func IsRunnableInBackground() bool {
return currentUI.runnableInBackground return currentUI.runnableInBackground
} }
func SetVsyncEnabled(enabled bool) {
currentUI.vsync = enabled
}
func IsVsyncEnabled() bool {
return currentUI.vsync
}
func ScreenPadding() (x0, y0, x1, y1 float64) { func ScreenPadding() (x0, y0, x1, y1 float64) {
return 0, 0, 0, 0 return 0, 0, 0, 0
} }
@ -215,7 +226,11 @@ func (u *userInterface) loop(g GraphicsContext) error {
close(ch) close(ch)
return return
} }
requestAnimationFrame.Invoke(cf) if u.vsync {
requestAnimationFrame.Invoke(cf)
} else {
setTimeoutForLoop.Invoke(cf)
}
} }
cf = js.NewCallback(f) cf = js.NewCallback(f)
// Call f asyncly to be async since ch is used in f. // Call f asyncly to be async since ch is used in f.

View File

@ -394,6 +394,14 @@ func SetWindowDecorated(decorated bool) {
// Do nothing // Do nothing
} }
func IsVsyncEnabled() bool {
return true
}
func SetVsyncEnabled(enabled bool) {
// Do nothing
}
func UpdateTouches(touches []*input.Touch) { func UpdateTouches(touches []*input.Touch) {
input.Get().UpdateTouches(touches) input.Get().UpdateTouches(touches)
} }

25
run.go
View File

@ -520,3 +520,28 @@ func SetWindowIcon(iconImages []image.Image) {
func DeviceScaleFactor() float64 { func DeviceScaleFactor() float64 {
return devicescale.DeviceScale() 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)
}