mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 11:18:54 +01:00
uidriver/glfw: Force to swap buffer or recreate an window after toggling fullscreen
This change resolves a bug that an image lag remains after toggling fullscreen. On OpenGL, swapping buffer is necessary after toggling fullscreen. On Metal, recreating a window works after toggling fullscreen. Fixes #1004
This commit is contained in:
parent
2180613e2a
commit
80fbc9701c
@ -21,6 +21,7 @@ package glfw
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
@ -323,22 +324,14 @@ func (u *UserInterface) SetScreenSize(width, height int) {
|
|||||||
if !u.isRunning() {
|
if !u.isRunning() {
|
||||||
panic("glfw: SetScreenSize can't be called before the main loop starts")
|
panic("glfw: SetScreenSize can't be called before the main loop starts")
|
||||||
}
|
}
|
||||||
_ = u.t.Call(func() error {
|
u.setScreenSize(width, height, u.scale, u.isFullscreen(), u.vsync)
|
||||||
// TODO: What if the window is maximized? (#320)
|
|
||||||
u.setScreenSize(width, height, u.scale, u.isFullscreen(), u.vsync)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) SetScreenScale(scale float64) {
|
func (u *UserInterface) SetScreenScale(scale float64) {
|
||||||
if !u.isRunning() {
|
if !u.isRunning() {
|
||||||
panic("glfw: SetScreenScale can't be called before the main loop starts")
|
panic("glfw: SetScreenScale can't be called before the main loop starts")
|
||||||
}
|
}
|
||||||
_ = u.t.Call(func() error {
|
u.setScreenSize(u.width, u.height, scale, u.isFullscreen(), u.vsync)
|
||||||
// TODO: What if the window is maximized? (#320)
|
|
||||||
u.setScreenSize(u.width, u.height, scale, u.isFullscreen(), u.vsync)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) ScreenScale() float64 {
|
func (u *UserInterface) ScreenScale() float64 {
|
||||||
@ -378,10 +371,7 @@ func (u *UserInterface) SetFullscreen(fullscreen bool) {
|
|||||||
u.setInitFullscreen(fullscreen)
|
u.setInitFullscreen(fullscreen)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = u.t.Call(func() error {
|
u.setScreenSize(u.width, u.height, u.scale, fullscreen, u.vsync)
|
||||||
u.setScreenSize(u.width, u.height, u.scale, fullscreen, u.vsync)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) SetRunnableInBackground(runnableInBackground bool) {
|
func (u *UserInterface) SetRunnableInBackground(runnableInBackground bool) {
|
||||||
@ -403,10 +393,7 @@ func (u *UserInterface) SetVsyncEnabled(enabled bool) {
|
|||||||
u.m.Unlock()
|
u.m.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = u.t.Call(func() error {
|
u.setScreenSize(u.width, u.height, u.scale, u.isFullscreen(), enabled)
|
||||||
u.setScreenSize(u.width, u.height, u.scale, u.isFullscreen(), enabled)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) IsVsyncEnabled() bool {
|
func (u *UserInterface) IsVsyncEnabled() bool {
|
||||||
@ -676,6 +663,12 @@ func (u *UserInterface) createWindow() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) run(width, height int, scale float64, title string, context driver.UIContext) error {
|
func (u *UserInterface) run(width, height int, scale float64, title string, context driver.UIContext) error {
|
||||||
|
var (
|
||||||
|
m *glfw.Monitor
|
||||||
|
mx, my int
|
||||||
|
v *glfw.VidMode
|
||||||
|
)
|
||||||
|
|
||||||
if err := u.t.Call(func() error {
|
if err := u.t.Call(func() error {
|
||||||
if u.graphics.IsGL() {
|
if u.graphics.IsGL() {
|
||||||
glfw.WindowHint(glfw.ContextVersionMajor, 2)
|
glfw.WindowHint(glfw.ContextVersionMajor, 2)
|
||||||
@ -728,13 +721,20 @@ func (u *UserInterface) run(width, height int, scale float64, title string, cont
|
|||||||
// there is not the active window but the foreground window. After showing the current window, the
|
// there is not the active window but the foreground window. After showing the current window, the
|
||||||
// current window will be the active window. Thus, currentMonitor result varies before and after
|
// current window will be the active window. Thus, currentMonitor result varies before and after
|
||||||
// showing the window.
|
// showing the window.
|
||||||
m := u.currentMonitor()
|
m = u.currentMonitor()
|
||||||
mx, my := m.GetPos()
|
mx, my = m.GetPos()
|
||||||
v := m.GetVideoMode()
|
v = m.GetVideoMode()
|
||||||
|
|
||||||
// The game is in window mode (not fullscreen mode) at the first state.
|
return nil
|
||||||
// Don't refer u.initFullscreen here to avoid some GLFW problems.
|
}); err != nil {
|
||||||
u.setScreenSize(width, height, scale, false, u.vsync)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.vsync)
|
||||||
|
|
||||||
|
_ = u.t.Call(func() error {
|
||||||
// Get the window size before showing since window.Show might change the current
|
// Get the window size before showing since window.Show might change the current
|
||||||
// monitor which affects glfwSize result.
|
// monitor which affects glfwSize result.
|
||||||
w, h := u.glfwSize()
|
w, h := u.glfwSize()
|
||||||
@ -754,9 +754,7 @@ func (u *UserInterface) run(width, height int, scale float64, title string, cont
|
|||||||
u.window.SetPos(x, y)
|
u.window.SetPos(x, y)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
})
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var w unsafe.Pointer
|
var w unsafe.Pointer
|
||||||
_ = u.t.Call(func() error {
|
_ = u.t.Call(func() error {
|
||||||
@ -807,10 +805,10 @@ func (u *UserInterface) monitorScale() float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) updateSize(context driver.UIContext) {
|
func (u *UserInterface) updateSize(context driver.UIContext) {
|
||||||
|
u.setScreenSize(u.width, u.height, u.scale, u.isFullscreen(), u.vsync)
|
||||||
|
|
||||||
sizeChanged := false
|
sizeChanged := false
|
||||||
_ = u.t.Call(func() error {
|
_ = u.t.Call(func() error {
|
||||||
u.setScreenSize(u.width, u.height, u.scale, u.isFullscreen(), u.vsync)
|
|
||||||
|
|
||||||
if !u.toChangeSize {
|
if !u.toChangeSize {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -839,13 +837,10 @@ func (u *UserInterface) update(context driver.UIContext) error {
|
|||||||
return driver.RegularTermination
|
return driver.RegularTermination
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = u.t.Call(func() error {
|
if u.isInitFullscreen() {
|
||||||
if u.isInitFullscreen() {
|
u.setScreenSize(u.width, u.height, u.scale, true, u.vsync)
|
||||||
u.setScreenSize(u.width, u.height, u.scale, true, u.vsync)
|
u.setInitFullscreen(false)
|
||||||
u.setInitFullscreen(false)
|
}
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// This call is needed for initialization.
|
// This call is needed for initialization.
|
||||||
u.updateSize(context)
|
u.updateSize(context)
|
||||||
@ -876,15 +871,13 @@ func (u *UserInterface) update(context driver.UIContext) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the screen size when the window is resizable.
|
// Update the screen size when the window is resizable.
|
||||||
_ = u.t.Call(func() error {
|
// TODO: Need locks.
|
||||||
w, h := u.reqWidth, u.reqHeight
|
w, h := u.reqWidth, u.reqHeight
|
||||||
if w != 0 || h != 0 {
|
if w != 0 || h != 0 {
|
||||||
u.setScreenSize(w, h, u.scale, u.isFullscreen(), u.vsync)
|
u.setScreenSize(w, h, u.scale, u.isFullscreen(), u.vsync)
|
||||||
}
|
}
|
||||||
u.reqWidth = 0
|
u.reqWidth = 0
|
||||||
u.reqHeight = 0
|
u.reqHeight = 0
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -941,112 +934,142 @@ func (u *UserInterface) swapBuffers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setScreenSize must be called from the main thread.
|
|
||||||
func (u *UserInterface) setScreenSize(width, height int, scale float64, fullscreen bool, vsync bool) {
|
func (u *UserInterface) setScreenSize(width, height int, scale float64, fullscreen bool, vsync bool) {
|
||||||
if u.width == width && u.height == height && u.scale == scale && u.isFullscreen() == fullscreen && u.vsync == vsync && u.lastMonitorScale == u.monitorScale() {
|
windowRecreated := false
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// On Windows, giving a too small width doesn't call a callback (#165).
|
_ = u.t.Call(func() error {
|
||||||
// To prevent hanging up, return asap if the width is too small.
|
if u.width == width && u.height == height && u.scale == scale && u.isFullscreen() == fullscreen && u.vsync == vsync && u.lastMonitorScale == u.monitorScale() {
|
||||||
// 252 is an arbitrary number and I guess this is small enough.
|
return nil
|
||||||
minWindowWidth := 252
|
|
||||||
if u.window.GetAttrib(glfw.Decorated) == glfw.False {
|
|
||||||
minWindowWidth = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if width < 1 {
|
|
||||||
width = 1
|
|
||||||
}
|
|
||||||
if height < 1 {
|
|
||||||
height = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
u.width = width
|
|
||||||
u.windowWidth = width
|
|
||||||
s := scale * devicescale.GetAt(u.currentMonitor().GetPos())
|
|
||||||
if int(float64(width)*s) < minWindowWidth {
|
|
||||||
u.windowWidth = int(math.Ceil(float64(minWindowWidth) / s))
|
|
||||||
}
|
|
||||||
u.height = height
|
|
||||||
u.scale = scale
|
|
||||||
u.fullscreenScale = 0
|
|
||||||
u.vsync = vsync
|
|
||||||
u.lastMonitorScale = u.monitorScale()
|
|
||||||
|
|
||||||
// To make sure the current existing framebuffers are rendered,
|
|
||||||
// swap buffers here before SetSize is called.
|
|
||||||
u.swapBuffers()
|
|
||||||
|
|
||||||
if fullscreen {
|
|
||||||
if u.origPosX == invalidPos || u.origPosY == invalidPos {
|
|
||||||
u.origPosX, u.origPosY = u.window.GetPos()
|
|
||||||
}
|
|
||||||
m := u.currentMonitor()
|
|
||||||
v := m.GetVideoMode()
|
|
||||||
u.window.SetMonitor(m, 0, 0, v.Width, v.Height, v.RefreshRate)
|
|
||||||
} else {
|
|
||||||
if u.window.GetMonitor() != nil {
|
|
||||||
// Give dummy values as the window position and size.
|
|
||||||
// The new window position should be specifying after SetSize.
|
|
||||||
u.window.SetMonitor(nil, 0, 0, 16, 16, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oldW, oldH := u.window.GetSize()
|
// On Windows, giving a too small width doesn't call a callback (#165).
|
||||||
newW, newH := u.glfwSize()
|
// To prevent hanging up, return asap if the width is too small.
|
||||||
if oldW != newW || oldH != newH {
|
// 252 is an arbitrary number and I guess this is small enough.
|
||||||
ch := make(chan struct{})
|
minWindowWidth := 252
|
||||||
u.window.SetFramebufferSizeCallback(func(_ *glfw.Window, _, _ int) {
|
if u.window.GetAttrib(glfw.Decorated) == glfw.False {
|
||||||
u.window.SetFramebufferSizeCallback(nil)
|
minWindowWidth = 1
|
||||||
close(ch)
|
}
|
||||||
})
|
|
||||||
u.window.SetSize(u.glfwSize())
|
if width < 1 {
|
||||||
event:
|
width = 1
|
||||||
for {
|
}
|
||||||
|
if height < 1 {
|
||||||
|
height = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
u.width = width
|
||||||
|
u.windowWidth = width
|
||||||
|
s := scale * devicescale.GetAt(u.currentMonitor().GetPos())
|
||||||
|
if int(float64(width)*s) < minWindowWidth {
|
||||||
|
u.windowWidth = int(math.Ceil(float64(minWindowWidth) / s))
|
||||||
|
}
|
||||||
|
u.height = height
|
||||||
|
u.scale = scale
|
||||||
|
u.fullscreenScale = 0
|
||||||
|
u.vsync = vsync
|
||||||
|
u.lastMonitorScale = u.monitorScale()
|
||||||
|
|
||||||
|
// To make sure the current existing framebuffers are rendered,
|
||||||
|
// swap buffers here before SetSize is called.
|
||||||
|
u.swapBuffers()
|
||||||
|
|
||||||
|
if fullscreen {
|
||||||
|
if u.origPosX == invalidPos || u.origPosY == invalidPos {
|
||||||
|
u.origPosX, u.origPosY = u.window.GetPos()
|
||||||
|
}
|
||||||
|
m := u.currentMonitor()
|
||||||
|
v := m.GetVideoMode()
|
||||||
|
u.window.SetMonitor(m, 0, 0, v.Width, v.Height, v.RefreshRate)
|
||||||
|
|
||||||
|
// Swapping buffer is necesary to prevent the image lag (#1004).
|
||||||
|
if u.graphics.IsGL() {
|
||||||
glfw.PollEvents()
|
glfw.PollEvents()
|
||||||
select {
|
u.swapBuffers()
|
||||||
case <-ch:
|
}
|
||||||
break event
|
} else {
|
||||||
default:
|
if u.window.GetMonitor() != nil {
|
||||||
|
if u.graphics.IsGL() {
|
||||||
|
// When OpenGL is used, swapping buffer is enough to solve the image-lag
|
||||||
|
// issue (#1004). Rather, recreating window destroys GPU resources.
|
||||||
|
u.window.SetMonitor(nil, 0, 0, 16, 16, 0)
|
||||||
|
glfw.PollEvents()
|
||||||
|
u.swapBuffers()
|
||||||
|
} else {
|
||||||
|
// Recreate the window since an image lag remains after coming back from
|
||||||
|
// fullscreen (#1004).
|
||||||
|
if u.window != nil {
|
||||||
|
u.window.Destroy()
|
||||||
|
u.window = nil
|
||||||
|
}
|
||||||
|
if err := u.createWindow(); err != nil {
|
||||||
|
// TODO: This should return an error.
|
||||||
|
panic(fmt.Sprintf("glfw: failed to recreate window: %v", err))
|
||||||
|
}
|
||||||
|
windowRecreated = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if u.origPosX != invalidPos && u.origPosY != invalidPos {
|
oldW, oldH := u.window.GetSize()
|
||||||
x := u.origPosX
|
newW, newH := u.glfwSize()
|
||||||
y := u.origPosY
|
if oldW != newW || oldH != newH {
|
||||||
u.window.SetPos(x, y)
|
ch := make(chan struct{})
|
||||||
// Dirty hack for macOS (#703). Rendering doesn't work correctly with one SetPos, but work
|
u.window.SetFramebufferSizeCallback(func(_ *glfw.Window, _, _ int) {
|
||||||
// with two or more SetPos.
|
u.window.SetFramebufferSizeCallback(nil)
|
||||||
if runtime.GOOS == "darwin" {
|
close(ch)
|
||||||
u.window.SetPos(x+1, y)
|
})
|
||||||
u.window.SetPos(x, y)
|
u.window.SetSize(u.glfwSize())
|
||||||
|
event:
|
||||||
|
for {
|
||||||
|
glfw.PollEvents()
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
break event
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
u.origPosX = invalidPos
|
|
||||||
u.origPosY = invalidPos
|
if u.origPosX != invalidPos && u.origPosY != invalidPos {
|
||||||
|
x := u.origPosX
|
||||||
|
y := u.origPosY
|
||||||
|
u.window.SetPos(x, y)
|
||||||
|
// Dirty hack for macOS (#703). Rendering doesn't work correctly with one SetPos, but
|
||||||
|
// work with two or more SetPos.
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
u.window.SetPos(x+1, y)
|
||||||
|
u.window.SetPos(x, y)
|
||||||
|
}
|
||||||
|
u.origPosX = invalidPos
|
||||||
|
u.origPosY = invalidPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window title might be lost on macOS after coming back from fullscreen.
|
||||||
|
u.window.SetTitle(u.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window title might be lost on macOS after coming back from fullscreen.
|
if u.graphics.IsGL() {
|
||||||
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).
|
||||||
if u.graphics.IsGL() {
|
//
|
||||||
// SwapInterval is affected by the current monitor of the window.
|
// TODO: (#405) If triple buffering is needed, SwapInterval(0) should be called,
|
||||||
// This needs to be called at least after SetMonitor.
|
// but is this correct? If glfw.SwapInterval(0) and the driver doesn't support triple
|
||||||
// Without SwapInterval after SetMonitor, vsynch doesn't work (#375).
|
// buffering, what will happen?
|
||||||
//
|
if u.vsync {
|
||||||
// TODO: (#405) If triple buffering is needed, SwapInterval(0) should be called,
|
glfw.SwapInterval(1)
|
||||||
// but is this correct? If glfw.SwapInterval(0) and the driver doesn't support triple
|
} else {
|
||||||
// buffering, what will happen?
|
glfw.SwapInterval(0)
|
||||||
if u.vsync {
|
}
|
||||||
glfw.SwapInterval(1)
|
|
||||||
} else {
|
|
||||||
glfw.SwapInterval(0)
|
|
||||||
}
|
}
|
||||||
}
|
u.graphics.SetVsyncEnabled(vsync)
|
||||||
u.graphics.SetVsyncEnabled(vsync)
|
|
||||||
|
|
||||||
u.toChangeSize = true
|
u.toChangeSize = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if windowRecreated {
|
||||||
|
u.graphics.SetWindow(u.nativeWindow())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// currentMonitor returns the monitor most suitable with the current window.
|
// currentMonitor returns the monitor most suitable with the current window.
|
||||||
|
Loading…
Reference in New Issue
Block a user