From 0bdfeec61008065228e87fbae630f0321b4b9063 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Tue, 3 Jan 2023 21:12:10 +0900 Subject: [PATCH] internal/ui: use a separate render thread for Nintendo Switch Updates #2512 --- doc.go | 2 +- internal/ui/egl_nintendosdk.go | 5 ++- internal/ui/ui_nintendosdk.go | 59 +++++++++++++++++++++++++++++----- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/doc.go b/doc.go index edbd3a6f6..05e191ab1 100644 --- a/doc.go +++ b/doc.go @@ -99,7 +99,7 @@ // `ebitenginesinglethread` disables Ebitengine's thread safety to unlock maximum performance. If you use this you will have // to manage threads yourself. Functions like IsKeyPressed will no longer be concurrent-safe with this build tag. // They must be called from the main thread or the same goroutine as the given game's callback functions like Update -// to RunGame. +// to RunGame. `ebitenginesinglethread` works only with desktops. // // `microsoftgdk` is for Microsoft GDK (e.g. Xbox). // diff --git a/internal/ui/egl_nintendosdk.go b/internal/ui/egl_nintendosdk.go index 85b397046..6e734baf5 100644 --- a/internal/ui/egl_nintendosdk.go +++ b/internal/ui/egl_nintendosdk.go @@ -81,10 +81,13 @@ func (e *egl) init(nativeWindowHandle C.NativeWindowType) error { return fmt.Errorf("ui: eglCreateContext failed: error: %d", C.eglGetError()) } + return nil +} + +func (e *egl) makeContextCurrent() error { if r := C.eglMakeCurrent(e.display, e.surface, e.surface, e.context); r == 0 { return fmt.Errorf("ui: eglMakeCurrent failed") } - return nil } diff --git a/internal/ui/ui_nintendosdk.go b/internal/ui/ui_nintendosdk.go index edf1c8006..1325cbc90 100644 --- a/internal/ui/ui_nintendosdk.go +++ b/internal/ui/ui_nintendosdk.go @@ -21,11 +21,16 @@ package ui import "C" import ( + stdcontext "context" "runtime" + "golang.org/x/sync/errgroup" + "github.com/hajimehoshi/ebiten/v2/internal/gamepad" + "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" + "github.com/hajimehoshi/ebiten/v2/internal/thread" ) type graphicsDriverCreatorImpl struct{} @@ -61,6 +66,9 @@ type userInterfaceImpl struct { nativeTouches []C.struct_Touch egl egl + + mainThread *thread.OSThread + renderThread *thread.OSThread } func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error { @@ -78,19 +86,54 @@ func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error { initializeProfiler() - for { - recordProfilerHeartbeat() + u.mainThread = thread.NewOSThread() + u.renderThread = thread.NewOSThread() + graphicscommand.SetRenderThread(u.renderThread) - // TODO: Make a separate thread for rendering (#2512). - gamepad.Update() - u.updateInputState() + ctx, cancel := stdcontext.WithCancel(stdcontext.Background()) + defer cancel() - if err := u.context.updateFrame(u.graphicsDriver, float64(C.kScreenWidth), float64(C.kScreenHeight), deviceScaleFactor, u); err != nil { - return err + var wg errgroup.Group + + // Run the render thread. + wg.Go(func() error { + defer cancel() + _ = u.renderThread.Loop(ctx) + return nil + }) + + // Run the game thread. + wg.Go(func() error { + defer cancel() + + u.renderThread.Call(func() { + u.egl.makeContextCurrent() + }) + + for { + recordProfilerHeartbeat() + + u.mainThread.Call(func() { + gamepad.Update() + u.updateInputState() + }) + + if err := u.context.updateFrame(u.graphicsDriver, float64(C.kScreenWidth), float64(C.kScreenHeight), deviceScaleFactor, u); err != nil { + return err + } + + u.renderThread.Call(func() { + u.egl.swapBuffers() + }) } + }) - u.egl.swapBuffers() + // Run the main thread. + _ = u.mainThread.Loop(ctx) + if err := wg.Wait(); err != nil { + return err } + return nil } func (*userInterfaceImpl) DeviceScaleFactor() float64 {