From dd853050e9f5c31e2bce141a2ad2421619ca8529 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Thu, 29 Dec 2022 21:09:30 +0900 Subject: [PATCH] internal/ui: refactoring --- internal/ui/ui_glfw.go | 173 +++++++++++++++++++++-------------------- 1 file changed, 90 insertions(+), 83 deletions(-) diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index b99d97886..008ed57e4 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -1071,95 +1071,102 @@ func (u *userInterfaceImpl) loopGame() error { u.window.Destroy() glfw.Terminate() }) - for { - var unfocused bool - - // On Windows, the focusing state might be always false (#987). - // On Windows, even if a window is in another workspace, vsync seems to work. - // Then let's assume the window is always 'focused' as a workaround. - if runtime.GOOS != "windows" { - unfocused = u.window.GetAttrib(glfw.Focused) == glfw.False - } - - var t1, t2 time.Time - - if unfocused { - t1 = time.Now() - } - - var outsideWidth, outsideHeight float64 - var deviceScaleFactor float64 - var err error - if u.mainThread.Call(func() { - outsideWidth, outsideHeight, err = u.update() - deviceScaleFactor = u.deviceScaleFactor(u.currentMonitor()) - }); err != nil { + if err := u.updateGame(); err != nil { return err } - - if err := u.context.updateFrame(u.graphicsDriver, outsideWidth, outsideHeight, deviceScaleFactor, u); err != nil { - return err - } - - // Create icon images in a different goroutine (#1478). - // In the fullscreen mode, SetIcon fails (#1578). - if imgs := u.getIconImages(); len(imgs) > 0 && !u.isFullscreen() { - u.setIconImages(nil) - - // Convert the icons in the different goroutine, as (*ebiten.Image).At cannot be invoked - // from this goroutine. At works only in between BeginFrame and EndFrame. - go func() { - newImgs := make([]image.Image, len(imgs)) - for i, img := range imgs { - // TODO: If img is not *ebiten.Image, this converting is not necessary. - // However, this package cannot refer *ebiten.Image due to the package - // dependencies. - - b := img.Bounds() - rgba := image.NewRGBA(b) - for j := b.Min.Y; j < b.Max.Y; j++ { - for i := b.Min.X; i < b.Max.X; i++ { - rgba.Set(i, j, img.At(i, j)) - } - } - newImgs[i] = rgba - } - - u.mainThread.Call(func() { - // In the fullscreen mode, reset the icon images and try again later. - if u.isFullscreen() { - u.setIconImages(imgs) - return - } - u.window.SetIcon(newImgs) - }) - }() - } - - // swapBuffers also checks IsGL, so this condition is redundant. - // However, (*thread).Call is not good for performance due to channels. - // Let's avoid this whenever possible (#1367). - if u.graphicsDriver.IsGL() { - u.mainThread.Call(u.swapBuffers) - } - - if unfocused { - t2 = time.Now() - } - - // When a window is not focused, SwapBuffers might return immediately and CPU might be busy. - // Mitigate this by sleeping (#982). - if unfocused { - d := t2.Sub(t1) - const wait = time.Second / 60 - if d < wait { - time.Sleep(wait - d) - } - } } } +func (u *userInterfaceImpl) updateGame() error { + var unfocused bool + + // On Windows, the focusing state might be always false (#987). + // On Windows, even if a window is in another workspace, vsync seems to work. + // Then let's assume the window is always 'focused' as a workaround. + if runtime.GOOS != "windows" { + unfocused = u.window.GetAttrib(glfw.Focused) == glfw.False + } + + var t1, t2 time.Time + + if unfocused { + t1 = time.Now() + } + + var outsideWidth, outsideHeight float64 + var deviceScaleFactor float64 + var err error + if u.mainThread.Call(func() { + outsideWidth, outsideHeight, err = u.update() + deviceScaleFactor = u.deviceScaleFactor(u.currentMonitor()) + }); err != nil { + return err + } + + if err := u.context.updateFrame(u.graphicsDriver, outsideWidth, outsideHeight, deviceScaleFactor, u); err != nil { + return err + } + + // Create icon images in a different goroutine (#1478). + // In the fullscreen mode, SetIcon fails (#1578). + if imgs := u.getIconImages(); len(imgs) > 0 && !u.isFullscreen() { + u.setIconImages(nil) + + // Convert the icons in the different goroutine, as (*ebiten.Image).At cannot be invoked + // from this goroutine. At works only in between BeginFrame and EndFrame. + go func() { + newImgs := make([]image.Image, len(imgs)) + for i, img := range imgs { + // TODO: If img is not *ebiten.Image, this converting is not necessary. + // However, this package cannot refer *ebiten.Image due to the package + // dependencies. + + b := img.Bounds() + rgba := image.NewRGBA(b) + for j := b.Min.Y; j < b.Max.Y; j++ { + for i := b.Min.X; i < b.Max.X; i++ { + rgba.Set(i, j, img.At(i, j)) + } + } + newImgs[i] = rgba + } + + u.mainThread.Call(func() { + // In the fullscreen mode, reset the icon images and try again later. + if u.isFullscreen() { + u.setIconImages(imgs) + return + } + u.window.SetIcon(newImgs) + }) + }() + } + + // swapBuffers also checks IsGL, so this condition is redundant. + // However, (*thread).Call is not good for performance due to channels. + // Let's avoid this whenever possible (#1367). + if u.graphicsDriver.IsGL() { + u.mainThread.Call(u.swapBuffers) + } + + if unfocused { + t2 = time.Now() + } + + // When a window is not focused, SwapBuffers might return immediately and CPU might be busy. + // Mitigate this by sleeping (#982). + if unfocused { + d := t2.Sub(t1) + const wait = time.Second / 60 + if d < wait { + time.Sleep(wait - d) + } + } + + return nil +} + // swapBuffers must be called from the main thread. func (u *userInterfaceImpl) swapBuffers() { if u.graphicsDriver.IsGL() {